├── .gitattributes
├── .gitignore
├── .npmrc
├── LICENSE
├── README.md
├── index.html
├── package.json
└── spec.emu
/.gitattributes:
--------------------------------------------------------------------------------
1 | index.html -diff merge=ours
2 | spec.js -diff merge=ours
3 | spec.css -diff merge=ours
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # nyc test coverage
18 | .nyc_output
19 |
20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
21 | .grunt
22 |
23 | # node-waf configuration
24 | .lock-wscript
25 |
26 | # Compiled binary addons (http://nodejs.org/api/addons.html)
27 | build/Release
28 |
29 | # Dependency directories
30 | node_modules
31 | jspm_packages
32 |
33 | # Optional npm cache directory
34 | .npm
35 |
36 | # Optional REPL history
37 | .node_repl_history
38 |
39 | # Only apps should have lockfiles
40 | yarn.lock
41 | package-lock.json
42 | npm-shrinkwrap.json
43 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | package-lock=false
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 ECMA TC39 and contributors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # proposal-logical-assignment
2 |
3 | A proposal to combine Logical Operators and Assignment Expressions:
4 |
5 | ```js
6 | // "Or Or Equals" (or, the Mallet operator :wink:)
7 | a ||= b;
8 | a || (a = b);
9 |
10 | // "And And Equals"
11 | a &&= b;
12 | a && (a = b);
13 |
14 | // "QQ Equals"
15 | a ??= b;
16 | a ?? (a = b);
17 | ```
18 |
19 | ## Status
20 |
21 | Current [Stage](https://tc39.es/process-document/): 4
22 |
23 | ## Champions
24 |
25 | - Justin Ridgewell ([@jridgewell](https://github.com/jridgewell/))
26 | - Hemanth HM ([@hemanth](https://github.com/hemanth/))
27 |
28 | ## Motivation
29 |
30 | Convenience operators, inspired by
31 | [Ruby's](https://docs.ruby-lang.org/en/2.5.0/syntax/assignment_rdoc.html#label-Abbreviated+Assignment).
32 | We already have a dozen [mathematical assignment
33 | operators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Assignment_Operators),
34 | but we don't have ones for the often used logical operators.
35 |
36 | ```js
37 | function example(a) {
38 | // Default `a` to "foo"
39 | if (!a) {
40 | a = 'foo';
41 | }
42 | }
43 | ```
44 |
45 | If statements work, but terseness would be nice. Especially when dealing
46 | with deep property assignment:
47 |
48 | ```js
49 | function example(opts) {
50 | // Ok, but could trigger setter.
51 | opts.foo = opts.foo ?? 'bar'
52 |
53 | // No setter, but 'feels wrong' to write.
54 | opts.baz ?? (opts.baz = 'qux');
55 | }
56 |
57 | example({ foo: 'foo' })
58 | ```
59 |
60 | With this proposal, we get terseness and we don't have to suffer from
61 | setter calls:
62 |
63 | ```js
64 | function example(opts) {
65 | // Setters are not needlessly called.
66 | opts.foo ??= 'bar'
67 |
68 | // No repetition of `opts.baz`.
69 | opts.baz ??= 'qux';
70 | }
71 |
72 | example({ foo: 'foo' })
73 | ```
74 |
75 | ## Semantics
76 |
77 | The logical assignment operators function a bit differently than their
78 | mathematical assignment friends. While math assignment operators
79 | _always_ trigger a set operation, logical assignment embraces their
80 | short-circuiting semantics to avoid it when possible.
81 |
82 | ```js
83 | let x = 0;
84 | const obj = {
85 | get x() {
86 | return x;
87 | },
88 |
89 | set x(value) {
90 | console.log('setter called');
91 | x = value;
92 | }
93 | };
94 |
95 | // This always logs "setter called"
96 | obj.x += 1;
97 | assert.equal(obj.x, 1);
98 |
99 | // Logical operators do not call setters unnecessarily
100 | // This will not log.
101 | obj.x ||= 2;
102 | assert.equal(obj.x, 1);
103 |
104 | // But setters are called if the operator does not short circuit
105 | // "setter called"
106 | obj.x &&= 3;
107 | assert.equal(obj.x, 3);
108 | ```
109 |
110 | In most cases, the fact that the set operation is short-circuited has no
111 | observable impact beyond performance. But when it has side effects, it
112 | is often desirable to avoid it when appropriate. In the following
113 | example, if the `.innerHTML` setter was triggered uselessly, it could
114 | result in the loss of state (such as focus) that is not serialized in
115 | HTML:
116 |
117 | ```js
118 | document.getElementById('previewZone').innerHTML ||= 'Nothing to preview';
119 | ```
120 |
121 | See discussion of short-circuit semantics in [#3](https://github.com/tc39/proposal-logical-assignment/issues/3). It also highlights differences already present in mathematical assignment operators in code like `obj.deep[key++] += 1` vs `obj.deep[key] = obj.deep[key++] + 1`.
122 |
123 | ## Related
124 |
125 | - [Ruby's logical operators](https://docs.ruby-lang.org/en/2.5.0/syntax/assignment_rdoc.html#label-Abbreviated+Assignment)
126 | - [Explainer on no-set semantics](http://www.rubyinside.com/what-rubys-double-pipe-or-equals-really-does-5488.html)
127 | - [CoffeeScript](http://coffeescript.org/#try:a%20%3D%201%0Ab%20%3D%202%0A%0A%0A%23%20%22Or%20Or%20Equals%22%20(or%2C%20the%20Mallet%20operator%20%3Awink%3A)%0Aa%20%7C%7C%3D%20b%3B%0Aa%20%7C%7C%20(a%20%3D%20b)%3B%0A%0A%23%20%22And%20And%20Equals%22%0Aa%20%26%26%3D%20b%3B%0Aa%20%26%26%20(a%20%3D%20b)%3B%0A%0A%23%20Eventually....%0A%23%20%22QQ%20Equals%22%0A%23a%20%3F%3F%3D%20b%3B%0A%23a%20%3F%3F%20(a%20%3D%20b)%3B%0A)
128 | - [C#'s null coalescing assignment](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/null-coalescing-assignment#detailed-design)
129 | - My very first [Babel PR](https://github.com/babel/babel/pull/516) (back when it was still [6to5](https://github.com/babel/babel/tree/ecd85f53b4764ada862537aa767699814f1f1fe2)). 😄
130 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
1856 | Each Logical Assignment operator embraces the short-circuiting semantics of the respective Binary Logical operator. If the operator short circuits, then the [[Set]] operation is skipped.
1857 |
1858 |
1859 | The new LogicalAssignmentPunctuator production defined in Punctuators section is used only to highlight the new additions. When merged with the main ecma262 spec, the new punctuators should just be added to OtherPunctuator.
1860 |
When this expression occurs within strict mode code, it is a runtime error if lref in step 1.e, 2, 2, 2, 2 is an unresolvable reference. If it is, a ReferenceError exception is thrown. Additionally, it is a runtime error if the lref in step 8, 7, 7, 6 is a reference to a data property with the attribute value { [[Writable]]: false }, to an accessor property with the attribute value { [[Set]]: undefined }, or to a non-existent property of an object for which the IsExtensible predicate returns the value false. In these cases a TypeError exception is thrown.
call is a Parse Node that represents a specific range of source text. When the following algorithms compare call to another Parse Node, it is a test of whether they represent the same source text.
A potential tail position call that is immediately followed by return GetValue of the call result is also a possible tail position call. Function calls cannot return reference values, so such a GetValue operation will always return the same value as the actual function call result.
All Software contained in this document ("Software") is protected by copyright and is being made available under the "BSD License", included below. This Software may be subject to third party rights (rights from parties other than Ecma International), including patent rights, and no licenses under such third party rights are granted under this license even if the third party concerned is a member of Ecma International. SEE THE ECMA CODE OF CONDUCT IN PATENT MATTERS AVAILABLE AT https://ecma-international.org/memento/codeofconduct.htm FOR INFORMATION REGARDING THE LICENSING OF PATENT CLAIMS THAT ARE REQUIRED TO IMPLEMENT ECMA INTERNATIONAL STANDARDS.
2194 |
2195 |
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
2196 |
2197 |
2198 |
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2199 |
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
2200 |
Neither the name of the authors nor Ecma International may be used to endorse or promote products derived from this software without specific prior written permission.
2201 |
2202 |
2203 |
THIS SOFTWARE IS PROVIDED BY THE ECMA INTERNATIONAL "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ECMA INTERNATIONAL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
20 | Each Logical Assignment operator embraces the short-circuiting semantics of the respective Binary Logical operator. If the operator short circuits, then the [[Set]] operation is skipped.
21 |
22 |
23 | The new `LogicalAssignmentPunctuator` production defined in Punctuators section is used only to highlight the new additions. When merged with the main ecma262 spec, the new punctuators should just be added to `OtherPunctuator`.
24 |
93 | It is a Syntax Error if |LeftHandSideExpression| is either an |ObjectLiteral| or an |ArrayLiteral| and |LeftHandSideExpression| is not covering an |AssignmentPattern|.
94 |
95 |
96 | It is an early Syntax Error if |LeftHandSideExpression| is neither an |ObjectLiteral| nor an |ArrayLiteral| and AssignmentTargetType of |LeftHandSideExpression| is not ~simple~.
97 |
160 | AssignmentExpression : LeftHandSideExpression `=` AssignmentExpression
161 |
162 | 1. If |LeftHandSideExpression| is neither an |ObjectLiteral| nor an |ArrayLiteral|, then
163 | 1. Let _lref_ be the result of evaluating |LeftHandSideExpression|.
164 | 1. ReturnIfAbrupt(_lref_).
165 | 1. If IsAnonymousFunctionDefinition(|AssignmentExpression|) and IsIdentifierRef of |LeftHandSideExpression| are both *true*, then
166 | 1. Let _rval_ be NamedEvaluation of |AssignmentExpression| with argument GetReferencedName(_lref_).
167 | 1. Else,
168 | 1. Let _rref_ be the result of evaluating |AssignmentExpression|.
169 | 1. Let _rval_ be ? GetValue(_rref_).
170 | 1. [id="step-assignmentexpression-evaluation-simple-putvalue"] Perform ? PutValue(_lref_, _rval_).
171 | 1. Return _rval_.
172 | 1. Let _assignmentPattern_ be the |AssignmentPattern| that is covered by |LeftHandSideExpression|.
173 | 1. Let _rref_ be the result of evaluating |AssignmentExpression|.
174 | 1. Let _rval_ be ? GetValue(_rref_).
175 | 1. Perform ? DestructuringAssignmentEvaluation of _assignmentPattern_ using _rval_ as the argument.
176 | 1. Return _rval_.
177 |
178 | AssignmentExpression : LeftHandSideExpression AssignmentOperator AssignmentExpression
179 |
180 | 1. Let _lref_ be the result of evaluating |LeftHandSideExpression|.
181 | 1. [id="step-assignmentexpression-evaluation-compound-getvalue"] Let _lval_ be ? GetValue(_lref_).
182 | 1. Let _rref_ be the result of evaluating |AssignmentExpression|.
183 | 1. Let _rval_ be ? GetValue(_rref_).
184 | 1. Let _assignmentOpText_ be the source text matched by |AssignmentOperator|.
185 | 1. Let _opText_ be the sequence of Unicode code points associated with _assignmentOpText_ in the following table:
186 |
187 |
188 |
189 |
_assignmentOpText_
_opText_
190 |
`**=`
`**`
191 |
`*=`
`*`
192 |
`/=`
`/`
193 |
`%=`
`%`
194 |
`+=`
`+`
195 |
`-=`
`-`
196 |
`<<=`
`<<`
197 |
`>>=`
`>>`
198 |
`>>>=`
`>>>`
199 |
`&=`
`&`
200 |
`^=`
`^`
201 |
`|=`
`|`
202 |
203 |
204 |
205 | 1. Let _r_ be ApplyStringOrNumericBinaryOperator(_lval_, _opText_, _rval_).
206 | 1. [id="step-assignmentexpression-evaluation-compound-putvalue"] Perform ? PutValue(_lref_, _r_).
207 | 1. Return _r_.
208 |
209 |
210 | AssignmentExpression : LeftHandSideExpression `&&=` AssignmentExpression
211 |
212 | 1. Let _lref_ be the result of evaluating |LeftHandSideExpression|.
213 | 1. [id="step-assignmentexpression-evaluation-lgcl-and-getvalue"] Let _lval_ be ? GetValue(_lref_).
214 | 1. Let _lbool_ be ! ToBoolean(_lval_).
215 | 1. If _lbool_ is *false*, return _lval_.
216 | 1. If IsAnonymousFunctionDefinition(|AssignmentExpression|) is *true* and IsIdentifierRef of |LeftHandSideExpression| is *true*, then
217 | 1. Let _rval_ be NamedEvaluation of |AssignmentExpression| with argument GetReferencedName(_lref_).
218 | 1. Else,
219 | 1. Let _rref_ be the result of evaluating |AssignmentExpression|.
220 | 1. Let _rval_ be ? GetValue(_rref_).
221 | 1. [id="step-assignmentexpression-evaluation-lgcl-and-putvalue"] Perform ? PutValue(_lref_, _rval_).
222 | 1. Return _rval_.
223 |
224 |
225 |
226 | AssignmentExpression : LeftHandSideExpression `||=` AssignmentExpression
227 |
228 | 1. Let _lref_ be the result of evaluating |LeftHandSideExpression|.
229 | 1. [id="step-assignmentexpression-evaluation-lgcl-or-getvalue"] Let _lval_ be ? GetValue(_lref_).
230 | 1. Let _lbool_ be ! ToBoolean(_lval_).
231 | 1. If _lbool_ is *true*, return _lval_.
232 | 1. If IsAnonymousFunctionDefinition(|AssignmentExpression|) is *true* and IsIdentifierRef of |LeftHandSideExpression| is *true*, then
233 | 1. Let _rval_ be NamedEvaluation of |AssignmentExpression| with argument GetReferencedName(_lref_).
234 | 1. Else,
235 | 1. Let _rref_ be the result of evaluating |AssignmentExpression|.
236 | 1. Let _rval_ be ? GetValue(_rref_).
237 | 1. [id="step-assignmentexpression-evaluation-lgcl-or-putvalue"] Perform ? PutValue(_lref_, _rval_).
238 | 1. Return _rval_.
239 |
240 |
241 |
242 | AssignmentExpression : LeftHandSideExpression `??=` AssignmentExpression
243 |
244 | 1. Let _lref_ be the result of evaluating |LeftHandSideExpression|.
245 | 1. [id="step-assignmentexpression-evaluation-lgcl-nullish-getvalue"] Let _lval_ be ? GetValue(_lref_).
246 | 1. If _lval_ is neither *undefined* nor *null*, return _lval_.
247 | 1. If IsAnonymousFunctionDefinition(|AssignmentExpression|) is *true* and IsIdentifierRef of |LeftHandSideExpression| is *true*, then
248 | 1. Let _rval_ be NamedEvaluation of |AssignmentExpression| with argument GetReferencedName(_lref_).
249 | 1. Else,
250 | 1. Let _rref_ be the result of evaluating |AssignmentExpression|.
251 | 1. Let _rval_ be ? GetValue(_rref_).
252 | 1. [id="step-assignmentexpression-evaluation-lgcl-nullish-putvalue"] Perform ? PutValue(_lref_, _rval_).
253 | 1. Return _rval_.
254 |
255 |
256 |
257 |
When this expression occurs within strict mode code, it is a runtime error if _lref_ in step , , , , is an unresolvable reference. If it is, a *ReferenceError* exception is thrown. Additionally, it is a runtime error if the _lref_ in step , , , is a reference to a data property with the attribute value { [[Writable]]: *false* }, to an accessor property with the attribute value { [[Set]]: *undefined* }, or to a non-existent property of an object for which the IsExtensible predicate returns the value *false*. In these cases a *TypeError* exception is thrown.
_call_ is a Parse Node that represents a specific range of source text. When the following algorithms compare _call_ to another Parse Node, it is a test of whether they represent the same source text.
A potential tail position call that is immediately followed by return GetValue of the call result is also a possible tail position call. Function calls cannot return reference values, so such a GetValue operation will always return the same value as the actual function call result.