Title: [260119] trunk
Revision
260119
Author
[email protected]
Date
2020-04-15 00:02:55 -0700 (Wed, 15 Apr 2020)

Log Message

[ESNext] Implement logical assignment operators
https://bugs.webkit.org/show_bug.cgi?id=209716

Reviewed by Ross Kirsling.

JSTests:

* stress/logical-assignment-operator-and.js: Added.
* stress/logical-assignment-operator-nullish.js: Added.
* stress/logical-assignment-operator-or.js: Added.

* test262/config.yaml:
* test262/expectations.yaml:
Right now, test262 expects an early error to be thrown if the lhs is not simple, which does
not match what we do with other read-modify assignment operators. This is likely to change
in the future to match existing behavior (throw a `ReferenceError`) [1].

[1]: <https://github.com/tc39/ecma262/issues/257#issuecomment-502878708>

Source/_javascript_Core:

Implement the logical assignment operators proposal, which is now Stage 3. It introduces
three new assignment operators which will only store the result of the rhs in the lhs if the
lhs meets the given condition:
 - `??=`, for if the lhs is nullish (`null` or `undefined`)
 - `||=`, for if the lhs is falsy
 - `&&=`, for if the lhs is truthy

This short circuiting can be beneficial as it can avoid a redundant store when used in the
common _javascript_ programming pattern of "defaulting" a parameter.

```js
    function foo(x) {
        x = x || 42;
    }
```

If `x` is a truthy value, it would result in the rhs `x` being stored back into the lhs `x`.
In some situations, this can have negative unintended side-effects, such as for `innerHTML`.

Logical assignment operators, however, are defined such that they only store if the rhs is
to actually be needed/used, skipping the redundant store and simply returning lhs otherwise.

In the case of readonly references, this means that an error is only thrown when the
assignment occurs, meaning that if the lhs already satisfies the condition it will be used
and returned with no error.

* parser/ParserTokens.h:
* parser/Lexer.cpp:
(JSC::Lexer<T>::lexWithoutClearingLineTerminator):
* parser/Parser.cpp:
(JSC::Parser<LexerType>::parseAssignmentExpression):

* parser/ASTBuilder.h:
(JSC::ASTBuilder::makeAssignNode):
* parser/Nodes.h:
* parser/NodeConstructors.h:
(JSC::ShortCircuitReadModifyResolveNode::ShortCircuitReadModifyResolveNode): Added.
(JSC::ShortCircuitReadModifyBracketNode::ShortCircuitReadModifyBracketNode): Added.
(JSC::ShortCircuitReadModifyDotNode::ShortCircuitReadModifyDotNode): Added.
* bytecompiler/NodesCodegen.cpp:
(JSC::emitShortCircuitAssignment): Added.
(JSC::ShortCircuitReadModifyResolveNode::emitBytecode): Added.
(JSC::ShortCircuitReadModifyDotNode::emitBytecode): Added.
(JSC::ShortCircuitReadModifyBracketNode::emitBytecode): Added.

* runtime/OptionsList.h:
Add a `useLogicalAssignmentOperators` setting for controlling this feature.

Tools:

* Scripts/run-jsc-stress-tests:

Modified Paths

Added Paths

Diff

Modified: trunk/JSTests/ChangeLog (260118 => 260119)


--- trunk/JSTests/ChangeLog	2020-04-15 06:20:39 UTC (rev 260118)
+++ trunk/JSTests/ChangeLog	2020-04-15 07:02:55 UTC (rev 260119)
@@ -1,3 +1,22 @@
+2020-04-15  Devin Rousso  <[email protected]>
+
+        [ESNext] Implement logical assignment operators
+        https://bugs.webkit.org/show_bug.cgi?id=209716
+
+        Reviewed by Ross Kirsling.
+
+        * stress/logical-assignment-operator-and.js: Added.
+        * stress/logical-assignment-operator-nullish.js: Added.
+        * stress/logical-assignment-operator-or.js: Added.
+
+        * test262/config.yaml:
+        * test262/expectations.yaml:
+        Right now, test262 expects an early error to be thrown if the lhs is not simple, which does
+        not match what we do with other read-modify assignment operators. This is likely to change
+        in the future to match existing behavior (throw a `ReferenceError`) [1].
+
+        [1]: <https://github.com/tc39/ecma262/issues/257#issuecomment-502878708>
+
 2020-04-14  Saam Barati  <[email protected]>
 
         Skip all low executable memory wasm tests on arm64

Added: trunk/JSTests/stress/logical-assignment-operator-and.js (0 => 260119)


--- trunk/JSTests/stress/logical-assignment-operator-and.js	                        (rev 0)
+++ trunk/JSTests/stress/logical-assignment-operator-and.js	2020-04-15 07:02:55 UTC (rev 260119)
@@ -0,0 +1,1684 @@
+//@ runLogicalAssignmentOperatorsEnabled
+
+function shouldBe(func, expected) {
+    let actual = func();
+    if (typeof expected === "function" ? !(actual instanceof expected) : actual !== expected)
+        throw new Error(`expected ${JSON.stringify(expected)} but got ${JSON.stringify(actual)}`);
+}
+
+function shouldThrow(func, errorType) {
+    let error;
+    try {
+        func();
+    } catch (e) {
+        error = e;
+    }
+
+    if (!(error instanceof errorType))
+        throw new Error(`Expected ${errorType.name} but saw ${error && error.name}!`);
+}
+
+function shouldThrowSyntaxError(script) {
+    let error;
+    try {
+        eval(script);
+    } catch (e) {
+        error = e;
+    }
+
+    if (!(error instanceof SyntaxError))
+        throw new Error(`Expected SyntaxError but saw ${error && error.name}!`);
+}
+
+shouldBe(function() {
+    let x;
+    return x &&= 42;
+}, undefined);
+
+shouldBe(function() {
+    let x;
+    x &&= 42;
+    return x;
+}, undefined);
+
+shouldBe(function() {
+    let x = undefined;
+    return x &&= 42;
+}, undefined);
+
+shouldBe(function() {
+    let x = undefined;
+    x &&= 42;
+    return x;
+}, undefined);
+
+shouldBe(function() {
+    let x = null;
+    return x &&= 42;
+}, null);
+
+shouldBe(function() {
+    let x = null;
+    x &&= 42;
+    return x;
+}, null);
+
+shouldBe(function() {
+    let x = true;
+    return x &&= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = true;
+    x &&= 42;
+    return x;
+}, 42);
+
+shouldBe(function() {
+    let x = false;
+    return x &&= 42;
+}, false);
+
+shouldBe(function() {
+    let x = false;
+    x &&= 42;
+    return x;
+}, false);
+
+shouldBe(function() {
+    let x = 0;
+    return x &&= 42;
+}, 0);
+
+shouldBe(function() {
+    let x = 0;
+    x &&= 42;
+    return x;
+}, 0);
+
+shouldBe(function() {
+    let x = 1;
+    return x &&= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = 1;
+    x &&= 42;
+    return x;
+}, 42);
+
+shouldBe(function() {
+    let x = "";
+    return x &&= 42;
+}, "");
+
+shouldBe(function() {
+    let x = "";
+    x &&= 42;
+    return x;
+}, "");
+
+shouldBe(function() {
+    let x = "test";
+    return x &&= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = "test";
+    x &&= 42;
+    return x;
+}, 42);
+
+shouldBe(function() {
+    let x = {};
+    return x &&= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = {};
+    x &&= 42;
+    return x;
+}, 42);
+
+shouldBe(function() {
+    let x = [];
+    return x &&= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = [];
+    x &&= 42;
+    return x;
+}, 42);
+
+
+
+shouldBe(function() {
+    const x = undefined;
+    return x &&= 42;
+}, undefined);
+
+shouldBe(function() {
+    const x = undefined;
+    x &&= 42;
+    return x;
+}, undefined);
+
+shouldBe(function() {
+    const x = null;
+    return x &&= 42;
+}, null);
+
+shouldBe(function() {
+    const x = null;
+    x &&= 42;
+    return x;
+}, null);
+
+shouldThrow(function() {
+    const x = true;
+    return x &&= 42;
+}, TypeError);
+
+shouldBe(function() {
+    const x = false;
+    return x &&= 42;
+}, false);
+
+shouldBe(function() {
+    const x = false;
+    x &&= 42;
+    return x;
+}, false);
+
+shouldBe(function() {
+    const x = 0;
+    return x &&= 42;
+}, 0);
+
+shouldBe(function() {
+    const x = 0;
+    x &&= 42;
+    return x;
+}, 0);
+
+shouldThrow(function() {
+    const x = 1;
+    return x &&= 42;
+}, TypeError);
+
+shouldBe(function() {
+    const x = "";
+    return x &&= 42;
+}, "");
+
+shouldBe(function() {
+    const x = "";
+    x &&= 42;
+    return x;
+}, "");
+
+shouldThrow(function() {
+    const x = "test";
+    return x &&= 42;
+}, TypeError);
+
+shouldThrow(function() {
+    const x = {};
+    return x &&= 42;
+}, TypeError);
+
+shouldThrow(function() {
+    const x = [];
+    return x &&= 42;
+}, TypeError);
+
+
+
+shouldBe(function() {
+    let x = {};
+    return x.a &&= 42;
+}, undefined);
+
+shouldBe(function() {
+    let x = {};
+    x.a &&= 42;
+    return x.a;
+}, undefined);
+
+shouldBe(function() {
+    let x = {a: undefined};
+    return x.a &&= 42;
+}, undefined);
+
+shouldBe(function() {
+    let x = {a: undefined};
+    x.a &&= 42;
+    return x.a;
+}, undefined);
+
+shouldBe(function() {
+    let x = {a: null};
+    return x.a &&= 42;
+}, null);
+
+shouldBe(function() {
+    let x = {a: null};
+    x.a &&= 42;
+    return x.a;
+}, null);
+
+shouldBe(function() {
+    let x = {a: true};
+    return x.a &&= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = {a: true};
+    x.a &&= 42;
+    return x.a;
+}, 42);
+
+shouldBe(function() {
+    let x = {a: false};
+    return x.a &&= 42;
+}, false);
+
+shouldBe(function() {
+    let x = {a: false};
+    x.a &&= 42;
+    return x.a;
+}, false);
+
+shouldBe(function() {
+    let x = {a: 0};
+    return x.a &&= 42;
+}, 0);
+
+shouldBe(function() {
+    let x = {a: 0};
+    x.a &&= 42;
+    return x.a;
+}, 0);
+
+shouldBe(function() {
+    let x = {a: 1};
+    return x.a &&= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = {a: 1};
+    x.a &&= 42;
+    return x.a;
+}, 42);
+
+shouldBe(function() {
+    let x = {a: ""};
+    return x.a &&= 42;
+}, "");
+
+shouldBe(function() {
+    let x = {a: ""};
+    x.a &&= 42;
+    return x.a;
+}, "");
+
+shouldBe(function() {
+    let x = {a: "test"};
+    return x.a &&= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = {a: "test"};
+    x.a &&= 42;
+    return x.a;
+}, 42);
+
+shouldBe(function() {
+    let x = {a: {}};
+    return x.a &&= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = {a: {}};
+    x.a &&= 42;
+    return x.a;
+}, 42);
+
+shouldBe(function() {
+    let x = {a: []};
+    return x.a &&= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = {a: []};
+    x.a &&= 42;
+    return x.a;
+}, 42);
+
+
+
+shouldBe(function() {
+    const x = {};
+    return x.a &&= 42;
+}, undefined);
+
+shouldBe(function() {
+    const x = {};
+    x.a &&= 42;
+    return x.a;
+}, undefined);
+
+shouldBe(function() {
+    const x = {a: undefined};
+    return x.a &&= 42;
+}, undefined);
+
+shouldBe(function() {
+    const x = {a: undefined};
+    x.a &&= 42;
+    return x.a;
+}, undefined);
+
+shouldBe(function() {
+    const x = {a: null};
+    return x.a &&= 42;
+}, null);
+
+shouldBe(function() {
+    const x = {a: null};
+    x.a &&= 42;
+    return x.a;
+}, null);
+
+shouldBe(function() {
+    const x = {a: true};
+    return x.a &&= 42;
+}, 42);
+
+shouldBe(function() {
+    const x = {a: true};
+    x.a &&= 42;
+    return x.a;
+}, 42);
+
+shouldBe(function() {
+    const x = {a: false};
+    return x.a &&= 42;
+}, false);
+
+shouldBe(function() {
+    const x = {a: false};
+    x.a &&= 42;
+    return x.a;
+}, false);
+
+shouldBe(function() {
+    const x = {a: 0};
+    return x.a &&= 42;
+}, 0);
+
+shouldBe(function() {
+    const x = {a: 0};
+    x.a &&= 42;
+    return x.a;
+}, 0);
+
+shouldBe(function() {
+    const x = {a: 1};
+    return x.a &&= 42;
+}, 42);
+
+shouldBe(function() {
+    const x = {a: 1};
+    x.a &&= 42;
+    return x.a;
+}, 42);
+
+shouldBe(function() {
+    const x = {a: ""};
+    return x.a &&= 42;
+}, "");
+
+shouldBe(function() {
+    const x = {a: ""};
+    x.a &&= 42;
+    return x.a;
+}, "");
+
+shouldBe(function() {
+    const x = {a: "test"};
+    return x.a &&= 42;
+}, 42);
+
+shouldBe(function() {
+    const x = {a: "test"};
+    x.a &&= 42;
+    return x.a;
+}, 42);
+
+shouldBe(function() {
+    const x = {a: {}};
+    return x.a &&= 42;
+}, 42);
+
+shouldBe(function() {
+    const x = {a: {}};
+    x.a &&= 42;
+    return x.a;
+}, 42);
+
+shouldBe(function() {
+    const x = {a: []};
+    return x.a &&= 42;
+}, 42);
+
+shouldBe(function() {
+    const x = {a: []};
+    x.a &&= 42;
+    return x.a;
+}, 42);
+
+
+
+shouldBe(function() {
+    let x = {};
+    return x["a"] &&= 42;
+}, undefined);
+
+shouldBe(function() {
+    let x = {};
+    x["a"] &&= 42;
+    return x["a"];
+}, undefined);
+
+shouldBe(function() {
+    let x = {a: undefined};
+    return x["a"] &&= 42;
+}, undefined);
+
+shouldBe(function() {
+    let x = {a: undefined};
+    x["a"] &&= 42;
+    return x["a"];
+}, undefined);
+
+shouldBe(function() {
+    let x = {a: null};
+    return x["a"] &&= 42;
+}, null);
+
+shouldBe(function() {
+    let x = {a: null};
+    x["a"] &&= 42;
+    return x["a"];
+}, null);
+
+shouldBe(function() {
+    let x = {a: true};
+    return x["a"] &&= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = {a: true};
+    x["a"] &&= 42;
+    return x["a"];
+}, 42);
+
+shouldBe(function() {
+    let x = {a: false};
+    return x["a"] &&= 42;
+}, false);
+
+shouldBe(function() {
+    let x = {a: false};
+    x["a"] &&= 42;
+    return x["a"];
+}, false);
+
+shouldBe(function() {
+    let x = {a: 0};
+    return x["a"] &&= 42;
+}, 0);
+
+shouldBe(function() {
+    let x = {a: 0};
+    x["a"] &&= 42;
+    return x["a"];
+}, 0);
+
+shouldBe(function() {
+    let x = {a: 1};
+    return x["a"] &&= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = {a: 1};
+    x["a"] &&= 42;
+    return x["a"];
+}, 42);
+
+shouldBe(function() {
+    let x = {a: ""};
+    return x["a"] &&= 42;
+}, "");
+
+shouldBe(function() {
+    let x = {a: ""};
+    x["a"] &&= 42;
+    return x["a"];
+}, "");
+
+shouldBe(function() {
+    let x = {a: "test"};
+    return x["a"] &&= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = {a: "test"};
+    x["a"] &&= 42;
+    return x["a"];
+}, 42);
+
+shouldBe(function() {
+    let x = {a: {}};
+    return x["a"] &&= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = {a: {}};
+    x["a"] &&= 42;
+    return x["a"];
+}, 42);
+
+shouldBe(function() {
+    let x = {a: []};
+    return x["a"] &&= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = {a: []};
+    x["a"] &&= 42;
+    return x["a"];
+}, 42);
+
+
+
+shouldBe(function() {
+    const x = {};
+    return x["a"] &&= 42;
+}, undefined);
+
+shouldBe(function() {
+    const x = {};
+    x["a"] &&= 42;
+    return x["a"];
+}, undefined);
+
+shouldBe(function() {
+    const x = {a: undefined};
+    return x["a"] &&= 42;
+}, undefined);
+
+shouldBe(function() {
+    const x = {a: undefined};
+    x["a"] &&= 42;
+    return x["a"];
+}, undefined);
+
+shouldBe(function() {
+    const x = {a: null};
+    return x["a"] &&= 42;
+}, null);
+
+shouldBe(function() {
+    const x = {a: null};
+    x["a"] &&= 42;
+    return x["a"];
+}, null);
+
+shouldBe(function() {
+    const x = {a: true};
+    return x["a"] &&= 42;
+}, 42);
+
+shouldBe(function() {
+    const x = {a: true};
+    x["a"] &&= 42;
+    return x["a"];
+}, 42);
+
+shouldBe(function() {
+    const x = {a: false};
+    return x["a"] &&= 42;
+}, false);
+
+shouldBe(function() {
+    const x = {a: false};
+    x["a"] &&= 42;
+    return x["a"];
+}, false);
+
+shouldBe(function() {
+    const x = {a: 0};
+    return x["a"] &&= 42;
+}, 0);
+
+shouldBe(function() {
+    const x = {a: 0};
+    x["a"] &&= 42;
+    return x["a"];
+}, 0);
+
+shouldBe(function() {
+    const x = {a: 1};
+    return x["a"] &&= 42;
+}, 42);
+
+shouldBe(function() {
+    const x = {a: 1};
+    x["a"] &&= 42;
+    return x["a"];
+}, 42);
+
+shouldBe(function() {
+    const x = {a: ""};
+    return x["a"] &&= 42;
+}, "");
+
+shouldBe(function() {
+    const x = {a: ""};
+    x["a"] &&= 42;
+    return x["a"];
+}, "");
+
+shouldBe(function() {
+    const x = {a: "test"};
+    return x["a"] &&= 42;
+}, 42);
+
+shouldBe(function() {
+    const x = {a: "test"};
+    x["a"] &&= 42;
+    return x["a"];
+}, 42);
+
+shouldBe(function() {
+    const x = {a: {}};
+    return x["a"] &&= 42;
+}, 42);
+
+shouldBe(function() {
+    const x = {a: {}};
+    x["a"] &&= 42;
+    return x["a"];
+}, 42);
+
+shouldBe(function() {
+    const x = {a: []};
+    return x["a"] &&= 42;
+}, 42);
+
+shouldBe(function() {
+    const x = {a: []};
+    x["a"] &&= 42;
+    return x["a"];
+}, 42);
+
+
+
+shouldBe(function() {
+    let x = [];
+    return x[0] &&= 42;
+}, undefined);
+
+shouldBe(function() {
+    let x = [];
+    x[0] &&= 42;
+    return x[0];
+}, undefined);
+
+shouldBe(function() {
+    let x = [undefined];
+    return x[0] &&= 42;
+}, undefined);
+
+shouldBe(function() {
+    let x = [undefined];
+    x[0] &&= 42;
+    return x[0];
+}, undefined);
+
+shouldBe(function() {
+    let x = [null];
+    return x[0] &&= 42;
+}, null);
+
+shouldBe(function() {
+    let x = [null];
+    x[0] &&= 42;
+    return x[0];
+}, null);
+
+shouldBe(function() {
+    let x = [true];
+    return x[0] &&= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = [true];
+    x[0] &&= 42;
+    return x[0];
+}, 42);
+
+shouldBe(function() {
+    let x = [false];
+    return x[0] &&= 42;
+}, false);
+
+shouldBe(function() {
+    let x = [false];
+    x[0] &&= 42;
+    return x[0];
+}, false);
+
+shouldBe(function() {
+    let x = [0];
+    return x[0] &&= 42;
+}, 0);
+
+shouldBe(function() {
+    let x = [0];
+    x[0] &&= 42;
+    return x[0];
+}, 0);
+
+shouldBe(function() {
+    let x = [1];
+    return x[0] &&= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = [1];
+    x[0] &&= 42;
+    return x[0];
+}, 42);
+
+shouldBe(function() {
+    let x = [""];
+    return x[0] &&= 42;
+}, "");
+
+shouldBe(function() {
+    let x = [""];
+    x[0] &&= 42;
+    return x[0];
+}, "");
+
+shouldBe(function() {
+    let x = ["test"];
+    return x[0] &&= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = ["test"];
+    x[0] &&= 42;
+    return x[0];
+}, 42);
+
+shouldBe(function() {
+    let x = [{}];
+    return x[0] &&= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = [{}];
+    x[0] &&= 42;
+    return x[0];
+}, 42);
+
+shouldBe(function() {
+    let x = [[]];
+    return x[0] &&= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = [[]];
+    x[0] &&= 42;
+    return x[0];
+}, 42);
+
+
+
+shouldBe(function() {
+    const x = [];
+    return x[0] &&= 42;
+}, undefined);
+
+shouldBe(function() {
+    const x = [];
+    x[0] &&= 42;
+    return x[0];
+}, undefined);
+
+shouldBe(function() {
+    const x = [undefined];
+    return x[0] &&= 42;
+}, undefined);
+
+shouldBe(function() {
+    const x = [undefined];
+    x[0] &&= 42;
+    return x[0];
+}, undefined);
+
+shouldBe(function() {
+    const x = [null];
+    return x[0] &&= 42;
+}, null);
+
+shouldBe(function() {
+    const x = [null];
+    x[0] &&= 42;
+    return x[0];
+}, null);
+
+shouldBe(function() {
+    const x = [true];
+    return x[0] &&= 42;
+}, 42);
+
+shouldBe(function() {
+    const x = [true];
+    x[0] &&= 42;
+    return x[0];
+}, 42);
+
+shouldBe(function() {
+    const x = [false];
+    return x[0] &&= 42;
+}, false);
+
+shouldBe(function() {
+    const x = [false];
+    x[0] &&= 42;
+    return x[0];
+}, false);
+
+shouldBe(function() {
+    const x = [0];
+    return x[0] &&= 42;
+}, 0);
+
+shouldBe(function() {
+    const x = [0];
+    x[0] &&= 42;
+    return x[0];
+}, 0);
+
+shouldBe(function() {
+    const x = [1];
+    return x[0] &&= 42;
+}, 42);
+
+shouldBe(function() {
+    const x = [1];
+    x[0] &&= 42;
+    return x[0];
+}, 42);
+
+shouldBe(function() {
+    const x = [""];
+    return x[0] &&= 42;
+}, "");
+
+shouldBe(function() {
+    const x = [""];
+    x[0] &&= 42;
+    return x[0];
+}, "");
+
+shouldBe(function() {
+    const x = ["test"];
+    return x[0] &&= 42;
+}, 42);
+
+shouldBe(function() {
+    const x = ["test"];
+    x[0] &&= 42;
+    return x[0];
+}, 42);
+
+shouldBe(function() {
+    const x = [{}];
+    return x[0] &&= 42;
+}, 42);
+
+shouldBe(function() {
+    const x = [{}];
+    x[0] &&= 42;
+    return x[0];
+}, 42);
+
+shouldBe(function() {
+    const x = [[]];
+    return x[0] &&= 42;
+}, 42);
+
+shouldBe(function() {
+    const x = [[]];
+    x[0] &&= 42;
+    return x[0];
+}, 42);
+
+
+
+shouldBe(function() {
+    let x = false;
+    let y = 1;
+    let z = 2;
+    return x &&= y + z;
+}, false);
+
+shouldBe(function() {
+    let x = false;
+    let y = 1;
+    let z = 2;
+    return x &&= y = z;
+}, false);
+
+shouldBe(function() {
+    let x = false;
+    let y = 1;
+    let z = 2;
+    return x &&= y && z;
+}, false);
+
+shouldBe(function() {
+    let x = false;
+    let y = 1;
+    let z = 2;
+    return x &&= y ?? z;
+}, false);
+
+shouldBe(function() {
+    let x = false;
+    let y = 1;
+    let z = 2;
+    return x &&= y || z;
+}, false);
+
+shouldBe(function() {
+    let x = false;
+    let y = 1;
+    let z = 2;
+    return x &&= y &&= z;
+}, false);
+
+shouldBe(function() {
+    let x = false;
+    let y = 1;
+    let z = 2;
+    return x &&= y ??= z;
+}, false);
+
+shouldBe(function() {
+    let x = false;
+    let y = 1;
+    let z = 2;
+    return x &&= y ||= z;
+}, false);
+
+
+
+shouldBe(function() {
+    let x = true;
+    let y = 1;
+    let z = 2;
+    return x &&= y + z;
+}, 3);
+
+shouldBe(function() {
+    let x = true;
+    let y = 1;
+    let z = 2;
+    return x &&= y = z;
+}, 2);
+
+shouldBe(function() {
+    let x = true;
+    let y = 1;
+    let z = 2;
+    return x &&= y && z;
+}, 2);
+
+shouldBe(function() {
+    let x = true;
+    let y = 1;
+    let z = 2;
+    return x &&= y ?? z;
+}, 1);
+
+shouldBe(function() {
+    let x = true;
+    let y = 1;
+    let z = 2;
+    return x &&= y || z;
+}, 1);
+
+shouldBe(function() {
+    let x = true;
+    let y = 1;
+    let z = 2;
+    return x &&= y &&= z;
+}, 2);
+
+shouldBe(function() {
+    let x = true;
+    let y = 1;
+    let z = 2;
+    return x &&= y ??= z;
+}, 1);
+
+shouldBe(function() {
+    let x = true;
+    let y = 1;
+    let z = 2;
+    return x &&= y ||= z;
+}, 1);
+
+
+
+shouldBe(function() {
+    let log = [];
+
+    let a = true;
+    let x = {
+        get a() {
+            log.push("get");
+            return a;
+        },
+        set a(v) {
+            log.push("set");
+            a = v;
+        },
+    };
+
+    x.a &&= 42;
+    x.a &&= 42;
+
+    return log.join(" ") + " " + a;
+}, "get set get set 42");
+
+shouldBe(function() {
+    let log = [];
+
+    let a = false;
+    let x = {
+        get a() {
+            log.push("get");
+            return a;
+        },
+        set a(v) {
+            log.push("set");
+            a = v;
+        },
+    };
+
+    x.a &&= 42;
+    x.a &&= 42;
+
+    return log.join(" ") + " " + a;
+}, "get get false");
+
+shouldBe(function() {
+    let log = [];
+
+    let a = undefined;
+    let x = {
+        get a() {
+            log.push("get");
+            return a;
+        },
+        set a(v) {
+            log.push("set");
+            a = v;
+        },
+    };
+
+    x.a &&= 42;
+    x.a &&= 42;
+
+    return log.join(" ") + " " + a;
+}, "get get undefined");
+
+
+
+shouldBe(function() {
+    let log = [];
+
+    let a = true;
+    let x = {
+        get a() {
+            log.push("get");
+            return a;
+        },
+        set a(v) {
+            log.push("set");
+            a = v;
+        },
+    };
+
+    x["a"] &&= 42;
+    x["a"] &&= 42;
+
+    return log.join(" ") + " " + a;
+}, "get set get set 42");
+
+shouldBe(function() {
+    let log = [];
+
+    let a = false;
+    let x = {
+        get a() {
+            log.push("get");
+            return a;
+        },
+        set a(v) {
+            log.push("set");
+            a = v;
+        },
+    };
+
+    x["a"] &&= 42;
+    x["a"] &&= 42;
+
+    return log.join(" ") + " " + a;
+}, "get get false");
+
+shouldBe(function() {
+    let log = [];
+
+    let a = undefined;
+    let x = {
+        get a() {
+            log.push("get");
+            return a;
+        },
+        set a(v) {
+            log.push("set");
+            a = v;
+        },
+    };
+
+    x["a"] &&= 42;
+    x["a"] &&= 42;
+
+    return log.join(" ") + " " + a;
+}, "get get undefined");
+
+
+
+shouldBe(function() {
+    let log = [];
+
+    class Parent {
+        get a() {
+            log.push("get-parent");
+            return this._a;
+        }
+        set a(v) {
+            log.push("set-parent");
+            this._a &&= v;
+        }
+    }
+    class Child extends Parent {
+        get a() {
+            log.push("get-child");
+            return super.a;
+        }
+        set a(v) {
+            log.push("set-child");
+            super.a &&= v;
+        }
+    }
+
+    let x = new Child;
+    x._a = true;
+    x.a &&= 42;
+    x.a &&= 42;
+
+    return log.join(" ") + " " + x._a;
+}, "get-child get-parent set-child get-parent set-parent get-child get-parent set-child get-parent set-parent 42");
+
+shouldBe(function() {
+    let log = [];
+
+    class Parent {
+        get a() {
+            log.push("get-parent");
+            return this._a;
+        }
+        set a(v) {
+            log.push("set-parent");
+            this._a &&= v;
+        }
+    }
+    class Child extends Parent {
+        get a() {
+            log.push("get-child");
+            return super.a;
+        }
+        set a(v) {
+            log.push("set-child");
+            super.a &&= v;
+        }
+    }
+
+    let x = new Child;
+    x._a = false;
+    x.a &&= 42;
+    x.a &&= 42;
+
+    return log.join(" ") + " " + x._a;
+}, "get-child get-parent get-child get-parent false");
+
+shouldBe(function() {
+    let log = [];
+
+    class Parent {
+        get a() {
+            log.push("get-parent");
+            return this._a;
+        }
+        set a(v) {
+            log.push("set-parent");
+            this._a &&= v;
+        }
+    }
+    class Child extends Parent {
+        get a() {
+            log.push("get-child");
+            return super.a;
+        }
+        set a(v) {
+            log.push("set-child");
+            super.a &&= v;
+        }
+    }
+
+    let x = new Child;
+    x._a = undefined;
+    x.a &&= 42;
+    x.a &&= 42;
+
+    return log.join(" ") + " " + x._a;
+}, "get-child get-parent get-child get-parent undefined");
+
+
+
+shouldBe(function() {
+    let log = [];
+
+    class Parent {
+        get a() {
+            log.push("get-parent");
+            return this._a;
+        }
+        set a(v) {
+            log.push("set-parent");
+            this._a &&= v;
+        }
+    }
+    class Child extends Parent {
+        get a() {
+            log.push("get-child");
+            return super["a"];
+        }
+        set a(v) {
+            log.push("set-child");
+            super["a"] &&= v;
+        }
+    }
+
+    let x = new Child;
+    x._a = true;
+    x["a"] &&= 42;
+    x["a"] &&= 42;
+
+    return log.join(" ") + " " + x._a;
+}, "get-child get-parent set-child get-parent set-parent get-child get-parent set-child get-parent set-parent 42");
+
+shouldBe(function() {
+    let log = [];
+
+    class Parent {
+        get a() {
+            log.push("get-parent");
+            return this._a;
+        }
+        set a(v) {
+            log.push("set-parent");
+            this._a &&= v;
+        }
+    }
+    class Child extends Parent {
+        get a() {
+            log.push("get-child");
+            return super["a"];
+        }
+        set a(v) {
+            log.push("set-child");
+            super["a"] &&= v;
+        }
+    }
+
+    let x = new Child;
+    x._a = false;
+    x["a"] &&= 42;
+    x["a"] &&= 42;
+
+    return log.join(" ") + " " + x._a;
+}, "get-child get-parent get-child get-parent false");
+
+shouldBe(function() {
+    let log = [];
+
+    class Parent {
+        get a() {
+            log.push("get-parent");
+            return this._a;
+        }
+        set a(v) {
+            log.push("set-parent");
+            this._a &&= v;
+        }
+    }
+    class Child extends Parent {
+        get a() {
+            log.push("get-child");
+            return super["a"];
+        }
+        set a(v) {
+            log.push("set-child");
+            super["a"] &&= v;
+        }
+    }
+
+    let x = new Child;
+    x._a = undefined;
+    x["a"] &&= 42;
+    x["a"] &&= 42;
+
+    return log.join(" ") + " " + x._a;
+}, "get-child get-parent get-child get-parent undefined");
+
+
+
+shouldBe(function() {
+    let x = {};
+    Object.defineProperty(x, "a", {
+        value: true,
+        writable: false,
+    });
+    return x.a &&= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = {};
+    Object.defineProperty(x, "a", {
+        value: false,
+        writable: false,
+    });
+    return x.a &&= 42;
+}, false);
+
+shouldBe(function() {
+    let x = {};
+    Object.defineProperty(x, "a", {
+        value: undefined,
+        writable: false,
+    });
+    return x.a &&= 42;
+}, undefined);
+
+
+
+shouldThrow(function() {
+    "use strict";
+    let x = {};
+    Object.defineProperty(x, "a", {
+        value: true,
+        writable: false,
+    });
+    return x.a &&= 42;
+}, TypeError);
+
+shouldBe(function() {
+    "use strict";
+    let x = {};
+    Object.defineProperty(x, "a", {
+        value: false,
+        writable: false,
+    });
+    return x.a &&= 42;
+}, false);
+
+shouldBe(function() {
+    "use strict";
+    let x = {};
+    Object.defineProperty(x, "a", {
+        value: undefined,
+        writable: false,
+    });
+    return x.a &&= 42;
+}, undefined);
+
+
+
+shouldBe(function() {
+    let x = {};
+    Object.defineProperty(x, "a", {
+        value: true,
+        writable: false,
+    });
+    return x["a"] &&= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = {};
+    Object.defineProperty(x, "a", {
+        value: false,
+        writable: false,
+    });
+    return x["a"] &&= 42;
+}, false);
+
+shouldBe(function() {
+    let x = {};
+    Object.defineProperty(x, "a", {
+        value: undefined,
+        writable: false,
+    });
+    return x["a"] &&= 42;
+}, undefined);
+
+
+
+shouldThrow(function() {
+    "use strict";
+    let x = {};
+    Object.defineProperty(x, "a", {
+        value: true,
+        writable: false,
+    });
+    return x["a"] &&= 42;
+}, TypeError);
+
+shouldBe(function() {
+    "use strict";
+    let x = {};
+    Object.defineProperty(x, "a", {
+        value: false,
+        writable: false,
+    });
+    return x["a"] &&= 42;
+}, false);
+
+shouldBe(function() {
+    "use strict";
+    let x = {};
+    Object.defineProperty(x, "a", {
+        value: undefined,
+        writable: false,
+    });
+    return x["a"] &&= 42;
+}, undefined);
+
+
+
+shouldBe(function() {
+    let x = true;
+    (function() {
+        x &&= 42;
+    })();
+    return x;
+}, 42);
+
+shouldBe(function() {
+    let x = false;
+    return (function() {
+        return x &&= 42;
+    })();
+}, false);
+
+shouldBe(function() {
+    let x = undefined;
+    return (function() {
+        return x &&= 42;
+    })();
+}, undefined);
+
+
+
+shouldThrow(function() {
+    const x = true;
+    (function() {
+        x &&= 42;
+    })();
+    return x;
+}, TypeError);
+
+shouldBe(function() {
+    const x = false;
+    return (function() {
+        return x &&= 42;
+    })();
+}, false);
+
+shouldBe(function() {
+    const x = undefined;
+    return (function() {
+        return x &&= 42;
+    })();
+}, undefined);
+
+
+
+shouldBe(function() {
+    let count = 0;
+
+    const x = true;
+    try {
+        x &&= ++count;
+    } catch { }
+
+    return count;
+}, 1);
+
+shouldBe(function() {
+    let count = 0;
+
+    const x = false;
+    x &&= ++count;
+
+    return count;
+}, 0);
+
+shouldBe(function() {
+    let count = 0;
+
+    const x = undefined;
+    x &&= ++count;
+
+    return count;
+}, 0);
+
+
+
+shouldBe(function() {
+    let count = 0;
+
+    const x = true;
+    function capture() { return x; }
+
+    try {
+        x &&= ++count;
+    } catch { }
+
+    return count;
+}, 1);
+
+shouldBe(function() {
+    let count = 0;
+
+    const x = false;
+    function capture() { return x; }
+
+    x &&= ++count;
+
+    return count;
+}, 0);
+
+shouldBe(function() {
+    let count = 0;
+
+    const x = undefined;
+    function capture() { return x; }
+
+    x &&= ++count;
+
+    return count;
+}, 0);
+
+
+
+shouldThrow(function() {
+    x &&= 42;
+    let x = true;
+    return x;
+}, ReferenceError);
+
+
+
+shouldBe(function() {
+    return undefined &&= 42;
+}, undefined);
+
+shouldThrowSyntaxError(`null &&= 42`);
+
+shouldThrowSyntaxError(`true &&= 42`);
+
+shouldThrowSyntaxError(`false &&= 42`);
+
+shouldThrowSyntaxError(`0 &&= 42`);
+
+shouldThrowSyntaxError(`1 &&= 42`);
+
+shouldThrowSyntaxError(`"" &&= 42`);
+
+shouldThrowSyntaxError(`"test" &&= 42`);
+
+shouldThrowSyntaxError(`{} &&= 42`);
+
+shouldThrowSyntaxError(`[] &&= 42`);

Added: trunk/JSTests/stress/logical-assignment-operator-nullish.js (0 => 260119)


--- trunk/JSTests/stress/logical-assignment-operator-nullish.js	                        (rev 0)
+++ trunk/JSTests/stress/logical-assignment-operator-nullish.js	2020-04-15 07:02:55 UTC (rev 260119)
@@ -0,0 +1,1699 @@
+//@ runLogicalAssignmentOperatorsEnabled
+
+function shouldBe(func, expected) {
+    let actual = func();
+    if (typeof expected === "function" ? !(actual instanceof expected) : actual !== expected)
+        throw new Error(`expected ${JSON.stringify(expected)} but got ${JSON.stringify(actual)}`);
+}
+
+function shouldThrow(func, errorType) {
+    let error;
+    try {
+        func();
+    } catch (e) {
+        error = e;
+    }
+
+    if (!(error instanceof errorType))
+        throw new Error(`Expected ${errorType.name} but saw ${error && error.name}!`);
+}
+
+function shouldThrowSyntaxError(script) {
+    let error;
+    try {
+        eval(script);
+    } catch (e) {
+        error = e;
+    }
+
+    if (!(error instanceof SyntaxError))
+        throw new Error(`Expected SyntaxError but saw ${error && error.name}!`);
+}
+
+shouldBe(function() {
+    let x;
+    return x ??= 42;
+}, 42);
+
+shouldBe(function() {
+    let x;
+    x ??= 42;
+    return x;
+}, 42);
+
+shouldBe(function() {
+    let x = undefined;
+    return x ??= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = undefined;
+    x ??= 42;
+    return x;
+}, 42);
+
+shouldBe(function() {
+    let x = null;
+    return x ??= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = null;
+    x ??= 42;
+    return x;
+}, 42);
+
+shouldBe(function() {
+    let x = true;
+    return x ??= 42;
+}, true);
+
+shouldBe(function() {
+    let x = true;
+    x ??= 42;
+    return x;
+}, true);
+
+shouldBe(function() {
+    let x = false;
+    return x ??= 42;
+}, false);
+
+shouldBe(function() {
+    let x = false;
+    x ??= 42;
+    return x;
+}, false);
+
+shouldBe(function() {
+    let x = 0;
+    return x ??= 42;
+}, 0);
+
+shouldBe(function() {
+    let x = 0;
+    x ??= 42;
+    return x;
+}, 0);
+
+shouldBe(function() {
+    let x = 1;
+    return x ??= 42;
+}, 1);
+
+shouldBe(function() {
+    let x = 1;
+    x ??= 42;
+    return x;
+}, 1);
+
+shouldBe(function() {
+    let x = "";
+    return x ??= 42;
+}, "");
+
+shouldBe(function() {
+    let x = "";
+    x ??= 42;
+    return x;
+}, "");
+
+shouldBe(function() {
+    let x = "test";
+    return x ??= 42;
+}, "test");
+
+shouldBe(function() {
+    let x = "test";
+    x ??= 42;
+    return x;
+}, "test");
+
+shouldBe(function() {
+    let x = {};
+    return x ??= 42;
+}, Object);
+
+shouldBe(function() {
+    let x = {};
+    x ??= 42;
+    return x;
+}, Object);
+
+shouldBe(function() {
+    let x = [];
+    return x ??= 42;
+}, Array);
+
+shouldBe(function() {
+    let x = [];
+    x ??= 42;
+    return x;
+}, Array);
+
+
+
+shouldThrow(function() {
+    const x = undefined;
+    return x ??= 42;
+}, TypeError);
+
+shouldThrow(function() {
+    const x = null;
+    return x ??= 42;
+}, TypeError);
+
+shouldBe(function() {
+    const x = true;
+    return x ??= 42;
+}, true);
+
+shouldBe(function() {
+    const x = true;
+    x ??= 42;
+    return x;
+}, true);
+
+shouldBe(function() {
+    const x = false;
+    return x ??= 42;
+}, false);
+
+shouldBe(function() {
+    const x = false;
+    x ??= 42;
+    return x;
+}, false);
+
+shouldBe(function() {
+    const x = 0;
+    return x ??= 42;
+}, 0);
+
+shouldBe(function() {
+    const x = 0;
+    x ??= 42;
+    return x;
+}, 0);
+
+shouldBe(function() {
+    const x = 1;
+    return x ??= 42;
+}, 1);
+
+shouldBe(function() {
+    const x = 1;
+    x ??= 42;
+    return x;
+}, 1);
+
+shouldBe(function() {
+    const x = "";
+    return x ??= 42;
+}, "");
+
+shouldBe(function() {
+    const x = "";
+    x ??= 42;
+    return x;
+}, "");
+
+shouldBe(function() {
+    const x = "test";
+    return x ??= 42;
+}, "test");
+
+shouldBe(function() {
+    const x = "test";
+    return x ??= 42;
+}, "test");
+
+shouldBe(function() {
+    const x = {};
+    return x ??= 42;
+}, Object);
+
+shouldBe(function() {
+    const x = {};
+    return x ??= 42;
+}, Object);
+
+shouldBe(function() {
+    const x = [];
+    return x ??= 42;
+}, Array);
+
+shouldBe(function() {
+    const x = [];
+    return x ??= 42;
+}, Array);
+
+
+
+shouldBe(function() {
+    let x = {};
+    return x.a ??= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = {};
+    x.a ??= 42;
+    return x.a;
+}, 42);
+
+shouldBe(function() {
+    let x = {a: undefined};
+    return x.a ??= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = {a: undefined};
+    x.a ??= 42;
+    return x.a;
+}, 42);
+
+shouldBe(function() {
+    let x = {a: null};
+    return x.a ??= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = {a: null};
+    x.a ??= 42;
+    return x.a;
+}, 42);
+
+shouldBe(function() {
+    let x = {a: true};
+    return x.a ??= 42;
+}, true);
+
+shouldBe(function() {
+    let x = {a: true};
+    x.a ??= 42;
+    return x.a;
+}, true);
+
+shouldBe(function() {
+    let x = {a: false};
+    return x.a ??= 42;
+}, false);
+
+shouldBe(function() {
+    let x = {a: false};
+    x.a ??= 42;
+    return x.a;
+}, false);
+
+shouldBe(function() {
+    let x = {a: 0};
+    return x.a ??= 42;
+}, 0);
+
+shouldBe(function() {
+    let x = {a: 0};
+    x.a ??= 42;
+    return x.a;
+}, 0);
+
+shouldBe(function() {
+    let x = {a: 1};
+    return x.a ??= 42;
+}, 1);
+
+shouldBe(function() {
+    let x = {a: 1};
+    x.a ??= 42;
+    return x.a;
+}, 1);
+
+shouldBe(function() {
+    let x = {a: ""};
+    return x.a ??= 42;
+}, "");
+
+shouldBe(function() {
+    let x = {a: ""};
+    x.a ??= 42;
+    return x.a;
+}, "");
+
+shouldBe(function() {
+    let x = {a: "test"};
+    return x.a ??= 42;
+}, "test");
+
+shouldBe(function() {
+    let x = {a: "test"};
+    x.a ??= 42;
+    return x.a;
+}, "test");
+
+shouldBe(function() {
+    let x = {a: {}};
+    return x.a ??= 42;
+}, Object);
+
+shouldBe(function() {
+    let x = {a: {}};
+    x.a ??= 42;
+    return x.a;
+}, Object);
+
+shouldBe(function() {
+    let x = {a: []};
+    return x.a ??= 42;
+}, Array);
+
+shouldBe(function() {
+    let x = {a: []};
+    x.a ??= 42;
+    return x.a;
+}, Array);
+
+
+
+shouldBe(function() {
+    const x = {};
+    return x.a ??= 42;
+}, 42);
+
+shouldBe(function() {
+    const x = {};
+    x.a ??= 42;
+    return x.a;
+}, 42);
+
+shouldBe(function() {
+    const x = {a: undefined};
+    return x.a ??= 42;
+}, 42);
+
+shouldBe(function() {
+    const x = {a: undefined};
+    x.a ??= 42;
+    return x.a;
+}, 42);
+
+shouldBe(function() {
+    const x = {a: null};
+    return x.a ??= 42;
+}, 42);
+
+shouldBe(function() {
+    const x = {a: null};
+    x.a ??= 42;
+    return x.a;
+}, 42);
+
+shouldBe(function() {
+    const x = {a: true};
+    return x.a ??= 42;
+}, true);
+
+shouldBe(function() {
+    const x = {a: true};
+    x.a ??= 42;
+    return x.a;
+}, true);
+
+shouldBe(function() {
+    const x = {a: false};
+    return x.a ??= 42;
+}, false);
+
+shouldBe(function() {
+    const x = {a: false};
+    x.a ??= 42;
+    return x.a;
+}, false);
+
+shouldBe(function() {
+    const x = {a: 0};
+    return x.a ??= 42;
+}, 0);
+
+shouldBe(function() {
+    const x = {a: 0};
+    x.a ??= 42;
+    return x.a;
+}, 0);
+
+shouldBe(function() {
+    const x = {a: 1};
+    return x.a ??= 42;
+}, 1);
+
+shouldBe(function() {
+    const x = {a: 1};
+    x.a ??= 42;
+    return x.a;
+}, 1);
+
+shouldBe(function() {
+    const x = {a: ""};
+    return x.a ??= 42;
+}, "");
+
+shouldBe(function() {
+    const x = {a: ""};
+    x.a ??= 42;
+    return x.a;
+}, "");
+
+shouldBe(function() {
+    const x = {a: "test"};
+    return x.a ??= 42;
+}, "test");
+
+shouldBe(function() {
+    const x = {a: "test"};
+    x.a ??= 42;
+    return x.a;
+}, "test");
+
+shouldBe(function() {
+    const x = {a: {}};
+    return x.a ??= 42;
+}, Object);
+
+shouldBe(function() {
+    const x = {a: {}};
+    x.a ??= 42;
+    return x.a;
+}, Object);
+
+shouldBe(function() {
+    const x = {a: []};
+    return x.a ??= 42;
+}, Array);
+
+shouldBe(function() {
+    const x = {a: []};
+    x.a ??= 42;
+    return x.a;
+}, Array);
+
+
+
+shouldBe(function() {
+    let x = {};
+    return x["a"] ??= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = {};
+    x["a"] ??= 42;
+    return x["a"];
+}, 42);
+
+shouldBe(function() {
+    let x = {a: undefined};
+    return x["a"] ??= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = {a: undefined};
+    x["a"] ??= 42;
+    return x["a"];
+}, 42);
+
+shouldBe(function() {
+    let x = {a: null};
+    return x["a"] ??= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = {a: null};
+    x["a"] ??= 42;
+    return x["a"];
+}, 42);
+
+shouldBe(function() {
+    let x = {a: true};
+    return x["a"] ??= 42;
+}, true);
+
+shouldBe(function() {
+    let x = {a: true};
+    x["a"] ??= 42;
+    return x["a"];
+}, true);
+
+shouldBe(function() {
+    let x = {a: false};
+    return x["a"] ??= 42;
+}, false);
+
+shouldBe(function() {
+    let x = {a: false};
+    x["a"] ??= 42;
+    return x["a"];
+}, false);
+
+shouldBe(function() {
+    let x = {a: 0};
+    return x["a"] ??= 42;
+}, 0);
+
+shouldBe(function() {
+    let x = {a: 0};
+    x["a"] ??= 42;
+    return x["a"];
+}, 0);
+
+shouldBe(function() {
+    let x = {a: 1};
+    return x["a"] ??= 42;
+}, 1);
+
+shouldBe(function() {
+    let x = {a: 1};
+    x["a"] ??= 42;
+    return x["a"];
+}, 1);
+
+shouldBe(function() {
+    let x = {a: ""};
+    return x["a"] ??= 42;
+}, "");
+
+shouldBe(function() {
+    let x = {a: ""};
+    x["a"] ??= 42;
+    return x["a"];
+}, "");
+
+shouldBe(function() {
+    let x = {a: "test"};
+    return x["a"] ??= 42;
+}, "test");
+
+shouldBe(function() {
+    let x = {a: "test"};
+    x["a"] ??= 42;
+    return x["a"];
+}, "test");
+
+shouldBe(function() {
+    let x = {a: {}};
+    return x["a"] ??= 42;
+}, Object);
+
+shouldBe(function() {
+    let x = {a: {}};
+    x["a"] ??= 42;
+    return x["a"];
+}, Object);
+
+shouldBe(function() {
+    let x = {a: []};
+    return x["a"] ??= 42;
+}, Array);
+
+shouldBe(function() {
+    let x = {a: []};
+    x["a"] ??= 42;
+    return x["a"];
+}, Array);
+
+
+
+shouldBe(function() {
+    const x = {};
+    return x["a"] ??= 42;
+}, 42);
+
+shouldBe(function() {
+    const x = {};
+    x["a"] ??= 42;
+    return x["a"];
+}, 42);
+
+shouldBe(function() {
+    const x = {a: undefined};
+    return x["a"] ??= 42;
+}, 42);
+
+shouldBe(function() {
+    const x = {a: undefined};
+    x["a"] ??= 42;
+    return x["a"];
+}, 42);
+
+shouldBe(function() {
+    const x = {a: null};
+    return x["a"] ??= 42;
+}, 42);
+
+shouldBe(function() {
+    const x = {a: null};
+    x["a"] ??= 42;
+    return x["a"];
+}, 42);
+
+shouldBe(function() {
+    const x = {a: true};
+    return x["a"] ??= 42;
+}, true);
+
+shouldBe(function() {
+    const x = {a: true};
+    x["a"] ??= 42;
+    return x["a"];
+}, true);
+
+shouldBe(function() {
+    const x = {a: false};
+    return x["a"] ??= 42;
+}, false);
+
+shouldBe(function() {
+    const x = {a: false};
+    x["a"] ??= 42;
+    return x["a"];
+}, false);
+
+shouldBe(function() {
+    const x = {a: 0};
+    return x["a"] ??= 42;
+}, 0);
+
+shouldBe(function() {
+    const x = {a: 0};
+    x["a"] ??= 42;
+    return x["a"];
+}, 0);
+
+shouldBe(function() {
+    const x = {a: 1};
+    return x["a"] ??= 42;
+}, 1);
+
+shouldBe(function() {
+    const x = {a: 1};
+    x["a"] ??= 42;
+    return x["a"];
+}, 1);
+
+shouldBe(function() {
+    const x = {a: ""};
+    return x["a"] ??= 42;
+}, "");
+
+shouldBe(function() {
+    const x = {a: ""};
+    x["a"] ??= 42;
+    return x["a"];
+}, "");
+
+shouldBe(function() {
+    const x = {a: "test"};
+    return x["a"] ??= 42;
+}, "test");
+
+shouldBe(function() {
+    const x = {a: "test"};
+    x["a"] ??= 42;
+    return x["a"];
+}, "test");
+
+shouldBe(function() {
+    const x = {a: {}};
+    return x["a"] ??= 42;
+}, Object);
+
+shouldBe(function() {
+    const x = {a: {}};
+    x["a"] ??= 42;
+    return x["a"];
+}, Object);
+
+shouldBe(function() {
+    const x = {a: []};
+    return x["a"] ??= 42;
+}, Array);
+
+shouldBe(function() {
+    const x = {a: []};
+    x["a"] ??= 42;
+    return x["a"];
+}, Array);
+
+
+
+shouldBe(function() {
+    let x = [];
+    return x[0] ??= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = [];
+    x[0] ??= 42;
+    return x[0];
+}, 42);
+
+shouldBe(function() {
+    let x = [undefined];
+    return x[0] ??= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = [undefined];
+    x[0] ??= 42;
+    return x[0];
+}, 42);
+
+shouldBe(function() {
+    let x = [null];
+    return x[0] ??= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = [null];
+    x[0] ??= 42;
+    return x[0];
+}, 42);
+
+shouldBe(function() {
+    let x = [true];
+    return x[0] ??= 42;
+}, true);
+
+shouldBe(function() {
+    let x = [true];
+    x[0] ??= 42;
+    return x[0];
+}, true);
+
+shouldBe(function() {
+    let x = [false];
+    return x[0] ??= 42;
+}, false);
+
+shouldBe(function() {
+    let x = [false];
+    x[0] ??= 42;
+    return x[0];
+}, false);
+
+shouldBe(function() {
+    let x = [0];
+    return x[0] ??= 42;
+}, 0);
+
+shouldBe(function() {
+    let x = [0];
+    x[0] ??= 42;
+    return x[0];
+}, 0);
+
+shouldBe(function() {
+    let x = [1];
+    return x[0] ??= 42;
+}, 1);
+
+shouldBe(function() {
+    let x = [1];
+    x[0] ??= 42;
+    return x[0];
+}, 1);
+
+shouldBe(function() {
+    let x = [""];
+    return x[0] ??= 42;
+}, "");
+
+shouldBe(function() {
+    let x = [""];
+    x[0] ??= 42;
+    return x[0];
+}, "");
+
+shouldBe(function() {
+    let x = ["test"];
+    return x[0] ??= 42;
+}, "test");
+
+shouldBe(function() {
+    let x = ["test"];
+    x[0] ??= 42;
+    return x[0];
+}, "test");
+
+shouldBe(function() {
+    let x = [{}];
+    return x[0] ??= 42;
+}, Object);
+
+shouldBe(function() {
+    let x = [{}];
+    x[0] ??= 42;
+    return x[0];
+}, Object);
+
+shouldBe(function() {
+    let x = [[]];
+    return x[0] ??= 42;
+}, Array);
+
+shouldBe(function() {
+    let x = [[]];
+    x[0] ??= 42;
+    return x[0];
+}, Array);
+
+
+
+shouldBe(function() {
+    const x = [];
+    return x[0] ??= 42;
+}, 42);
+
+shouldBe(function() {
+    const x = [];
+    x[0] ??= 42;
+    return x[0];
+}, 42);
+
+shouldBe(function() {
+    const x = [undefined];
+    return x[0] ??= 42;
+}, 42);
+
+shouldBe(function() {
+    const x = [undefined];
+    x[0] ??= 42;
+    return x[0];
+}, 42);
+
+shouldBe(function() {
+    const x = [null];
+    return x[0] ??= 42;
+}, 42);
+
+shouldBe(function() {
+    const x = [null];
+    x[0] ??= 42;
+    return x[0];
+}, 42);
+
+shouldBe(function() {
+    const x = [true];
+    return x[0] ??= 42;
+}, true);
+
+shouldBe(function() {
+    const x = [true];
+    x[0] ??= 42;
+    return x[0];
+}, true);
+
+shouldBe(function() {
+    const x = [false];
+    return x[0] ??= 42;
+}, false);
+
+shouldBe(function() {
+    const x = [false];
+    x[0] ??= 42;
+    return x[0];
+}, false);
+
+shouldBe(function() {
+    const x = [0];
+    return x[0] ??= 42;
+}, 0);
+
+shouldBe(function() {
+    const x = [0];
+    x[0] ??= 42;
+    return x[0];
+}, 0);
+
+shouldBe(function() {
+    const x = [1];
+    return x[0] ??= 42;
+}, 1);
+
+shouldBe(function() {
+    const x = [1];
+    x[0] ??= 42;
+    return x[0];
+}, 1);
+
+shouldBe(function() {
+    const x = [""];
+    return x[0] ??= 42;
+}, "");
+
+shouldBe(function() {
+    const x = [""];
+    x[0] ??= 42;
+    return x[0];
+}, "");
+
+shouldBe(function() {
+    const x = ["test"];
+    return x[0] ??= 42;
+}, "test");
+
+shouldBe(function() {
+    const x = ["test"];
+    x[0] ??= 42;
+    return x[0];
+}, "test");
+
+shouldBe(function() {
+    const x = [{}];
+    return x[0] ??= 42;
+}, Object);
+
+shouldBe(function() {
+    const x = [{}];
+    x[0] ??= 42;
+    return x[0];
+}, Object);
+
+shouldBe(function() {
+    const x = [[]];
+    return x[0] ??= 42;
+}, Array);
+
+shouldBe(function() {
+    const x = [[]];
+    x[0] ??= 42;
+    return x[0];
+}, Array);
+
+
+
+shouldBe(function() {
+    let x = false;
+    let y = 1;
+    let z = 2;
+    return x ??= y + z;
+}, false);
+
+shouldBe(function() {
+    let x = false;
+    let y = 1;
+    let z = 2;
+    return x ??= y = z;
+}, false);
+
+shouldBe(function() {
+    let x = false;
+    let y = 1;
+    let z = 2;
+    return x ??= y && z;
+}, false);
+
+shouldBe(function() {
+    let x = false;
+    let y = 1;
+    let z = 2;
+    return x ??= y ?? z;
+}, false);
+
+shouldBe(function() {
+    let x = false;
+    let y = 1;
+    let z = 2;
+    return x ??= y || z;
+}, false);
+
+shouldBe(function() {
+    let x = false;
+    let y = 1;
+    let z = 2;
+    return x ??= y &&= z;
+}, false);
+
+shouldBe(function() {
+    let x = false;
+    let y = 1;
+    let z = 2;
+    return x ??= y ??= z;
+}, false);
+
+shouldBe(function() {
+    let x = false;
+    let y = 1;
+    let z = 2;
+    return x ??= y ||= z;
+}, false);
+
+
+
+shouldBe(function() {
+    let x = null;
+    let y = 1;
+    let z = 2;
+    return x ??= y + z;
+}, 3);
+
+shouldBe(function() {
+    let x = null;
+    let y = 1;
+    let z = 2;
+    return x ??= y = z;
+}, 2);
+
+shouldBe(function() {
+    let x = null;
+    let y = 1;
+    let z = 2;
+    return x ??= y && z;
+}, 2);
+
+shouldBe(function() {
+    let x = null;
+    let y = 1;
+    let z = 2;
+    return x ??= y ?? z;
+}, 1);
+
+shouldBe(function() {
+    let x = null;
+    let y = 1;
+    let z = 2;
+    return x ??= y || z;
+}, 1);
+
+shouldBe(function() {
+    let x = null;
+    let y = 1;
+    let z = 2;
+    return x ??= y &&= z;
+}, 2);
+
+shouldBe(function() {
+    let x = null;
+    let y = 1;
+    let z = 2;
+    return x ??= y ??= z;
+}, 1);
+
+shouldBe(function() {
+    let x = null;
+    let y = 1;
+    let z = 2;
+    return x ??= y ||= z;
+}, 1);
+
+
+
+shouldBe(function() {
+    let log = [];
+
+    let a = true;
+    let x = {
+        get a() {
+            log.push("get");
+            return a;
+        },
+        set a(v) {
+            log.push("set");
+            a = v;
+        },
+    };
+
+    x.a ??= 42;
+    x.a ??= 42;
+
+    return log.join(" ") + " " + a;
+}, "get get true");
+
+shouldBe(function() {
+    let log = [];
+
+    let a = false;
+    let x = {
+        get a() {
+            log.push("get");
+            return a;
+        },
+        set a(v) {
+            log.push("set");
+            a = v;
+        },
+    };
+
+    x.a ??= 42;
+    x.a ??= 42;
+
+    return log.join(" ") + " " + a;
+}, "get get false");
+
+shouldBe(function() {
+    let log = [];
+
+    let a = undefined;
+    let x = {
+        get a() {
+            log.push("get");
+            return a;
+        },
+        set a(v) {
+            log.push("set");
+            a = v;
+        },
+    };
+
+    x.a ??= 42;
+    x.a ??= 42;
+
+    return log.join(" ") + " " + a;
+}, "get set get 42");
+
+
+
+shouldBe(function() {
+    let log = [];
+
+    let a = true;
+    let x = {
+        get a() {
+            log.push("get");
+            return a;
+        },
+        set a(v) {
+            log.push("set");
+            a = v;
+        },
+    };
+
+    x["a"] ??= 42;
+    x["a"] ??= 42;
+
+    return log.join(" ") + " " + a;
+}, "get get true");
+
+shouldBe(function() {
+    let log = [];
+
+    let a = false;
+    let x = {
+        get a() {
+            log.push("get");
+            return a;
+        },
+        set a(v) {
+            log.push("set");
+            a = v;
+        },
+    };
+
+    x["a"] ??= 42;
+    x["a"] ??= 42;
+
+    return log.join(" ") + " " + a;
+}, "get get false");
+
+shouldBe(function() {
+    let log = [];
+
+    let a = undefined;
+    let x = {
+        get a() {
+            log.push("get");
+            return a;
+        },
+        set a(v) {
+            log.push("set");
+            a = v;
+        },
+    };
+
+    x["a"] ??= 42;
+    x["a"] ??= 42;
+
+    return log.join(" ") + " " + a;
+}, "get set get 42");
+
+
+
+shouldBe(function() {
+    let log = [];
+
+    class Parent {
+        get a() {
+            log.push("get-parent");
+            return this._a;
+        }
+        set a(v) {
+            log.push("set-parent");
+            this._a ??= v;
+        }
+    }
+    class Child extends Parent {
+        get a() {
+            log.push("get-child");
+            return super.a;
+        }
+        set a(v) {
+            log.push("set-child");
+            super.a ??= v;
+        }
+    }
+
+    let x = new Child;
+    x._a = true;
+    x.a ??= 42;
+    x.a ??= 42;
+
+    return log.join(" ") + " " + x._a;
+}, "get-child get-parent get-child get-parent true");
+
+shouldBe(function() {
+    let log = [];
+
+    class Parent {
+        get a() {
+            log.push("get-parent");
+            return this._a;
+        }
+        set a(v) {
+            log.push("set-parent");
+            this._a ??= v;
+        }
+    }
+    class Child extends Parent {
+        get a() {
+            log.push("get-child");
+            return super.a;
+        }
+        set a(v) {
+            log.push("set-child");
+            super.a ??= v;
+        }
+    }
+
+    let x = new Child;
+    x._a = false;
+    x.a ??= 42;
+    x.a ??= 42;
+
+    return log.join(" ") + " " + x._a;
+}, "get-child get-parent get-child get-parent false");
+
+shouldBe(function() {
+    let log = [];
+
+    class Parent {
+        get a() {
+            log.push("get-parent");
+            return this._a;
+        }
+        set a(v) {
+            log.push("set-parent");
+            this._a ??= v;
+        }
+    }
+    class Child extends Parent {
+        get a() {
+            log.push("get-child");
+            return super.a;
+        }
+        set a(v) {
+            log.push("set-child");
+            super.a ??= v;
+        }
+    }
+
+    let x = new Child;
+    x._a = undefined;
+    x.a ??= 42;
+    x.a ??= 42;
+
+    return log.join(" ") + " " + x._a;
+}, "get-child get-parent set-child get-parent set-parent get-child get-parent 42");
+
+
+
+shouldBe(function() {
+    let log = [];
+
+    class Parent {
+        get a() {
+            log.push("get-parent");
+            return this._a;
+        }
+        set a(v) {
+            log.push("set-parent");
+            this._a ??= v;
+        }
+    }
+    class Child extends Parent {
+        get a() {
+            log.push("get-child");
+            return super["a"];
+        }
+        set a(v) {
+            log.push("set-child");
+            super["a"] ??= v;
+        }
+    }
+
+    let x = new Child;
+    x._a = true;
+    x["a"] ??= 42;
+    x["a"] ??= 42;
+
+    return log.join(" ") + " " + x._a;
+}, "get-child get-parent get-child get-parent true");
+
+shouldBe(function() {
+    let log = [];
+
+    class Parent {
+        get a() {
+            log.push("get-parent");
+            return this._a;
+        }
+        set a(v) {
+            log.push("set-parent");
+            this._a ??= v;
+        }
+    }
+    class Child extends Parent {
+        get a() {
+            log.push("get-child");
+            return super["a"];
+        }
+        set a(v) {
+            log.push("set-child");
+            super["a"] ??= v;
+        }
+    }
+
+    let x = new Child;
+    x._a = false;
+    x["a"] ??= 42;
+    x["a"] ??= 42;
+
+    return log.join(" ") + " " + x._a;
+}, "get-child get-parent get-child get-parent false");
+
+shouldBe(function() {
+    let log = [];
+
+    class Parent {
+        get a() {
+            log.push("get-parent");
+            return this._a;
+        }
+        set a(v) {
+            log.push("set-parent");
+            this._a ??= v;
+        }
+    }
+    class Child extends Parent {
+        get a() {
+            log.push("get-child");
+            return super["a"];
+        }
+        set a(v) {
+            log.push("set-child");
+            super["a"] ??= v;
+        }
+    }
+
+    let x = new Child;
+    x._a = undefined;
+    x["a"] ??= 42;
+    x["a"] ??= 42;
+
+    return log.join(" ") + " " + x._a;
+}, "get-child get-parent set-child get-parent set-parent get-child get-parent 42");
+
+
+
+shouldBe(function() {
+    let x = {};
+    Object.defineProperty(x, "a", {
+        value: true,
+        writable: false,
+    });
+    return x.a ??= 42;
+}, true);
+
+shouldBe(function() {
+    let x = {};
+    Object.defineProperty(x, "a", {
+        value: false,
+        writable: false,
+    });
+    return x.a ??= 42;
+}, false);
+
+shouldBe(function() {
+    let x = {};
+    Object.defineProperty(x, "a", {
+        value: undefined,
+        writable: false,
+    });
+    return x.a ??= 42;
+}, 42);
+
+
+
+shouldBe(function() {
+    "use strict";
+    let x = {};
+    Object.defineProperty(x, "a", {
+        value: true,
+        writable: false,
+    });
+    return x.a ??= 42;
+}, true);
+
+shouldBe(function() {
+    "use strict";
+    let x = {};
+    Object.defineProperty(x, "a", {
+        value: false,
+        writable: false,
+    });
+    return x.a ??= 42;
+}, false);
+
+shouldThrow(function() {
+    "use strict";
+    let x = {};
+    Object.defineProperty(x, "a", {
+        value: undefined,
+        writable: false,
+    });
+    return x.a ??= 42;
+}, TypeError);
+
+
+
+shouldBe(function() {
+    let x = {};
+    Object.defineProperty(x, "a", {
+        value: true,
+        writable: false,
+    });
+    return x["a"] ??= 42;
+}, true);
+
+shouldBe(function() {
+    let x = {};
+    Object.defineProperty(x, "a", {
+        value: false,
+        writable: false,
+    });
+    return x["a"] ??= 42;
+}, false);
+
+shouldBe(function() {
+    let x = {};
+    Object.defineProperty(x, "a", {
+        value: undefined,
+        writable: false,
+    });
+    return x["a"] ??= 42;
+}, 42);
+
+
+
+shouldBe(function() {
+    "use strict";
+    let x = {};
+    Object.defineProperty(x, "a", {
+        value: true,
+        writable: false,
+    });
+    return x["a"] ??= 42;
+}, true);
+
+shouldBe(function() {
+    "use strict";
+    let x = {};
+    Object.defineProperty(x, "a", {
+        value: false,
+        writable: false,
+    });
+    return x["a"] ??= 42;
+}, false);
+
+shouldThrow(function() {
+    "use strict";
+    let x = {};
+    Object.defineProperty(x, "a", {
+        value: undefined,
+        writable: false,
+    });
+    return x["a"] ??= 42;
+}, TypeError);
+
+
+
+shouldBe(function() {
+    let x = true;
+    (function() {
+        x ??= 42;
+    })();
+    return x;
+}, true);
+
+shouldBe(function() {
+    let x = false;
+    return (function() {
+        return x ??= 42;
+    })();
+}, false);
+
+shouldBe(function() {
+    let x = undefined;
+    return (function() {
+        return x ??= 42;
+    })();
+}, 42);
+
+
+
+shouldBe(function() {
+    const x = true;
+    (function() {
+        x ??= 42;
+    })();
+    return x;
+}, true);
+
+shouldBe(function() {
+    const x = false;
+    return (function() {
+        return x ??= 42;
+    })();
+}, false);
+
+shouldThrow(function() {
+    const x = undefined;
+    return (function() {
+        return x ??= 42;
+    })();
+}, TypeError);
+
+
+
+shouldBe(function() {
+    let count = 0;
+
+    const x = true;
+    x ??= ++count;
+
+    return count;
+}, 0);
+
+shouldBe(function() {
+    let count = 0;
+
+    const x = false;
+    x ??= ++count;
+
+    return count;
+}, 0);
+
+shouldBe(function() {
+    let count = 0;
+
+    const x = undefined;
+    try {
+        x ??= ++count;
+    } catch { }
+
+    return count;
+}, 1);
+
+
+
+shouldBe(function() {
+    let count = 0;
+
+    const x = true;
+    function capture() { return x; }
+
+    x ??= ++count;
+
+    return count;
+}, 0);
+
+shouldBe(function() {
+    let count = 0;
+
+    const x = false;
+    function capture() { return x; }
+
+    x ??= ++count;
+
+    return count;
+}, 0);
+
+shouldBe(function() {
+    let count = 0;
+
+    const x = undefined;
+    function capture() { return x; }
+
+    try {
+        x ??= ++count;
+    } catch { }
+
+    return count;
+}, 1);
+
+
+
+shouldThrow(function() {
+    x ??= 42;
+    let x = true;
+    return x;
+}, ReferenceError);
+
+
+
+shouldBe(function() {
+    return undefined ??= 42;
+}, 42);
+
+shouldThrowSyntaxError(`null ??= 42`);
+
+shouldThrowSyntaxError(`true ??= 42`);
+
+shouldThrowSyntaxError(`false ??= 42`);
+
+shouldThrowSyntaxError(`0 ??= 42`);
+
+shouldThrowSyntaxError(`1 ??= 42`);
+
+shouldThrowSyntaxError(`"" ??= 42`);
+
+shouldThrowSyntaxError(`"test" ??= 42`);
+
+shouldThrowSyntaxError(`{} ??= 42`);
+
+shouldThrowSyntaxError(`[] ??= 42`);

Added: trunk/JSTests/stress/logical-assignment-operator-or.js (0 => 260119)


--- trunk/JSTests/stress/logical-assignment-operator-or.js	                        (rev 0)
+++ trunk/JSTests/stress/logical-assignment-operator-or.js	2020-04-15 07:02:55 UTC (rev 260119)
@@ -0,0 +1,1685 @@
+//@ runLogicalAssignmentOperatorsEnabled
+
+function shouldBe(func, expected) {
+    let actual = func();
+    if (typeof expected === "function" ? !(actual instanceof expected) : actual !== expected)
+        throw new Error(`expected ${JSON.stringify(expected)} but got ${JSON.stringify(actual)}`);
+}
+
+function shouldThrow(func, errorType) {
+    let error;
+    try {
+        func();
+    } catch (e) {
+        error = e;
+    }
+
+    if (!(error instanceof errorType))
+        throw new Error(`Expected ${errorType.name} but saw ${error && error.name}!`);
+}
+
+function shouldThrowSyntaxError(script) {
+    let error;
+    try {
+        eval(script);
+    } catch (e) {
+        error = e;
+    }
+
+    if (!(error instanceof SyntaxError))
+        throw new Error(`Expected SyntaxError but saw ${error && error.name}!`);
+}
+
+shouldBe(function() {
+    let x;
+    return x ||= 42;
+}, 42);
+
+shouldBe(function() {
+    let x;
+    x ||= 42;
+    return x;
+}, 42);
+
+shouldBe(function() {
+    let x = undefined;
+    return x ||= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = undefined;
+    x ||= 42;
+    return x;
+}, 42);
+
+shouldBe(function() {
+    let x = null;
+    return x ||= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = null;
+    x ||= 42;
+    return x;
+}, 42);
+
+shouldBe(function() {
+    let x = true;
+    return x ||= 42;
+}, true);
+
+shouldBe(function() {
+    let x = true;
+    x ||= 42;
+    return x;
+}, true);
+
+shouldBe(function() {
+    let x = false;
+    return x ||= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = false;
+    x ||= 42;
+    return x;
+}, 42);
+
+shouldBe(function() {
+    let x = 0;
+    return x ||= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = 0;
+    x ||= 42;
+    return x;
+}, 42);
+
+shouldBe(function() {
+    let x = 1;
+    return x ||= 42;
+}, 1);
+
+shouldBe(function() {
+    let x = 1;
+    x ||= 42;
+    return x;
+}, 1);
+
+shouldBe(function() {
+    let x = "";
+    return x ||= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = "";
+    x ||= 42;
+    return x;
+}, 42);
+
+shouldBe(function() {
+    let x = "test";
+    return x ||= 42;
+}, "test");
+
+shouldBe(function() {
+    let x = "test";
+    x ||= 42;
+    return x;
+}, "test");
+
+shouldBe(function() {
+    let x = {};
+    return x ||= 42;
+}, Object);
+
+shouldBe(function() {
+    let x = {};
+    x ||= 42;
+    return x;
+}, Object);
+
+shouldBe(function() {
+    let x = [];
+    return x ||= 42;
+}, Array);
+
+shouldBe(function() {
+    let x = [];
+    x ||= 42;
+    return x;
+}, Array);
+
+
+
+shouldThrow(function() {
+    const x = undefined;
+    return x ||= 42;
+}, TypeError);
+
+shouldThrow(function() {
+    const x = null;
+    return x ||= 42;
+}, TypeError);
+
+shouldBe(function() {
+    const x = true;
+    return x ||= 42;
+}, true);
+
+shouldBe(function() {
+    const x = true;
+    x ||= 42;
+    return x;
+}, true);
+
+shouldThrow(function() {
+    const x = false;
+    return x ||= 42;
+}, TypeError);
+
+shouldThrow(function() {
+    const x = 0;
+    return x ||= 42;
+}, TypeError);
+
+shouldBe(function() {
+    const x = 1;
+    return x ||= 42;
+}, 1);
+
+shouldBe(function() {
+    const x = 1;
+    x ||= 42;
+    return x;
+}, 1);
+
+shouldThrow(function() {
+    const x = "";
+    return x ||= 42;
+}, TypeError);
+
+shouldBe(function() {
+    const x = "test";
+    return x ||= 42;
+}, "test");
+
+shouldBe(function() {
+    const x = "test";
+    return x ||= 42;
+}, "test");
+
+shouldBe(function() {
+    const x = {};
+    return x ||= 42;
+}, Object);
+
+shouldBe(function() {
+    const x = {};
+    return x ||= 42;
+}, Object);
+
+shouldBe(function() {
+    const x = [];
+    return x ||= 42;
+}, Array);
+
+shouldBe(function() {
+    const x = [];
+    return x ||= 42;
+}, Array);
+
+
+
+shouldBe(function() {
+    let x = {};
+    return x.a ||= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = {};
+    x.a ||= 42;
+    return x.a;
+}, 42);
+
+shouldBe(function() {
+    let x = {a: undefined};
+    return x.a ||= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = {a: undefined};
+    x.a ||= 42;
+    return x.a;
+}, 42);
+
+shouldBe(function() {
+    let x = {a: null};
+    return x.a ||= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = {a: null};
+    x.a ||= 42;
+    return x.a;
+}, 42);
+
+shouldBe(function() {
+    let x = {a: true};
+    return x.a ||= 42;
+}, true);
+
+shouldBe(function() {
+    let x = {a: true};
+    x.a ||= 42;
+    return x.a;
+}, true);
+
+shouldBe(function() {
+    let x = {a: false};
+    return x.a ||= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = {a: false};
+    x.a ||= 42;
+    return x.a;
+}, 42);
+
+shouldBe(function() {
+    let x = {a: 0};
+    return x.a ||= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = {a: 0};
+    x.a ||= 42;
+    return x.a;
+}, 42);
+
+shouldBe(function() {
+    let x = {a: 1};
+    return x.a ||= 42;
+}, 1);
+
+shouldBe(function() {
+    let x = {a: 1};
+    x.a ||= 42;
+    return x.a;
+}, 1);
+
+shouldBe(function() {
+    let x = {a: ""};
+    return x.a ||= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = {a: ""};
+    x.a ||= 42;
+    return x.a;
+}, 42);
+
+shouldBe(function() {
+    let x = {a: "test"};
+    return x.a ||= 42;
+}, "test");
+
+shouldBe(function() {
+    let x = {a: "test"};
+    x.a ||= 42;
+    return x.a;
+}, "test");
+
+shouldBe(function() {
+    let x = {a: {}};
+    return x.a ||= 42;
+}, Object);
+
+shouldBe(function() {
+    let x = {a: {}};
+    x.a ||= 42;
+    return x.a;
+}, Object);
+
+shouldBe(function() {
+    let x = {a: []};
+    return x.a ||= 42;
+}, Array);
+
+shouldBe(function() {
+    let x = {a: []};
+    x.a ||= 42;
+    return x.a;
+}, Array);
+
+
+
+shouldBe(function() {
+    const x = {};
+    return x.a ||= 42;
+}, 42);
+
+shouldBe(function() {
+    const x = {};
+    x.a ||= 42;
+    return x.a;
+}, 42);
+
+shouldBe(function() {
+    const x = {a: undefined};
+    return x.a ||= 42;
+}, 42);
+
+shouldBe(function() {
+    const x = {a: undefined};
+    x.a ||= 42;
+    return x.a;
+}, 42);
+
+shouldBe(function() {
+    const x = {a: null};
+    return x.a ||= 42;
+}, 42);
+
+shouldBe(function() {
+    const x = {a: null};
+    x.a ||= 42;
+    return x.a;
+}, 42);
+
+shouldBe(function() {
+    const x = {a: true};
+    return x.a ||= 42;
+}, true);
+
+shouldBe(function() {
+    const x = {a: true};
+    x.a ||= 42;
+    return x.a;
+}, true);
+
+shouldBe(function() {
+    const x = {a: false};
+    return x.a ||= 42;
+}, 42);
+
+shouldBe(function() {
+    const x = {a: false};
+    x.a ||= 42;
+    return x.a;
+}, 42);
+
+shouldBe(function() {
+    const x = {a: 0};
+    return x.a ||= 42;
+}, 42);
+
+shouldBe(function() {
+    const x = {a: 0};
+    x.a ||= 42;
+    return x.a;
+}, 42);
+
+shouldBe(function() {
+    const x = {a: 1};
+    return x.a ||= 42;
+}, 1);
+
+shouldBe(function() {
+    const x = {a: 1};
+    x.a ||= 42;
+    return x.a;
+}, 1);
+
+shouldBe(function() {
+    const x = {a: ""};
+    return x.a ||= 42;
+}, 42);
+
+shouldBe(function() {
+    const x = {a: ""};
+    x.a ||= 42;
+    return x.a;
+}, 42);
+
+shouldBe(function() {
+    const x = {a: "test"};
+    return x.a ||= 42;
+}, "test");
+
+shouldBe(function() {
+    const x = {a: "test"};
+    x.a ||= 42;
+    return x.a;
+}, "test");
+
+shouldBe(function() {
+    const x = {a: {}};
+    return x.a ||= 42;
+}, Object);
+
+shouldBe(function() {
+    const x = {a: {}};
+    x.a ||= 42;
+    return x.a;
+}, Object);
+
+shouldBe(function() {
+    const x = {a: []};
+    return x.a ||= 42;
+}, Array);
+
+shouldBe(function() {
+    const x = {a: []};
+    x.a ||= 42;
+    return x.a;
+}, Array);
+
+
+
+shouldBe(function() {
+    let x = {};
+    return x["a"] ||= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = {};
+    x["a"] ||= 42;
+    return x["a"];
+}, 42);
+
+shouldBe(function() {
+    let x = {a: undefined};
+    return x["a"] ||= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = {a: undefined};
+    x["a"] ||= 42;
+    return x["a"];
+}, 42);
+
+shouldBe(function() {
+    let x = {a: null};
+    return x["a"] ||= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = {a: null};
+    x["a"] ||= 42;
+    return x["a"];
+}, 42);
+
+shouldBe(function() {
+    let x = {a: true};
+    return x["a"] ||= 42;
+}, true);
+
+shouldBe(function() {
+    let x = {a: true};
+    x["a"] ||= 42;
+    return x["a"];
+}, true);
+
+shouldBe(function() {
+    let x = {a: false};
+    return x["a"] ||= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = {a: false};
+    x["a"] ||= 42;
+    return x["a"];
+}, 42);
+
+shouldBe(function() {
+    let x = {a: 0};
+    return x["a"] ||= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = {a: 0};
+    x["a"] ||= 42;
+    return x["a"];
+}, 42);
+
+shouldBe(function() {
+    let x = {a: 1};
+    return x["a"] ||= 42;
+}, 1);
+
+shouldBe(function() {
+    let x = {a: 1};
+    x["a"] ||= 42;
+    return x["a"];
+}, 1);
+
+shouldBe(function() {
+    let x = {a: ""};
+    return x["a"] ||= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = {a: ""};
+    x["a"] ||= 42;
+    return x["a"];
+}, 42);
+
+shouldBe(function() {
+    let x = {a: "test"};
+    return x["a"] ||= 42;
+}, "test");
+
+shouldBe(function() {
+    let x = {a: "test"};
+    x["a"] ||= 42;
+    return x["a"];
+}, "test");
+
+shouldBe(function() {
+    let x = {a: {}};
+    return x["a"] ||= 42;
+}, Object);
+
+shouldBe(function() {
+    let x = {a: {}};
+    x["a"] ||= 42;
+    return x["a"];
+}, Object);
+
+shouldBe(function() {
+    let x = {a: []};
+    return x["a"] ||= 42;
+}, Array);
+
+shouldBe(function() {
+    let x = {a: []};
+    x["a"] ||= 42;
+    return x["a"];
+}, Array);
+
+
+
+shouldBe(function() {
+    const x = {};
+    return x["a"] ||= 42;
+}, 42);
+
+shouldBe(function() {
+    const x = {};
+    x["a"] ||= 42;
+    return x["a"];
+}, 42);
+
+shouldBe(function() {
+    const x = {a: undefined};
+    return x["a"] ||= 42;
+}, 42);
+
+shouldBe(function() {
+    const x = {a: undefined};
+    x["a"] ||= 42;
+    return x["a"];
+}, 42);
+
+shouldBe(function() {
+    const x = {a: null};
+    return x["a"] ||= 42;
+}, 42);
+
+shouldBe(function() {
+    const x = {a: null};
+    x["a"] ||= 42;
+    return x["a"];
+}, 42);
+
+shouldBe(function() {
+    const x = {a: true};
+    return x["a"] ||= 42;
+}, true);
+
+shouldBe(function() {
+    const x = {a: true};
+    x["a"] ||= 42;
+    return x["a"];
+}, true);
+
+shouldBe(function() {
+    const x = {a: false};
+    return x["a"] ||= 42;
+}, 42);
+
+shouldBe(function() {
+    const x = {a: false};
+    x["a"] ||= 42;
+    return x["a"];
+}, 42);
+
+shouldBe(function() {
+    const x = {a: 0};
+    return x["a"] ||= 42;
+}, 42);
+
+shouldBe(function() {
+    const x = {a: 0};
+    x["a"] ||= 42;
+    return x["a"];
+}, 42);
+
+shouldBe(function() {
+    const x = {a: 1};
+    return x["a"] ||= 42;
+}, 1);
+
+shouldBe(function() {
+    const x = {a: 1};
+    x["a"] ||= 42;
+    return x["a"];
+}, 1);
+
+shouldBe(function() {
+    const x = {a: ""};
+    return x["a"] ||= 42;
+}, 42);
+
+shouldBe(function() {
+    const x = {a: ""};
+    x["a"] ||= 42;
+    return x["a"];
+}, 42);
+
+shouldBe(function() {
+    const x = {a: "test"};
+    return x["a"] ||= 42;
+}, "test");
+
+shouldBe(function() {
+    const x = {a: "test"};
+    x["a"] ||= 42;
+    return x["a"];
+}, "test");
+
+shouldBe(function() {
+    const x = {a: {}};
+    return x["a"] ||= 42;
+}, Object);
+
+shouldBe(function() {
+    const x = {a: {}};
+    x["a"] ||= 42;
+    return x["a"];
+}, Object);
+
+shouldBe(function() {
+    const x = {a: []};
+    return x["a"] ||= 42;
+}, Array);
+
+shouldBe(function() {
+    const x = {a: []};
+    x["a"] ||= 42;
+    return x["a"];
+}, Array);
+
+
+
+shouldBe(function() {
+    let x = [];
+    return x[0] ||= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = [];
+    x[0] ||= 42;
+    return x[0];
+}, 42);
+
+shouldBe(function() {
+    let x = [undefined];
+    return x[0] ||= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = [undefined];
+    x[0] ||= 42;
+    return x[0];
+}, 42);
+
+shouldBe(function() {
+    let x = [null];
+    return x[0] ||= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = [null];
+    x[0] ||= 42;
+    return x[0];
+}, 42);
+
+shouldBe(function() {
+    let x = [true];
+    return x[0] ||= 42;
+}, true);
+
+shouldBe(function() {
+    let x = [true];
+    x[0] ||= 42;
+    return x[0];
+}, true);
+
+shouldBe(function() {
+    let x = [false];
+    return x[0] ||= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = [false];
+    x[0] ||= 42;
+    return x[0];
+}, 42);
+
+shouldBe(function() {
+    let x = [0];
+    return x[0] ||= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = [0];
+    x[0] ||= 42;
+    return x[0];
+}, 42);
+
+shouldBe(function() {
+    let x = [1];
+    return x[0] ||= 42;
+}, 1);
+
+shouldBe(function() {
+    let x = [1];
+    x[0] ||= 42;
+    return x[0];
+}, 1);
+
+shouldBe(function() {
+    let x = [""];
+    return x[0] ||= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = [""];
+    x[0] ||= 42;
+    return x[0];
+}, 42);
+
+shouldBe(function() {
+    let x = ["test"];
+    return x[0] ||= 42;
+}, "test");
+
+shouldBe(function() {
+    let x = ["test"];
+    x[0] ||= 42;
+    return x[0];
+}, "test");
+
+shouldBe(function() {
+    let x = [{}];
+    return x[0] ||= 42;
+}, Object);
+
+shouldBe(function() {
+    let x = [{}];
+    x[0] ||= 42;
+    return x[0];
+}, Object);
+
+shouldBe(function() {
+    let x = [[]];
+    return x[0] ||= 42;
+}, Array);
+
+shouldBe(function() {
+    let x = [[]];
+    x[0] ||= 42;
+    return x[0];
+}, Array);
+
+
+
+shouldBe(function() {
+    const x = [];
+    return x[0] ||= 42;
+}, 42);
+
+shouldBe(function() {
+    const x = [];
+    x[0] ||= 42;
+    return x[0];
+}, 42);
+
+shouldBe(function() {
+    const x = [undefined];
+    return x[0] ||= 42;
+}, 42);
+
+shouldBe(function() {
+    const x = [undefined];
+    x[0] ||= 42;
+    return x[0];
+}, 42);
+
+shouldBe(function() {
+    const x = [null];
+    return x[0] ||= 42;
+}, 42);
+
+shouldBe(function() {
+    const x = [null];
+    x[0] ||= 42;
+    return x[0];
+}, 42);
+
+shouldBe(function() {
+    const x = [true];
+    return x[0] ||= 42;
+}, true);
+
+shouldBe(function() {
+    const x = [true];
+    x[0] ||= 42;
+    return x[0];
+}, true);
+
+shouldBe(function() {
+    const x = [false];
+    return x[0] ||= 42;
+}, 42);
+
+shouldBe(function() {
+    const x = [false];
+    x[0] ||= 42;
+    return x[0];
+}, 42);
+
+shouldBe(function() {
+    const x = [0];
+    return x[0] ||= 42;
+}, 42);
+
+shouldBe(function() {
+    const x = [0];
+    x[0] ||= 42;
+    return x[0];
+}, 42);
+
+shouldBe(function() {
+    const x = [1];
+    return x[0] ||= 42;
+}, 1);
+
+shouldBe(function() {
+    const x = [1];
+    x[0] ||= 42;
+    return x[0];
+}, 1);
+
+shouldBe(function() {
+    const x = [""];
+    return x[0] ||= 42;
+}, 42);
+
+shouldBe(function() {
+    const x = [""];
+    x[0] ||= 42;
+    return x[0];
+}, 42);
+
+shouldBe(function() {
+    const x = ["test"];
+    return x[0] ||= 42;
+}, "test");
+
+shouldBe(function() {
+    const x = ["test"];
+    x[0] ||= 42;
+    return x[0];
+}, "test");
+
+shouldBe(function() {
+    const x = [{}];
+    return x[0] ||= 42;
+}, Object);
+
+shouldBe(function() {
+    const x = [{}];
+    x[0] ||= 42;
+    return x[0];
+}, Object);
+
+shouldBe(function() {
+    const x = [[]];
+    return x[0] ||= 42;
+}, Array);
+
+shouldBe(function() {
+    const x = [[]];
+    x[0] ||= 42;
+    return x[0];
+}, Array);
+
+
+
+shouldBe(function() {
+    let x = true;
+    let y = 1;
+    let z = 2;
+    return x ||= y + z;
+}, true);
+
+shouldBe(function() {
+    let x = true;
+    let y = 1;
+    let z = 2;
+    return x ||= y = z;
+}, true);
+
+shouldBe(function() {
+    let x = true;
+    let y = 1;
+    let z = 2;
+    return x ||= y && z;
+}, true);
+
+shouldBe(function() {
+    let x = true;
+    let y = 1;
+    let z = 2;
+    return x ||= y ?? z;
+}, true);
+
+shouldBe(function() {
+    let x = true;
+    let y = 1;
+    let z = 2;
+    return x ||= y || z;
+}, true);
+
+shouldBe(function() {
+    let x = true;
+    let y = 1;
+    let z = 2;
+    return x ||= y &&= z;
+}, true);
+
+shouldBe(function() {
+    let x = true;
+    let y = 1;
+    let z = 2;
+    return x ||= y ||= z;
+}, true);
+
+shouldBe(function() {
+    let x = true;
+    let y = 1;
+    let z = 2;
+    return x ||= y ??= z;
+}, true);
+
+
+
+shouldBe(function() {
+    let x = null;
+    let y = 1;
+    let z = 2;
+    return x ||= y + z;
+}, 3);
+
+shouldBe(function() {
+    let x = null;
+    let y = 1;
+    let z = 2;
+    return x ||= y = z;
+}, 2);
+
+shouldBe(function() {
+    let x = null;
+    let y = 1;
+    let z = 2;
+    return x ||= y && z;
+}, 2);
+
+shouldBe(function() {
+    let x = null;
+    let y = 1;
+    let z = 2;
+    return x ||= y ?? z;
+}, 1);
+
+shouldBe(function() {
+    let x = null;
+    let y = 1;
+    let z = 2;
+    return x ||= y || z;
+}, 1);
+
+shouldBe(function() {
+    let x = null;
+    let y = 1;
+    let z = 2;
+    return x ||= y &&= z;
+}, 2);
+
+shouldBe(function() {
+    let x = null;
+    let y = 1;
+    let z = 2;
+    return x ||= y ||= z;
+}, 1);
+
+shouldBe(function() {
+    let x = null;
+    let y = 1;
+    let z = 2;
+    return x ||= y ??= z;
+}, 1);
+
+
+
+shouldBe(function() {
+    let log = [];
+
+    let a = true;
+    let x = {
+        get a() {
+            log.push("get");
+            return a;
+        },
+        set a(v) {
+            log.push("set");
+            a = v;
+        },
+    };
+
+    x.a ||= 42;
+    x.a ||= 42;
+
+    return log.join(" ") + " " + a;
+}, "get get true");
+
+shouldBe(function() {
+    let log = [];
+
+    let a = false;
+    let x = {
+        get a() {
+            log.push("get");
+            return a;
+        },
+        set a(v) {
+            log.push("set");
+            a = v;
+        },
+    };
+
+    x.a ||= 42;
+    x.a ||= 42;
+
+    return log.join(" ") + " " + a;
+}, "get set get 42");
+
+shouldBe(function() {
+    let log = [];
+
+    let a = undefined;
+    let x = {
+        get a() {
+            log.push("get");
+            return a;
+        },
+        set a(v) {
+            log.push("set");
+            a = v;
+        },
+    };
+
+    x.a ||= 42;
+    x.a ||= 42;
+
+    return log.join(" ") + " " + a;
+}, "get set get 42");
+
+
+
+shouldBe(function() {
+    let log = [];
+
+    let a = true;
+    let x = {
+        get a() {
+            log.push("get");
+            return a;
+        },
+        set a(v) {
+            log.push("set");
+            a = v;
+        },
+    };
+
+    x["a"] ||= 42;
+    x["a"] ||= 42;
+
+    return log.join(" ") + " " + a;
+}, "get get true");
+
+shouldBe(function() {
+    let log = [];
+
+    let a = false;
+    let x = {
+        get a() {
+            log.push("get");
+            return a;
+        },
+        set a(v) {
+            log.push("set");
+            a = v;
+        },
+    };
+
+    x["a"] ||= 42;
+    x["a"] ||= 42;
+
+    return log.join(" ") + " " + a;
+}, "get set get 42");
+
+shouldBe(function() {
+    let log = [];
+
+    let a = undefined;
+    let x = {
+        get a() {
+            log.push("get");
+            return a;
+        },
+        set a(v) {
+            log.push("set");
+            a = v;
+        },
+    };
+
+    x["a"] ||= 42;
+    x["a"] ||= 42;
+
+    return log.join(" ") + " " + a;
+}, "get set get 42");
+
+
+
+shouldBe(function() {
+    let log = [];
+
+    class Parent {
+        get a() {
+            log.push("get-parent");
+            return this._a;
+        }
+        set a(v) {
+            log.push("set-parent");
+            this._a ||= v;
+        }
+    }
+    class Child extends Parent {
+        get a() {
+            log.push("get-child");
+            return super.a;
+        }
+        set a(v) {
+            log.push("set-child");
+            super.a ||= v;
+        }
+    }
+
+    let x = new Child;
+    x._a = true;
+    x.a ||= 42;
+    x.a ||= 42;
+
+    return log.join(" ") + " " + x._a;
+}, "get-child get-parent get-child get-parent true");
+
+shouldBe(function() {
+    let log = [];
+
+    class Parent {
+        get a() {
+            log.push("get-parent");
+            return this._a;
+        }
+        set a(v) {
+            log.push("set-parent");
+            this._a ||= v;
+        }
+    }
+    class Child extends Parent {
+        get a() {
+            log.push("get-child");
+            return super.a;
+        }
+        set a(v) {
+            log.push("set-child");
+            super.a ||= v;
+        }
+    }
+
+    let x = new Child;
+    x._a = false;
+    x.a ||= 42;
+    x.a ||= 42;
+
+    return log.join(" ") + " " + x._a;
+}, "get-child get-parent set-child get-parent set-parent get-child get-parent 42");
+
+shouldBe(function() {
+    let log = [];
+
+    class Parent {
+        get a() {
+            log.push("get-parent");
+            return this._a;
+        }
+        set a(v) {
+            log.push("set-parent");
+            this._a ||= v;
+        }
+    }
+    class Child extends Parent {
+        get a() {
+            log.push("get-child");
+            return super.a;
+        }
+        set a(v) {
+            log.push("set-child");
+            super.a ||= v;
+        }
+    }
+
+    let x = new Child;
+    x._a = undefined;
+    x.a ||= 42;
+    x.a ||= 42;
+
+    return log.join(" ") + " " + x._a;
+}, "get-child get-parent set-child get-parent set-parent get-child get-parent 42");
+
+
+
+shouldBe(function() {
+    let log = [];
+
+    class Parent {
+        get a() {
+            log.push("get-parent");
+            return this._a;
+        }
+        set a(v) {
+            log.push("set-parent");
+            this._a ||= v;
+        }
+    }
+    class Child extends Parent {
+        get a() {
+            log.push("get-child");
+            return super["a"];
+        }
+        set a(v) {
+            log.push("set-child");
+            super["a"] ||= v;
+        }
+    }
+
+    let x = new Child;
+    x._a = true;
+    x["a"] ||= 42;
+    x["a"] ||= 42;
+
+    return log.join(" ") + " " + x._a;
+}, "get-child get-parent get-child get-parent true");
+
+shouldBe(function() {
+    let log = [];
+
+    class Parent {
+        get a() {
+            log.push("get-parent");
+            return this._a;
+        }
+        set a(v) {
+            log.push("set-parent");
+            this._a ||= v;
+        }
+    }
+    class Child extends Parent {
+        get a() {
+            log.push("get-child");
+            return super["a"];
+        }
+        set a(v) {
+            log.push("set-child");
+            super["a"] ||= v;
+        }
+    }
+
+    let x = new Child;
+    x._a = false;
+    x["a"] ||= 42;
+    x["a"] ||= 42;
+
+    return log.join(" ") + " " + x._a;
+}, "get-child get-parent set-child get-parent set-parent get-child get-parent 42");
+
+shouldBe(function() {
+    let log = [];
+
+    class Parent {
+        get a() {
+            log.push("get-parent");
+            return this._a;
+        }
+        set a(v) {
+            log.push("set-parent");
+            this._a ||= v;
+        }
+    }
+    class Child extends Parent {
+        get a() {
+            log.push("get-child");
+            return super["a"];
+        }
+        set a(v) {
+            log.push("set-child");
+            super["a"] ||= v;
+        }
+    }
+
+    let x = new Child;
+    x._a = undefined;
+    x["a"] ||= 42;
+    x["a"] ||= 42;
+
+    return log.join(" ") + " " + x._a;
+}, "get-child get-parent set-child get-parent set-parent get-child get-parent 42");
+
+
+
+shouldBe(function() {
+    let x = {};
+    Object.defineProperty(x, "a", {
+        value: true,
+        writable: false,
+    });
+    return x.a ||= 42;
+}, true);
+
+shouldBe(function() {
+    let x = {};
+    Object.defineProperty(x, "a", {
+        value: false,
+        writable: false,
+    });
+    return x.a ||= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = {};
+    Object.defineProperty(x, "a", {
+        value: undefined,
+        writable: false,
+    });
+    return x.a ||= 42;
+}, 42);
+
+
+
+shouldBe(function() {
+    "use strict";
+    let x = {};
+    Object.defineProperty(x, "a", {
+        value: true,
+        writable: false,
+    });
+    return x.a ||= 42;
+}, true);
+
+shouldThrow(function() {
+    "use strict";
+    let x = {};
+    Object.defineProperty(x, "a", {
+        value: false,
+        writable: false,
+    });
+    return x.a ||= 42;
+}, TypeError);
+
+shouldThrow(function() {
+    "use strict";
+    let x = {};
+    Object.defineProperty(x, "a", {
+        value: undefined,
+        writable: false,
+    });
+    return x.a ||= 42;
+}, TypeError);
+
+
+
+shouldBe(function() {
+    let x = {};
+    Object.defineProperty(x, "a", {
+        value: true,
+        writable: false,
+    });
+    return x["a"] ||= 42;
+}, true);
+
+shouldBe(function() {
+    let x = {};
+    Object.defineProperty(x, "a", {
+        value: false,
+        writable: false,
+    });
+    return x["a"] ||= 42;
+}, 42);
+
+shouldBe(function() {
+    let x = {};
+    Object.defineProperty(x, "a", {
+        value: undefined,
+        writable: false,
+    });
+    return x["a"] ||= 42;
+}, 42);
+
+
+
+shouldBe(function() {
+    "use strict";
+    let x = {};
+    Object.defineProperty(x, "a", {
+        value: true,
+        writable: false,
+    });
+    return x["a"] ||= 42;
+}, true);
+
+shouldThrow(function() {
+    "use strict";
+    let x = {};
+    Object.defineProperty(x, "a", {
+        value: false,
+        writable: false,
+    });
+    return x["a"] ||= 42;
+}, TypeError);
+
+shouldThrow(function() {
+    "use strict";
+    let x = {};
+    Object.defineProperty(x, "a", {
+        value: undefined,
+        writable: false,
+    });
+    return x["a"] ||= 42;
+}, TypeError);
+
+
+
+shouldBe(function() {
+    let x = true;
+    (function() {
+        x ||= 42;
+    })();
+    return x;
+}, true);
+
+shouldBe(function() {
+    let x = false;
+    return (function() {
+        return x ||= 42;
+    })();
+}, 42);
+
+shouldBe(function() {
+    let x = undefined;
+    return (function() {
+        return x ||= 42;
+    })();
+}, 42);
+
+
+
+shouldBe(function() {
+    const x = true;
+    (function() {
+        x ||= 42;
+    })();
+    return x;
+}, true);
+
+shouldThrow(function() {
+    const x = false;
+    return (function() {
+        return x ||= 42;
+    })();
+}, TypeError);
+
+shouldThrow(function() {
+    const x = undefined;
+    return (function() {
+        return x ||= 42;
+    })();
+}, TypeError);
+
+
+
+shouldBe(function() {
+    let count = 0;
+
+    const x = true;
+    x ||= ++count;
+
+    return count;
+}, 0);
+
+shouldBe(function() {
+    let count = 0;
+
+    const x = false;
+    try {
+        x ||= ++count;
+    } catch { }
+
+    return count;
+}, 1);
+
+shouldBe(function() {
+    let count = 0;
+
+    const x = undefined;
+    try {
+        x ||= ++count;
+    } catch { }
+
+    return count;
+}, 1);
+
+
+
+shouldBe(function() {
+    let count = 0;
+
+    const x = true;
+    function capture() { return x; }
+
+    x ||= ++count;
+
+    return count;
+}, 0);
+
+shouldBe(function() {
+    let count = 0;
+
+    const x = false;
+    function capture() { return x; }
+
+    try {
+        x ||= ++count;
+    } catch { }
+
+    return count;
+}, 1);
+
+shouldBe(function() {
+    let count = 0;
+
+    const x = undefined;
+    function capture() { return x; }
+
+    try {
+        x ||= ++count;
+    } catch { }
+
+    return count;
+}, 1);
+
+
+
+shouldThrow(function() {
+    x ||= 42;
+    let x = true;
+    return x;
+}, ReferenceError);
+
+
+
+shouldBe(function() {
+    return undefined ||= 42;
+}, 42);
+
+shouldThrowSyntaxError(`null ||= 42`);
+
+shouldThrowSyntaxError(`true ||= 42`);
+
+shouldThrowSyntaxError(`false ||= 42`);
+
+shouldThrowSyntaxError(`0 ||= 42`);
+
+shouldThrowSyntaxError(`1 ||= 42`);
+
+shouldThrowSyntaxError(`"" ||= 42`);
+
+shouldThrowSyntaxError(`"test" ||= 42`);
+
+shouldThrowSyntaxError(`{} ||= 42`);
+
+shouldThrowSyntaxError(`[] ||= 42`);

Modified: trunk/JSTests/test262/config.yaml (260118 => 260119)


--- trunk/JSTests/test262/config.yaml	2020-04-15 06:20:39 UTC (rev 260118)
+++ trunk/JSTests/test262/config.yaml	2020-04-15 07:02:55 UTC (rev 260119)
@@ -4,6 +4,7 @@
   BigInt: useBigInt
   WeakRef: useWeakRefs
   class-fields-public: usePublicClassFields
+  logical-assignment-operators: useLogicalAssignmentOperators
 skip:
   features:
     - SharedArrayBuffer
@@ -20,8 +21,6 @@
     # https://bugs.webkit.org/show_bug.cgi?id=202475
     - regexp-match-indices
     - top-level-await
-    # https://bugs.webkit.org/show_bug.cgi?id=209716
-    - logical-assignment-operators
     # https://bugs.webkit.org/show_bug.cgi?id=202566
     - Promise.any
     - AggregateError

Modified: trunk/JSTests/test262/expectations.yaml (260118 => 260119)


--- trunk/JSTests/test262/expectations.yaml	2020-04-15 06:20:39 UTC (rev 260118)
+++ trunk/JSTests/test262/expectations.yaml	2020-04-15 07:02:55 UTC (rev 260119)
@@ -2718,6 +2718,12 @@
 test/language/expressions/instanceof/prototype-getter-with-primitive.js:
   default: "Test262Error: getter for 'prototype' called"
   strict mode: "Test262Error: getter for 'prototype' called"
+test/language/expressions/logical-assignment/lgcl-and-assignment-operator-non-simple-lhs.js:
+  default: "Test262: This statement should not be evaluated."
+test/language/expressions/logical-assignment/lgcl-nullish-assignment-operator-non-simple-lhs.js:
+  default: "Test262: This statement should not be evaluated."
+test/language/expressions/logical-assignment/lgcl-or-assignment-operator-non-simple-lhs.js:
+  default: "Test262: This statement should not be evaluated."
 test/language/expressions/new/ctorExpr-fn-ref-before-args-eval-fn-wrapup.js:
   default: "TypeError: 1 is not a constructor (evaluating 'new x(x = 1)')"
   strict mode: "TypeError: 1 is not a constructor (evaluating 'new x(x = 1)')"

Modified: trunk/Source/_javascript_Core/ChangeLog (260118 => 260119)


--- trunk/Source/_javascript_Core/ChangeLog	2020-04-15 06:20:39 UTC (rev 260118)
+++ trunk/Source/_javascript_Core/ChangeLog	2020-04-15 07:02:55 UTC (rev 260119)
@@ -1,3 +1,58 @@
+2020-04-15  Devin Rousso  <[email protected]>
+
+        [ESNext] Implement logical assignment operators
+        https://bugs.webkit.org/show_bug.cgi?id=209716
+
+        Reviewed by Ross Kirsling.
+
+        Implement the logical assignment operators proposal, which is now Stage 3. It introduces
+        three new assignment operators which will only store the result of the rhs in the lhs if the
+        lhs meets the given condition:
+         - `??=`, for if the lhs is nullish (`null` or `undefined`)
+         - `||=`, for if the lhs is falsy
+         - `&&=`, for if the lhs is truthy
+
+        This short circuiting can be beneficial as it can avoid a redundant store when used in the
+        common _javascript_ programming pattern of "defaulting" a parameter.
+
+        ```js
+            function foo(x) {
+                x = x || 42;
+            }
+        ```
+
+        If `x` is a truthy value, it would result in the rhs `x` being stored back into the lhs `x`.
+        In some situations, this can have negative unintended side-effects, such as for `innerHTML`.
+
+        Logical assignment operators, however, are defined such that they only store if the rhs is
+        to actually be needed/used, skipping the redundant store and simply returning lhs otherwise.
+
+        In the case of readonly references, this means that an error is only thrown when the
+        assignment occurs, meaning that if the lhs already satisfies the condition it will be used
+        and returned with no error.
+
+        * parser/ParserTokens.h:
+        * parser/Lexer.cpp:
+        (JSC::Lexer<T>::lexWithoutClearingLineTerminator):
+        * parser/Parser.cpp:
+        (JSC::Parser<LexerType>::parseAssignmentExpression):
+
+        * parser/ASTBuilder.h:
+        (JSC::ASTBuilder::makeAssignNode):
+        * parser/Nodes.h:
+        * parser/NodeConstructors.h:
+        (JSC::ShortCircuitReadModifyResolveNode::ShortCircuitReadModifyResolveNode): Added.
+        (JSC::ShortCircuitReadModifyBracketNode::ShortCircuitReadModifyBracketNode): Added.
+        (JSC::ShortCircuitReadModifyDotNode::ShortCircuitReadModifyDotNode): Added.
+        * bytecompiler/NodesCodegen.cpp:
+        (JSC::emitShortCircuitAssignment): Added.
+        (JSC::ShortCircuitReadModifyResolveNode::emitBytecode): Added.
+        (JSC::ShortCircuitReadModifyDotNode::emitBytecode): Added.
+        (JSC::ShortCircuitReadModifyBracketNode::emitBytecode): Added.
+
+        * runtime/OptionsList.h:
+        Add a `useLogicalAssignmentOperators` setting for controlling this feature.
+
 2020-04-14  Devin Rousso  <[email protected]>
 
         Web Inspector: Debugger: add a Step next that steps by _expression_

Modified: trunk/Source/_javascript_Core/bytecompiler/NodesCodegen.cpp (260118 => 260119)


--- trunk/Source/_javascript_Core/bytecompiler/NodesCodegen.cpp	2020-04-15 06:20:39 UTC (rev 260118)
+++ trunk/Source/_javascript_Core/bytecompiler/NodesCodegen.cpp	2020-04-15 07:02:55 UTC (rev 260119)
@@ -2862,6 +2862,109 @@
     return returnResult;
 }
 
+// ------------------------------ ShortCircuitReadModifyResolveNode -----------------------------------
+
+static ALWAYS_INLINE void emitShortCircuitAssignment(BytecodeGenerator& generator, RegisterID* value, Operator oper, Label& afterAssignment)
+{
+    switch (oper) {
+    case Operator::NullishEq:
+        generator.emitJumpIfFalse(generator.emitIsUndefinedOrNull(generator.newTemporary(), value), afterAssignment);
+        break;
+
+    case Operator::OrEq:
+        generator.emitJumpIfTrue(value, afterAssignment);
+        break;
+
+    case Operator::AndEq:
+        generator.emitJumpIfFalse(value, afterAssignment);
+        break;
+
+    default:
+        RELEASE_ASSERT_NOT_REACHED();
+        break;
+    }
+}
+
+RegisterID* ShortCircuitReadModifyResolveNode::emitBytecode(BytecodeGenerator& generator, RegisterID* dst)
+{
+    JSTextPosition newDivot = divotStart() + m_ident.length();
+
+    Variable var = generator.variable(m_ident);
+    bool isReadOnly = var.isReadOnly();
+
+    if (RefPtr<RegisterID> local = var.local()) {
+        generator.emitTDZCheckIfNecessary(var, local.get(), nullptr);
+
+        if (isReadOnly) {
+            RefPtr<RegisterID> result = local;
+
+            Ref<Label> afterAssignment = generator.newLabel();
+            emitShortCircuitAssignment(generator, result.get(), m_operator, afterAssignment.get());
+
+            result = generator.emitNode(result.get(), m_right); // Execute side effects first.
+            bool threwException = generator.emitReadOnlyExceptionIfNeeded(var);
+
+            if (!threwException)
+                generator.emitProfileType(result.get(), divotStart(), divotEnd());
+
+            generator.emitLabel(afterAssignment.get());
+            return generator.move(dst, result.get());
+        }
+
+        if (generator.leftHandSideNeedsCopy(m_rightHasAssignments, m_right->isPure(generator))) {
+            RefPtr<RegisterID> result = generator.tempDestination(dst);
+            generator.move(result.get(), local.get());
+
+            Ref<Label> afterAssignment = generator.newLabel();
+            emitShortCircuitAssignment(generator, result.get(), m_operator, afterAssignment.get());
+
+            result = generator.emitNode(result.get(), m_right);
+            generator.move(local.get(), result.get());
+            generator.emitProfileType(result.get(), var, divotStart(), divotEnd());
+
+            generator.emitLabel(afterAssignment.get());
+            return generator.move(dst, result.get());
+        }
+
+        RefPtr<RegisterID> result = local;
+
+        Ref<Label> afterAssignment = generator.newLabel();
+        emitShortCircuitAssignment(generator, result.get(), m_operator, afterAssignment.get());
+
+        result = generator.emitNode(result.get(), m_right);
+        generator.emitProfileType(result.get(), var, divotStart(), divotEnd());
+
+        generator.emitLabel(afterAssignment.get());
+        return generator.move(dst, result.get());
+    }
+
+    generator.emitExpressionInfo(newDivot, divotStart(), newDivot);
+    RefPtr<RegisterID> scope = generator.emitResolveScope(nullptr, var);
+
+    RefPtr<RegisterID> result = generator.emitGetFromScope(generator.tempDestination(dst), scope.get(), var, ThrowIfNotFound);
+    generator.emitTDZCheckIfNecessary(var, result.get(), nullptr);
+
+    Ref<Label> afterAssignment = generator.newLabel();
+    emitShortCircuitAssignment(generator, result.get(), m_operator, afterAssignment.get());
+
+    result = generator.emitNode(result.get(), m_right); // Execute side effects first.
+
+    bool threwException = isReadOnly ? generator.emitReadOnlyExceptionIfNeeded(var) : false;
+
+    if (!threwException)
+        generator.emitExpressionInfo(divot(), divotStart(), divotEnd());
+
+    if (!isReadOnly) {
+        result = generator.emitPutToScope(scope.get(), var, result.get(), ThrowIfNotFound, InitializationMode::NotInitialization);
+        generator.emitProfileType(result.get(), var, divotStart(), divotEnd());
+    }
+
+    generator.emitLabel(afterAssignment.get());
+    return generator.move(dst, result.get());
+}
+
+// ------------------------------ AssignResolveNode -----------------------------------
+
 static InitializationMode initializationModeForAssignmentContext(AssignmentContext assignmentContext)
 {
     switch (assignmentContext) {
@@ -2877,8 +2980,6 @@
     return InitializationMode::NotInitialization;
 }
 
-// ------------------------------ AssignResolveNode -----------------------------------
-
 RegisterID* AssignResolveNode::emitBytecode(BytecodeGenerator& generator, RegisterID* dst)
 {
     Variable var = generator.variable(m_ident);
@@ -2978,6 +3079,37 @@
     return ret;
 }
 
+// ------------------------------ ShortCircuitReadModifyDotNode -----------------------------------
+
+RegisterID* ShortCircuitReadModifyDotNode::emitBytecode(BytecodeGenerator& generator, RegisterID* dst)
+{
+    RefPtr<RegisterID> base = generator.emitNodeForLeftHandSide(m_base, m_rightHasAssignments, m_right->isPure(generator));
+    RefPtr<RegisterID> thisValue;
+
+    RefPtr<RegisterID> result;
+
+    generator.emitExpressionInfo(subexpressionDivot(), subexpressionStart(), subexpressionEnd());
+    if (m_base->isSuperNode()) {
+        thisValue = generator.ensureThis();
+        result = generator.emitGetById(generator.tempDestination(dst), base.get(), thisValue.get(), m_ident);
+    } else
+        result = generator.emitGetById(generator.tempDestination(dst), base.get(), m_ident);
+
+    Ref<Label> afterAssignment = generator.newLabel();
+    emitShortCircuitAssignment(generator, result.get(), m_operator, afterAssignment.get());
+
+    result = generator.emitNode(result.get(), m_right);
+    generator.emitExpressionInfo(divot(), divotStart(), divotEnd());
+    if (m_base->isSuperNode())
+        result = generator.emitPutById(base.get(), thisValue.get(), m_ident, result.get());
+    else
+        result = generator.emitPutById(base.get(), m_ident, result.get());
+    generator.emitProfileType(result.get(), divotStart(), divotEnd());
+
+    generator.emitLabel(afterAssignment.get());
+    return generator.move(dst, result.get());
+}
+
 // ------------------------------ AssignErrorNode -----------------------------------
 
 RegisterID* AssignErrorNode::emitBytecode(BytecodeGenerator& generator, RegisterID*)
@@ -3042,6 +3174,38 @@
     return updatedValue;
 }
 
+// ------------------------------ ShortCircuitReadModifyBracketNode -----------------------------------
+
+RegisterID* ShortCircuitReadModifyBracketNode::emitBytecode(BytecodeGenerator& generator, RegisterID* dst)
+{
+    RefPtr<RegisterID> base = generator.emitNodeForLeftHandSide(m_base, m_subscriptHasAssignments || m_rightHasAssignments, m_subscript->isPure(generator) && m_right->isPure(generator));
+    RefPtr<RegisterID> property = generator.emitNodeForLeftHandSideForProperty(m_subscript, m_rightHasAssignments, m_right->isPure(generator));
+    RefPtr<RegisterID> thisValue;
+
+    RefPtr<RegisterID> result;
+
+    generator.emitExpressionInfo(subexpressionDivot(), subexpressionStart(), subexpressionEnd());
+    if (m_base->isSuperNode()) {
+        thisValue = generator.ensureThis();
+        result = generator.emitGetByVal(generator.tempDestination(dst), base.get(), thisValue.get(), property.get());
+    } else
+        result = generator.emitGetByVal(generator.tempDestination(dst), base.get(), property.get());
+
+    Ref<Label> afterAssignment = generator.newLabel();
+    emitShortCircuitAssignment(generator, result.get(), m_operator, afterAssignment.get());
+
+    result = generator.emitNode(result.get(), m_right);
+    generator.emitExpressionInfo(divot(), divotStart(), divotEnd());
+    if (m_base->isSuperNode())
+        result = generator.emitPutByVal(base.get(), thisValue.get(), property.get(), result.get());
+    else
+        result = generator.emitPutByVal(base.get(), property.get(), result.get());
+    generator.emitProfileType(result.get(), divotStart(), divotEnd());
+
+    generator.emitLabel(afterAssignment.get());
+    return generator.move(dst, result.get());
+}
+
 // ------------------------------ CommaNode ------------------------------------
 
 RegisterID* CommaNode::emitBytecode(BytecodeGenerator& generator, RegisterID* dst)

Modified: trunk/Source/_javascript_Core/parser/ASTBuilder.h (260118 => 260119)


--- trunk/Source/_javascript_Core/parser/ASTBuilder.h	2020-04-15 06:20:39 UTC (rev 260118)
+++ trunk/Source/_javascript_Core/parser/ASTBuilder.h	2020-04-15 07:02:55 UTC (rev 260119)
@@ -1548,6 +1548,7 @@
 
     if (loc->isResolveNode()) {
         ResolveNode* resolve = static_cast<ResolveNode*>(loc);
+
         if (op == Operator::Equal) {
             if (expr->isBaseFuncExprNode()) {
                 auto metadata = static_cast<BaseFuncExprNode*>(expr)->metadata();
@@ -1558,21 +1559,42 @@
             setExceptionLocation(node, start, divot, end);
             return node;
         }
+
+        if (op == Operator::NullishEq || op == Operator::OrEq || op == Operator::AndEq)
+            return new (m_parserArena) ShortCircuitReadModifyResolveNode(location, resolve->identifier(), op, expr, exprHasAssignments, divot, start, end);
+
         return new (m_parserArena) ReadModifyResolveNode(location, resolve->identifier(), op, expr, exprHasAssignments, divot, start, end);
     }
+
     if (loc->isBracketAccessorNode()) {
         BracketAccessorNode* bracket = static_cast<BracketAccessorNode*>(loc);
+
         if (op == Operator::Equal)
             return new (m_parserArena) AssignBracketNode(location, bracket->base(), bracket->subscript(), expr, locHasAssignments, exprHasAssignments, bracket->divot(), start, end);
+
+        if (op == Operator::NullishEq || op == Operator::OrEq || op == Operator::AndEq) {
+            auto* node = new (m_parserArena) ShortCircuitReadModifyBracketNode(location, bracket->base(), bracket->subscript(), op, expr, locHasAssignments, exprHasAssignments, divot, start, end);
+            node->setSubexpressionInfo(bracket->divot(), bracket->divotEnd().offset);
+            return node;
+        }
+
         ReadModifyBracketNode* node = new (m_parserArena) ReadModifyBracketNode(location, bracket->base(), bracket->subscript(), op, expr, locHasAssignments, exprHasAssignments, divot, start, end);
         node->setSubexpressionInfo(bracket->divot(), bracket->divotEnd().offset);
         return node;
     }
+
     ASSERT(loc->isDotAccessorNode());
     DotAccessorNode* dot = static_cast<DotAccessorNode*>(loc);
+
     if (op == Operator::Equal)
         return new (m_parserArena) AssignDotNode(location, dot->base(), dot->identifier(), expr, exprHasAssignments, dot->divot(), start, end);
 
+    if (op == Operator::NullishEq || op == Operator::OrEq || op == Operator::AndEq) {
+        auto* node = new (m_parserArena) ShortCircuitReadModifyDotNode(location, dot->base(), dot->identifier(), op, expr, exprHasAssignments, divot, start, end);
+        node->setSubexpressionInfo(dot->divot(), dot->divotEnd().offset);
+        return node;
+    }
+
     ReadModifyDotNode* node = new (m_parserArena) ReadModifyDotNode(location, dot->base(), dot->identifier(), op, expr, exprHasAssignments, divot, start, end);
     node->setSubexpressionInfo(dot->divot(), dot->divotEnd().offset);
     return node;

Modified: trunk/Source/_javascript_Core/parser/Lexer.cpp (260118 => 260119)


--- trunk/Source/_javascript_Core/parser/Lexer.cpp	2020-04-15 06:20:39 UTC (rev 260118)
+++ trunk/Source/_javascript_Core/parser/Lexer.cpp	2020-04-15 07:02:55 UTC (rev 260119)
@@ -2086,6 +2086,11 @@
         shift();
         if (m_current == '&') {
             shift();
+            if (UNLIKELY(Options::useLogicalAssignmentOperators() && m_current == '=')) {
+                shift();
+                token = ANDEQUAL;
+                break;
+            }
             token = AND;
             break;
         }
@@ -2123,6 +2128,11 @@
         }
         if (m_current == '|') {
             shift();
+            if (UNLIKELY(Options::useLogicalAssignmentOperators() && m_current == '=')) {
+                shift();
+                token = OREQUAL;
+                break;
+            }
             token = OR;
             break;
         }
@@ -2159,6 +2169,11 @@
         shift();
         if (m_current == '?') {
             shift();
+            if (UNLIKELY(Options::useLogicalAssignmentOperators() && m_current == '=')) {
+                shift();
+                token = NULLISHEQUAL;
+                break;
+            }
             token = COALESCE;
             break;
         }

Modified: trunk/Source/_javascript_Core/parser/NodeConstructors.h (260118 => 260119)


--- trunk/Source/_javascript_Core/parser/NodeConstructors.h	2020-04-15 06:20:39 UTC (rev 260118)
+++ trunk/Source/_javascript_Core/parser/NodeConstructors.h	2020-04-15 07:02:55 UTC (rev 260119)
@@ -718,6 +718,16 @@
     {
     }
 
+    inline ShortCircuitReadModifyResolveNode::ShortCircuitReadModifyResolveNode(const JSTokenLocation& location, const Identifier& ident, Operator oper, ExpressionNode* right, bool rightHasAssignments, const JSTextPosition& divot, const JSTextPosition& divotStart, const JSTextPosition& divotEnd)
+        : ExpressionNode(location)
+        , ThrowableExpressionData(divot, divotStart, divotEnd)
+        , m_ident(ident)
+        , m_right(right)
+        , m_operator(oper)
+        , m_rightHasAssignments(rightHasAssignments)
+    {
+    }
+
     inline AssignResolveNode::AssignResolveNode(const JSTokenLocation& location, const Identifier& ident, ExpressionNode* right, AssignmentContext assignmentContext)
         : ExpressionNode(location)
         , m_ident(ident)
@@ -739,6 +749,18 @@
     {
     }
 
+    inline ShortCircuitReadModifyBracketNode::ShortCircuitReadModifyBracketNode(const JSTokenLocation& location, ExpressionNode* base, ExpressionNode* subscript, Operator oper, ExpressionNode* right, bool subscriptHasAssignments, bool rightHasAssignments, const JSTextPosition& divot, const JSTextPosition& divotStart, const JSTextPosition& divotEnd)
+        : ExpressionNode(location)
+        , ThrowableSubExpressionData(divot, divotStart, divotEnd)
+        , m_base(base)
+        , m_subscript(subscript)
+        , m_right(right)
+        , m_operator(oper)
+        , m_subscriptHasAssignments(subscriptHasAssignments)
+        , m_rightHasAssignments(rightHasAssignments)
+    {
+    }
+
     inline AssignBracketNode::AssignBracketNode(const JSTokenLocation& location, ExpressionNode* base, ExpressionNode* subscript, ExpressionNode* right, bool subscriptHasAssignments, bool rightHasAssignments, const JSTextPosition& divot, const JSTextPosition& divotStart, const JSTextPosition& divotEnd)
         : ExpressionNode(location)
         , ThrowableExpressionData(divot, divotStart, divotEnd)
@@ -771,6 +793,17 @@
     {
     }
 
+    inline ShortCircuitReadModifyDotNode::ShortCircuitReadModifyDotNode(const JSTokenLocation& location, ExpressionNode* base, const Identifier& ident, Operator oper, ExpressionNode* right, bool rightHasAssignments, const JSTextPosition& divot, const JSTextPosition& divotStart, const JSTextPosition& divotEnd)
+        : ExpressionNode(location)
+        , ThrowableSubExpressionData(divot, divotStart, divotEnd)
+        , m_base(base)
+        , m_ident(ident)
+        , m_right(right)
+        , m_operator(oper)
+        , m_rightHasAssignments(rightHasAssignments)
+    {
+    }
+
     inline AssignErrorNode::AssignErrorNode(const JSTokenLocation& location, const JSTextPosition& divot, const JSTextPosition& divotStart, const JSTextPosition& divotEnd)
         : ExpressionNode(location)
         , ThrowableExpressionData(divot, divotStart, divotEnd)

Modified: trunk/Source/_javascript_Core/parser/Nodes.h (260118 => 260119)


--- trunk/Source/_javascript_Core/parser/Nodes.h	2020-04-15 06:20:39 UTC (rev 260118)
+++ trunk/Source/_javascript_Core/parser/Nodes.h	2020-04-15 07:02:55 UTC (rev 260119)
@@ -68,6 +68,9 @@
         BitOrEq,
         ModEq,
         PowEq,
+        NullishEq,
+        OrEq,
+        AndEq,
         LShift,
         RShift,
         URShift
@@ -1389,6 +1392,19 @@
         bool m_rightHasAssignments;
     };
 
+    class ShortCircuitReadModifyResolveNode final : public ExpressionNode, public ThrowableExpressionData {
+    public:
+        ShortCircuitReadModifyResolveNode(const JSTokenLocation&, const Identifier&, Operator, ExpressionNode*  right, bool rightHasAssignments, const JSTextPosition& divot, const JSTextPosition& divotStart, const JSTextPosition& divotEnd);
+
+    private:
+        RegisterID* emitBytecode(BytecodeGenerator&, RegisterID* = 0) override;
+
+        const Identifier& m_ident;
+        ExpressionNode* m_right;
+        Operator m_operator;
+        bool m_rightHasAssignments;
+    };
+
     class AssignResolveNode final : public ExpressionNode, public ThrowableExpressionData {
     public:
         AssignResolveNode(const JSTokenLocation&, const Identifier&, ExpressionNode* right, AssignmentContext);
@@ -1418,6 +1434,21 @@
         bool m_rightHasAssignments : 1;
     };
 
+    class ShortCircuitReadModifyBracketNode final : public ExpressionNode, public ThrowableSubExpressionData {
+    public:
+        ShortCircuitReadModifyBracketNode(const JSTokenLocation&, ExpressionNode* base, ExpressionNode* subscript, Operator, ExpressionNode* right, bool subscriptHasAssignments, bool rightHasAssignments, const JSTextPosition& divot, const JSTextPosition& divotStart, const JSTextPosition& divotEnd);
+
+    private:
+        RegisterID* emitBytecode(BytecodeGenerator&, RegisterID* = 0) override;
+
+        ExpressionNode* m_base;
+        ExpressionNode* m_subscript;
+        ExpressionNode* m_right;
+        Operator m_operator;
+        bool m_subscriptHasAssignments : 1;
+        bool m_rightHasAssignments : 1;
+    };
+
     class AssignBracketNode final : public ExpressionNode, public ThrowableExpressionData {
     public:
         AssignBracketNode(const JSTokenLocation&, ExpressionNode* base, ExpressionNode* subscript, ExpressionNode* right, bool subscriptHasAssignments, bool rightHasAssignments, const JSTextPosition& divot, const JSTextPosition& divotStart, const JSTextPosition& divotEnd);
@@ -1459,6 +1490,20 @@
         bool m_rightHasAssignments : 1;
     };
 
+    class ShortCircuitReadModifyDotNode final : public ExpressionNode, public ThrowableSubExpressionData {
+    public:
+        ShortCircuitReadModifyDotNode(const JSTokenLocation&, ExpressionNode* base, const Identifier&, Operator, ExpressionNode* right, bool rightHasAssignments, const JSTextPosition& divot, const JSTextPosition& divotStart, const JSTextPosition& divotEnd);
+
+    private:
+        RegisterID* emitBytecode(BytecodeGenerator&, RegisterID* = 0) override;
+
+        ExpressionNode* m_base;
+        const Identifier& m_ident;
+        ExpressionNode* m_right;
+        Operator m_operator;
+        bool m_rightHasAssignments : 1;
+    };
+
     class AssignErrorNode final : public ExpressionNode, public ThrowableExpressionData {
     public:
         AssignErrorNode(const JSTokenLocation&, const JSTextPosition& divot, const JSTextPosition& divotStart, const JSTextPosition& divotEnd);

Modified: trunk/Source/_javascript_Core/parser/Parser.cpp (260118 => 260119)


--- trunk/Source/_javascript_Core/parser/Parser.cpp	2020-04-15 06:20:39 UTC (rev 260118)
+++ trunk/Source/_javascript_Core/parser/Parser.cpp	2020-04-15 07:02:55 UTC (rev 260119)
@@ -3847,7 +3847,7 @@
 
     failIfFalse(lhs, "Cannot parse _expression_");
     if (initialNonLHSCount != m_parserState.nonLHSCount) {
-        if (m_token.m_type >= EQUAL && m_token.m_type <= BITOREQUAL)
+        if (m_token.m_type >= EQUAL && m_token.m_type <= ANDEQUAL)
             semanticFail("Left hand side of operator '", getToken(), "' must be a reference");
 
         return lhs;
@@ -3871,6 +3871,9 @@
         case BITOREQUAL: op = Operator::BitOrEq; break;
         case MODEQUAL: op = Operator::ModEq; break;
         case POWEQUAL: op = Operator::PowEq; break;
+        case NULLISHEQUAL: op = Operator::NullishEq; break;
+        case OREQUAL: op = Operator::OrEq; break;
+        case ANDEQUAL: op = Operator::AndEq; break;
         default:
             goto end;
         }
@@ -3890,7 +3893,7 @@
         lhs = parseAssignmentExpression(context);
         failIfFalse(lhs, "Cannot parse the right hand side of an assignment _expression_");
         if (initialNonLHSCount != m_parserState.nonLHSCount) {
-            if (m_token.m_type >= EQUAL && m_token.m_type <= BITOREQUAL)
+            if (m_token.m_type >= EQUAL && m_token.m_type <= ANDEQUAL)
                 semanticFail("Left hand side of operator '", getToken(), "' must be a reference");
             break;
         }

Modified: trunk/Source/_javascript_Core/parser/ParserTokens.h (260118 => 260119)


--- trunk/Source/_javascript_Core/parser/ParserTokens.h	2020-04-15 06:20:39 UTC (rev 260118)
+++ trunk/Source/_javascript_Core/parser/ParserTokens.h	2020-04-15 07:02:55 UTC (rev 260119)
@@ -134,6 +134,9 @@
     BITANDEQUAL,
     BITXOREQUAL,
     BITOREQUAL,
+    NULLISHEQUAL,
+    OREQUAL,
+    ANDEQUAL,
     DOTDOTDOT,
     ARROWFUNCTION,
     QUESTIONDOT,

Modified: trunk/Source/_javascript_Core/runtime/OptionsList.h (260118 => 260119)


--- trunk/Source/_javascript_Core/runtime/OptionsList.h	2020-04-15 06:20:39 UTC (rev 260118)
+++ trunk/Source/_javascript_Core/runtime/OptionsList.h	2020-04-15 07:02:55 UTC (rev 260119)
@@ -500,6 +500,7 @@
     v(Bool, forceOSRExitToLLInt, false, Normal, "If true, we always exit to the LLInt. If false, we exit to whatever is most convenient.") \
     v(Unsigned, getByValICMaxNumberOfIdentifiers, 4, Normal, "Number of identifiers we see in the LLInt that could cause us to bail on generating an IC for get_by_val.") \
     v(Bool, usePublicClassFields, true, Normal, "If true, the parser will understand public data fields inside classes.") \
+    v(Bool, useLogicalAssignmentOperators, false, Normal, "Enable support for \?\?=, ||=, and &&= logical assignment operators.") \
     v(Bool, useRandomizingExecutableIslandAllocation, false, Normal, "For the arm64 ExecutableAllocator, if true, select which region to use randomly. This is useful for testing that jump islands work.") \
 
 enum OptionEquivalence {

Modified: trunk/Tools/ChangeLog (260118 => 260119)


--- trunk/Tools/ChangeLog	2020-04-15 06:20:39 UTC (rev 260118)
+++ trunk/Tools/ChangeLog	2020-04-15 07:02:55 UTC (rev 260119)
@@ -1,3 +1,12 @@
+2020-04-15  Devin Rousso  <[email protected]>
+
+        [ESNext] Implement logical assignment operators
+        https://bugs.webkit.org/show_bug.cgi?id=209716
+
+        Reviewed by Ross Kirsling.
+
+        * Scripts/run-jsc-stress-tests:
+
 2020-04-14  Jer Noble  <[email protected]>
 
         WKTR always enables capturing audio/video in GPUProcess

Modified: trunk/Tools/Scripts/run-jsc-stress-tests (260118 => 260119)


--- trunk/Tools/Scripts/run-jsc-stress-tests	2020-04-15 06:20:39 UTC (rev 260118)
+++ trunk/Tools/Scripts/run-jsc-stress-tests	2020-04-15 07:02:55 UTC (rev 260119)
@@ -787,6 +787,10 @@
     run("mini-mode", "--forceMiniVMMode=true", *optionalTestSpecificOptions)
 end
 
+def runLogicalAssignmentOperatorsEnabled(*optionalTestSpecificOptions)
+    run("logical-assignment-operators-enabled", "--useLogicalAssignmentOperators=true" , *(FTL_OPTIONS + optionalTestSpecificOptions))
+end
+
 def defaultRun
     if $mode == "quick"
         defaultQuickRun
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to