From 005371f3fb972ec12ca6af0054008982cf6fbb48 Mon Sep 17 00:00:00 2001 From: "andrea.bergia" Date: Fri, 18 Oct 2024 12:40:54 +0200 Subject: [PATCH] Implement nullish assignment operator `??=` --- .../org/mozilla/javascript/IRFactory.java | 3 +++ .../java/org/mozilla/javascript/Node.java | 2 ++ .../java/org/mozilla/javascript/Token.java | 7 +++++-- .../org/mozilla/javascript/TokenStream.java | 3 +++ .../org/mozilla/javascript/ast/AstNode.java | 3 +++ .../tests/NullishCoalescingOpTest.java | 21 +++++++++++++++++++ tests/testsrc/test262.properties | 16 ++++---------- 7 files changed, 41 insertions(+), 14 deletions(-) diff --git a/rhino/src/main/java/org/mozilla/javascript/IRFactory.java b/rhino/src/main/java/org/mozilla/javascript/IRFactory.java index 62b30a5dcc..a6ab06aea9 100644 --- a/rhino/src/main/java/org/mozilla/javascript/IRFactory.java +++ b/rhino/src/main/java/org/mozilla/javascript/IRFactory.java @@ -2150,6 +2150,9 @@ private Node createAssignment(int assignType, Node left, Node right) { case Token.ASSIGN_EXP: assignOp = Token.EXP; break; + case Token.ASSIGN_NULLISH: + assignOp = Token.NULLISH_COALESCING; + break; default: throw Kit.codeBug(); } diff --git a/rhino/src/main/java/org/mozilla/javascript/Node.java b/rhino/src/main/java/org/mozilla/javascript/Node.java index 0b4d83660a..835ad96ebc 100644 --- a/rhino/src/main/java/org/mozilla/javascript/Node.java +++ b/rhino/src/main/java/org/mozilla/javascript/Node.java @@ -943,6 +943,8 @@ public boolean hasSideEffects() { case Token.ASSIGN_LSH: case Token.ASSIGN_RSH: case Token.ASSIGN_URSH: + case Token.ASSIGN_EXP: + case Token.ASSIGN_NULLISH: case Token.ENTERWITH: case Token.LEAVEWITH: case Token.RETURN: diff --git a/rhino/src/main/java/org/mozilla/javascript/Token.java b/rhino/src/main/java/org/mozilla/javascript/Token.java index 986676a954..80bbc58384 100644 --- a/rhino/src/main/java/org/mozilla/javascript/Token.java +++ b/rhino/src/main/java/org/mozilla/javascript/Token.java @@ -148,9 +148,10 @@ public static enum CommentType { ASSIGN_MUL = ASSIGN_SUB + 1, // *= ASSIGN_DIV = ASSIGN_MUL + 1, // /= ASSIGN_MOD = ASSIGN_DIV + 1, // %= - ASSIGN_EXP = ASSIGN_MOD + 1; // **= + ASSIGN_EXP = ASSIGN_MOD + 1, // **= + ASSIGN_NULLISH = ASSIGN_EXP + 1; // ??= public static final int FIRST_ASSIGN = ASSIGN, - LAST_ASSIGN = ASSIGN_EXP, + LAST_ASSIGN = ASSIGN_NULLISH, HOOK = LAST_ASSIGN + 1, // conditional (?:) COLON = HOOK + 1, OR = COLON + 1, // logical or (||) @@ -468,6 +469,8 @@ public static String typeToName(int token) { return "ASSIGN_MOD"; case ASSIGN_EXP: return "ASSIGN_EXP"; + case ASSIGN_NULLISH: + return "ASSIGN_NULLISH"; case HOOK: return "HOOK"; case COLON: diff --git a/rhino/src/main/java/org/mozilla/javascript/TokenStream.java b/rhino/src/main/java/org/mozilla/javascript/TokenStream.java index 2ea0a016d5..07a51ac940 100644 --- a/rhino/src/main/java/org/mozilla/javascript/TokenStream.java +++ b/rhino/src/main/java/org/mozilla/javascript/TokenStream.java @@ -1156,6 +1156,9 @@ && peekChar() == '!' } ungetChar('.'); } else if (matchChar('?')) { + if (matchChar('=')) { + return Token.ASSIGN_NULLISH; + } return Token.NULLISH_COALESCING; } } diff --git a/rhino/src/main/java/org/mozilla/javascript/ast/AstNode.java b/rhino/src/main/java/org/mozilla/javascript/ast/AstNode.java index 7eb7f237f9..a7d4284844 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ast/AstNode.java +++ b/rhino/src/main/java/org/mozilla/javascript/ast/AstNode.java @@ -126,6 +126,7 @@ public abstract class AstNode extends Node implements Comparable { operatorNames.put(Token.ASSIGN_MOD, "%="); operatorNames.put(Token.ASSIGN_BITXOR, "^="); operatorNames.put(Token.ASSIGN_EXP, "**="); + operatorNames.put(Token.ASSIGN_NULLISH, "??="); operatorNames.put(Token.VOID, "void"); StringBuilder sb = new StringBuilder(); @@ -374,6 +375,8 @@ public boolean hasSideEffects() { case Token.ASSIGN_RSH: case Token.ASSIGN_SUB: case Token.ASSIGN_URSH: + case Token.ASSIGN_EXP: + case Token.ASSIGN_NULLISH: case Token.BLOCK: case Token.BREAK: case Token.CALL: diff --git a/tests/src/test/java/org/mozilla/javascript/tests/NullishCoalescingOpTest.java b/tests/src/test/java/org/mozilla/javascript/tests/NullishCoalescingOpTest.java index 18825dd68e..4c544c3f6f 100644 --- a/tests/src/test/java/org/mozilla/javascript/tests/NullishCoalescingOpTest.java +++ b/tests/src/test/java/org/mozilla/javascript/tests/NullishCoalescingOpTest.java @@ -72,4 +72,25 @@ public void testNullishCoalescingDoesNotLeakVariables() { String script = "$0 = false; true ?? true; $0"; Utils.assertWithAllOptimizationLevelsES6(false, script); } + + @Test + public void testNullishAssignmentRequiresES6() { + Utils.runWithAllOptimizationLevels( + cx -> { + Scriptable scope = cx.initStandardObjects(); + assertThrows( + EvaluatorException.class, + () -> + cx.evaluateString( + scope, "a = true; a ??= false", "test.js", 0, null)); + return null; + }); + } + + @Test + public void testNullishAssignment() { + Utils.assertWithAllOptimizationLevelsES6(true, "a = true; a ??= false; a"); + Utils.assertWithAllOptimizationLevelsES6(false, "a = undefined; a ??= false; a"); + Utils.assertWithAllOptimizationLevelsES6(false, "a = null; a ??= false; a"); + } } diff --git a/tests/testsrc/test262.properties b/tests/testsrc/test262.properties index 5ad0c3737a..b75a9fd5d3 100644 --- a/tests/testsrc/test262.properties +++ b/tests/testsrc/test262.properties @@ -4942,7 +4942,7 @@ language/expressions/less-than-or-equal 0/47 (0.0%) language/expressions/logical-and 1/18 (5.56%) tco-right.js {unsupported: [tail-call-optimization]} -language/expressions/logical-assignment 53/78 (67.95%) +language/expressions/logical-assignment 46/78 (58.97%) left-hand-side-private-reference-accessor-property-and.js {unsupported: [class-fields-private]} left-hand-side-private-reference-accessor-property-nullish.js {unsupported: [class-fields-private]} left-hand-side-private-reference-accessor-property-or.js {unsupported: [class-fields-private]} @@ -4973,21 +4973,14 @@ language/expressions/logical-assignment 53/78 (67.95%) lgcl-and-assignment-operator-non-extensible.js strict lgcl-and-assignment-operator-non-simple-lhs.js lgcl-and-assignment-operator-non-writeable.js strict - lgcl-nullish-assignment-operator.js - lgcl-nullish-assignment-operator-bigint.js + lgcl-nullish-arguments-strict.js strict lgcl-nullish-assignment-operator-lhs-before-rhs.js lgcl-nullish-assignment-operator-namedevaluation-arrow-function.js lgcl-nullish-assignment-operator-namedevaluation-class-expression.js lgcl-nullish-assignment-operator-namedevaluation-function.js - lgcl-nullish-assignment-operator-no-set.js strict lgcl-nullish-assignment-operator-no-set-put.js strict - lgcl-nullish-assignment-operator-non-extensible.js strict + lgcl-nullish-assignment-operator-non-simple-lhs.js lgcl-nullish-assignment-operator-non-writeable.js strict - lgcl-nullish-assignment-operator-non-writeable-put.js strict - lgcl-nullish-assignment-operator-unresolved-lhs.js strict - lgcl-nullish-assignment-operator-unresolved-rhs.js - lgcl-nullish-assignment-operator-unresolved-rhs-put.js - lgcl-nullish-whitespace.js lgcl-or-arguments-strict.js strict lgcl-or-assignment-operator-lhs-before-rhs.js lgcl-or-assignment-operator-namedevaluation-arrow-function.js @@ -5053,7 +5046,7 @@ language/expressions/new 41/59 (69.49%) ~language/expressions/new.target -language/expressions/object 809/1169 (69.2%) +language/expressions/object 808/1169 (69.12%) dstr/async-gen-meth-ary-init-iter-close.js {unsupported: [async-iteration, async]} dstr/async-gen-meth-ary-init-iter-get-err.js {unsupported: [async-iteration]} dstr/async-gen-meth-ary-init-iter-get-err-array-prototype.js {unsupported: [async-iteration]} @@ -5806,7 +5799,6 @@ language/expressions/object 809/1169 (69.2%) accessor-name-literal-numeric-octal.js {strict: [-1], non-strict: [-1]} computed-__proto__.js concise-generator.js - cpn-obj-lit-computed-property-name-from-assignment-expression-coalesce.js cpn-obj-lit-computed-property-name-from-async-arrow-function-expression.js cpn-obj-lit-computed-property-name-from-await-expression.js {unsupported: [module, async]} cpn-obj-lit-computed-property-name-from-yield-expression.js