Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FEAT: Implement ?? #1591

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions rhino/src/main/java/org/mozilla/javascript/IRFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -2038,6 +2038,27 @@ private static Node createBinary(int nodeType, Node left, Node right) {
}
break;
}

case Token.NULLISH_COALESCING:
{
// foo ?? default =>
// (foo == undefined || foo == null) ? foo (left) : default (right)

Node undefinedNode = new Name(0, "undefined");
Node nullNode = new Node(Token.NULL);

Node conditional =
new Node(
Token.OR,
new Node(Token.SHEQ, nullNode, left),
new Node(Token.SHEQ, undefinedNode, left));

return new Node(
Token.HOOK,
/* left= */ conditional,
/* mid= */ right,
/* right= */ left);
}
}

return new Node(nodeType, left, right);
Expand Down
21 changes: 20 additions & 1 deletion rhino/src/main/java/org/mozilla/javascript/Parser.java
Original file line number Diff line number Diff line change
Expand Up @@ -2370,7 +2370,7 @@ private AstNode assignExpr() throws IOException {
}

private AstNode condExpr() throws IOException {
AstNode pn = orExpr();
AstNode pn = nullishCoalescingExpr();
if (matchToken(Token.HOOK, true)) {
int line = ts.lineno;
int qmarkPos = ts.tokenBeg, colonPos = -1;
Expand Down Expand Up @@ -2402,6 +2402,25 @@ private AstNode condExpr() throws IOException {
return pn;
}

private AstNode nullishCoalescingExpr() throws IOException {
AstNode pn = orExpr();
if (matchToken(Token.NULLISH_COALESCING, true)) {
int opPos = ts.tokenBeg;
AstNode rn = nullishCoalescingExpr();

// Cannot immediately contain, or be contained within, an && or || operation.
if (pn.getType() == Token.OR
|| pn.getType() == Token.AND
|| rn.getType() == Token.OR
|| rn.getType() == Token.AND) {
reportError("msg.nullish.bad.token");
}

pn = new InfixExpression(Token.NULLISH_COALESCING, pn, rn, opPos);
}
return pn;
}

private AstNode orExpr() throws IOException {
AstNode pn = andExpr();
if (matchToken(Token.OR, true)) {
Expand Down
5 changes: 4 additions & 1 deletion rhino/src/main/java/org/mozilla/javascript/Token.java
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,8 @@ public static enum CommentType {
TEMPLATE_LITERAL_SUBST = 175, // template literal - substitution
TAGGED_TEMPLATE_LITERAL = 176, // template literal - tagged/handler
DOTDOTDOT = 177, // spread/rest ...
LAST_TOKEN = 177;
NULLISH_COALESCING = 178, // nullish coalescing (??)
LAST_TOKEN = 178;

/**
* Returns a name for the token. If Rhino is compiled with certain hardcoded debugging flags in
Expand Down Expand Up @@ -468,6 +469,8 @@ public static String typeToName(int token) {
return "COLON";
case OR:
return "OR";
case NULLISH_COALESCING:
return "NULLISH_COALESCING";
case AND:
return "AND";
case INC:
Expand Down
3 changes: 3 additions & 0 deletions rhino/src/main/java/org/mozilla/javascript/TokenStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -1146,6 +1146,9 @@ && peekChar() == '!'
case ',':
return Token.COMMA;
case '?':
if (matchChar('?')) {
return Token.NULLISH_COALESCING;
}
return Token.HOOK;
case ':':
if (matchChar(':')) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ public abstract class AstNode extends Node implements Comparable<AstNode> {
operatorNames.put(Token.COMMA, ",");
operatorNames.put(Token.COLON, ":");
operatorNames.put(Token.OR, "||");
operatorNames.put(Token.NULLISH_COALESCING, "??");
operatorNames.put(Token.AND, "&&");
operatorNames.put(Token.INC, "++");
operatorNames.put(Token.DEC, "--");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ msg.bad.yield =\
msg.yield.parenthesized =\
yield expression must be parenthesized.

msg.nullish.bad.token =\
Syntax Error: Unexpected token.

# NativeGlobal
msg.cant.call.indirect =\
Function "{0}" must be called directly, and not by way of a \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ msg.bad.yield =\
msg.yield.parenthesized =\
L''expression suivant ''yield'' doit \u00eatre entre parenth\u00e8ses.

msg.nullish.bad.token =\
Erreur de syntaxe: Jeton inattendu.

# NativeGlobal
msg.cant.call.indirect =\
La fonction "{0}" doit \u00EAtre appel\u00E9e directement et non par l''interm\u00E9diaire \
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package org.mozilla.javascript.tests;

import org.junit.Assert;
import org.junit.Test;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Scriptable;

public class NullishCoalescingOpTest {

@Test
public void testNullishColascingBasic() {
Utils.runWithAllOptimizationLevels(
cx -> {
Scriptable scope = cx.initStandardObjects();
cx.setLanguageVersion(Context.VERSION_ES6);

String script = "null ?? 'default string'";
Assert.assertEquals(
"default string",
cx.evaluateString(scope, script, "nullish coalescing basic", 0, null));

String script2 = "undefined ?? 'default string'";
Assert.assertEquals(
"default string",
cx.evaluateString(scope, script2, "nullish coalescing basic", 0, null));
return null;
});
}

@Test
public void testNullishColascingShortCircuit() {
String script = "0 || 0 ?? true";
Utils.assertEvaluatorExceptionES6("Syntax Error: Unexpected token. (test#1)", script);

String script2 = "0 && 0 ?? true";
Utils.assertEvaluatorExceptionES6("Syntax Error: Unexpected token. (test#1)", script2);

String script3 = "0 ?? 0 && true;";
Utils.assertEvaluatorExceptionES6("Syntax Error: Unexpected token. (test#1)", script3);

String script4 = "0 ?? 0 || true;";
Utils.assertEvaluatorExceptionES6("Syntax Error: Unexpected token. (test#1)", script4);
}

@Test
public void testNullishColascingPrecedence() {
Utils.runWithAllOptimizationLevels(
cx -> {
Scriptable scope = cx.initStandardObjects();
cx.setLanguageVersion(Context.VERSION_ES6);

String script1 = "3 == 3 ? 'yes' ?? 'default string' : 'no'";
Assert.assertEquals(
"yes",
cx.evaluateString(scope, script1, "nullish coalescing basic", 0, null));
return null;
});
}
}
10 changes: 5 additions & 5 deletions tests/testsrc/test262.properties
Original file line number Diff line number Diff line change
Expand Up @@ -4470,7 +4470,9 @@ language/expressions/call 60/92 (65.22%)

~language/expressions/class

~language/expressions/coalesce
language/expressions/coalesce 2/24 (8.33%)
tco-pos-null.js {unsupported: [tail-call-optimization]}
tco-pos-undefined.js {unsupported: [tail-call-optimization]}

language/expressions/comma 1/6 (16.67%)
tco-final.js {unsupported: [tail-call-optimization]}
Expand Down Expand Up @@ -4616,8 +4618,7 @@ language/expressions/compound-assignment 137/454 (30.18%)

language/expressions/concatenation 0/5 (0.0%)

language/expressions/conditional 3/22 (13.64%)
coalesce-expr-ternary.js
language/expressions/conditional 2/22 (9.09%)
tco-cond.js {unsupported: [tail-call-optimization]}
tco-pos.js {unsupported: [tail-call-optimization]}

Expand Down Expand Up @@ -5271,7 +5272,7 @@ language/expressions/new 41/59 (69.49%)

~language/expressions/new.target

language/expressions/object 864/1169 (73.91%)
language/expressions/object 865/1169 (73.99%)
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]}
Expand Down Expand Up @@ -6077,7 +6078,6 @@ language/expressions/object 864/1169 (73.91%)
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-expression-coalesce.js
cpn-obj-lit-computed-property-name-from-yield-expression.js
fn-name-accessor-get.js
fn-name-accessor-set.js
Expand Down
Loading