[ESNext] Implement logical assignment operators
authordrousso@apple.com <drousso@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 15 Apr 2020 07:02:55 +0000 (07:02 +0000)
committerdrousso@apple.com <drousso@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 15 Apr 2020 07:02:55 +0000 (07:02 +0000)
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/JavaScriptCore:

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:

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@260119 268f45cc-cd09-0410-ab3c-d52691b4dbfc

17 files changed:
JSTests/ChangeLog
JSTests/stress/logical-assignment-operator-and.js [new file with mode: 0644]
JSTests/stress/logical-assignment-operator-nullish.js [new file with mode: 0644]
JSTests/stress/logical-assignment-operator-or.js [new file with mode: 0644]
JSTests/test262/config.yaml
JSTests/test262/expectations.yaml
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp
Source/JavaScriptCore/parser/ASTBuilder.h
Source/JavaScriptCore/parser/Lexer.cpp
Source/JavaScriptCore/parser/NodeConstructors.h
Source/JavaScriptCore/parser/Nodes.h
Source/JavaScriptCore/parser/Parser.cpp
Source/JavaScriptCore/parser/ParserTokens.h
Source/JavaScriptCore/runtime/OptionsList.h
Tools/ChangeLog
Tools/Scripts/run-jsc-stress-tests

index 9dc989c..db90f5f 100644 (file)
@@ -1,3 +1,22 @@
+2020-04-15  Devin Rousso  <drousso@apple.com>
+
+        [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  <sbarati@apple.com>
 
         Skip all low executable memory wasm tests on arm64
diff --git a/JSTests/stress/logical-assignment-operator-and.js b/JSTests/stress/logical-assignment-operator-and.js
new file mode 100644 (file)
index 0000000..f9757a5
--- /dev/null
@@ -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`);
diff --git a/JSTests/stress/logical-assignment-operator-nullish.js b/JSTests/stress/logical-assignment-operator-nullish.js
new file mode 100644 (file)
index 0000000..f9465cc
--- /dev/null
@@ -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`);
diff --git a/JSTests/stress/logical-assignment-operator-or.js b/JSTests/stress/logical-assignment-operator-or.js
new file mode 100644 (file)
index 0000000..1e894c4
--- /dev/null
@@ -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`);
index 0b6b56e..0c67a4d 100644 (file)
@@ -4,6 +4,7 @@ flags:
   BigInt: useBigInt
   WeakRef: useWeakRefs
   class-fields-public: usePublicClassFields
+  logical-assignment-operators: useLogicalAssignmentOperators
 skip:
   features:
     - SharedArrayBuffer
@@ -20,8 +21,6 @@ skip:
     # 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
index 4ae2a77..630fac9 100644 (file)
@@ -2718,6 +2718,12 @@ test/language/expressions/import.meta/syntax/invalid-assignment-target-for-of-lo
 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)')"
index 81b82cb..7c382d7 100644 (file)
@@ -1,3 +1,58 @@
+2020-04-15  Devin Rousso  <drousso@apple.com>
+
+        [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  <drousso@apple.com>
 
         Web Inspector: Debugger: add a Step next that steps by expression
index d4fdb4d..54c3a9f 100644 (file)
@@ -2862,6 +2862,109 @@ RegisterID* ReadModifyResolveNode::emitBytecode(BytecodeGenerator& generator, Re
     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 @@ static InitializationMode initializationModeForAssignmentContext(AssignmentConte
     return InitializationMode::NotInitialization;
 }
 
-// ------------------------------ AssignResolveNode -----------------------------------
-
 RegisterID* AssignResolveNode::emitBytecode(BytecodeGenerator& generator, RegisterID* dst)
 {
     Variable var = generator.variable(m_ident);
@@ -2978,6 +3079,37 @@ RegisterID* ReadModifyDotNode::emitBytecode(BytecodeGenerator& generator, Regist
     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 @@ RegisterID* ReadModifyBracketNode::emitBytecode(BytecodeGenerator& generator, Re
     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)
index 43b171f..5e353bd 100644 (file)
@@ -1548,6 +1548,7 @@ ExpressionNode* ASTBuilder::makeAssignNode(const JSTokenLocation& location, Expr
 
     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 @@ ExpressionNode* ASTBuilder::makeAssignNode(const JSTokenLocation& location, Expr
             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;
index 4fb83ca..bae5f6f 100644 (file)
@@ -2086,6 +2086,11 @@ start:
         shift();
         if (m_current == '&') {
             shift();
+            if (UNLIKELY(Options::useLogicalAssignmentOperators() && m_current == '=')) {
+                shift();
+                token = ANDEQUAL;
+                break;
+            }
             token = AND;
             break;
         }
@@ -2123,6 +2128,11 @@ start:
         }
         if (m_current == '|') {
             shift();
+            if (UNLIKELY(Options::useLogicalAssignmentOperators() && m_current == '=')) {
+                shift();
+                token = OREQUAL;
+                break;
+            }
             token = OR;
             break;
         }
@@ -2159,6 +2169,11 @@ start:
         shift();
         if (m_current == '?') {
             shift();
+            if (UNLIKELY(Options::useLogicalAssignmentOperators() && m_current == '=')) {
+                shift();
+                token = NULLISHEQUAL;
+                break;
+            }
             token = COALESCE;
             break;
         }
index fd97a0c..9d5f931 100644 (file)
@@ -718,6 +718,16 @@ namespace JSC {
     {
     }
 
+    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 @@ namespace JSC {
     {
     }
 
+    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 @@ namespace JSC {
     {
     }
 
+    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)
index 22df5ae..b515604 100644 (file)
@@ -68,6 +68,9 @@ namespace JSC {
         BitOrEq,
         ModEq,
         PowEq,
+        NullishEq,
+        OrEq,
+        AndEq,
         LShift,
         RShift,
         URShift
@@ -1389,6 +1392,19 @@ namespace JSC {
         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 @@ namespace JSC {
         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 @@ namespace JSC {
         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);
index dd251f7..976757b 100644 (file)
@@ -3847,7 +3847,7 @@ template <typename TreeBuilder> TreeExpression Parser<LexerType>::parseAssignmen
 
     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 @@ template <typename TreeBuilder> TreeExpression Parser<LexerType>::parseAssignmen
         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 @@ template <typename TreeBuilder> TreeExpression Parser<LexerType>::parseAssignmen
         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;
         }
index 48181ae..d1c47ef 100644 (file)
@@ -134,6 +134,9 @@ enum JSTokenType {
     BITANDEQUAL,
     BITXOREQUAL,
     BITOREQUAL,
+    NULLISHEQUAL,
+    OREQUAL,
+    ANDEQUAL,
     DOTDOTDOT,
     ARROWFUNCTION,
     QUESTIONDOT,
index 99694d0..c28bfc3 100644 (file)
@@ -500,6 +500,7 @@ constexpr bool enableWebAssemblyStreamingApi = false;
     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 {
index b63bc6d..d5b261f 100644 (file)
@@ -1,3 +1,12 @@
+2020-04-15  Devin Rousso  <drousso@apple.com>
+
+        [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  <jer.noble@apple.com>
 
         WKTR always enables capturing audio/video in GPUProcess
index 7bda617..c4e08f0 100755 (executable)
@@ -787,6 +787,10 @@ def runMiniMode(*optionalTestSpecificOptions)
     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