summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--devtools/server/main.js4
-rw-r--r--js/src/builtin/ReflectParse.cpp44
-rw-r--r--js/src/frontend/BytecodeEmitter.cpp2
-rw-r--r--js/src/frontend/FoldConstants.cpp46
-rw-r--r--js/src/frontend/NameFunctions.cpp1
-rw-r--r--js/src/frontend/ParseNode.cpp1
-rw-r--r--js/src/frontend/ParseNode.h19
-rw-r--r--js/src/frontend/Parser.cpp110
-rw-r--r--js/src/frontend/TokenKind.h12
-rw-r--r--js/src/frontend/TokenStream.cpp6
-rw-r--r--js/src/jit/BaselineCompiler.cpp19
-rw-r--r--js/src/jit/BaselineCompiler.h1
-rw-r--r--js/src/jit/CodeGenerator.cpp18
-rw-r--r--js/src/jit/CodeGenerator.h1
-rw-r--r--js/src/jit/IonBuilder.cpp40
-rw-r--r--js/src/jit/IonBuilder.h8
-rw-r--r--js/src/jit/Lowering.cpp10
-rw-r--r--js/src/jit/Lowering.h1
-rw-r--r--js/src/jit/MIR.h24
-rw-r--r--js/src/jit/MOpcodes.h1
-rw-r--r--js/src/jit/shared/LIR-shared.h15
-rw-r--r--js/src/jit/shared/LOpcodes-shared.h1
-rw-r--r--js/src/js.msg1
-rw-r--r--js/src/jsopcodeinlines.h1
-rw-r--r--js/src/tests/js1_8_5/reflect-parse/expression.js35
-rw-r--r--js/src/tests/non262/expressions/nullish-coalescing.js112
-rw-r--r--js/src/tests/test262/language/expressions/coalesce/abrupt-is-a-short-circuit.js60
-rw-r--r--js/src/tests/test262/language/expressions/coalesce/browser.js0
-rw-r--r--js/src/tests/test262/language/expressions/coalesce/cannot-chain-head-with-logical-and.js30
-rw-r--r--js/src/tests/test262/language/expressions/coalesce/cannot-chain-head-with-logical-or.js30
-rw-r--r--js/src/tests/test262/language/expressions/coalesce/cannot-chain-tail-with-logical-and.js31
-rw-r--r--js/src/tests/test262/language/expressions/coalesce/cannot-chain-tail-with-logical-or.js31
-rw-r--r--js/src/tests/test262/language/expressions/coalesce/chainable-if-parenthesis-covered-logical-and.js55
-rw-r--r--js/src/tests/test262/language/expressions/coalesce/chainable-if-parenthesis-covered-logical-or.js63
-rw-r--r--js/src/tests/test262/language/expressions/coalesce/chainable-with-bitwise-and.js51
-rw-r--r--js/src/tests/test262/language/expressions/coalesce/chainable-with-bitwise-or.js51
-rw-r--r--js/src/tests/test262/language/expressions/coalesce/chainable-with-bitwise-xor.js51
-rw-r--r--js/src/tests/test262/language/expressions/coalesce/chainable.js52
-rw-r--r--js/src/tests/test262/language/expressions/coalesce/follows-null.js51
-rw-r--r--js/src/tests/test262/language/expressions/coalesce/follows-undefined.js51
-rw-r--r--js/src/tests/test262/language/expressions/coalesce/shell.js0
-rw-r--r--js/src/tests/test262/language/expressions/coalesce/short-circuit-number-0.js83
-rw-r--r--js/src/tests/test262/language/expressions/coalesce/short-circuit-number-42.js83
-rw-r--r--js/src/tests/test262/language/expressions/coalesce/short-circuit-number-empty-string.js84
-rw-r--r--js/src/tests/test262/language/expressions/coalesce/short-circuit-number-false.js83
-rw-r--r--js/src/tests/test262/language/expressions/coalesce/short-circuit-number-object.js91
-rw-r--r--js/src/tests/test262/language/expressions/coalesce/short-circuit-number-string.js84
-rw-r--r--js/src/tests/test262/language/expressions/coalesce/short-circuit-number-symbol.js84
-rw-r--r--js/src/tests/test262/language/expressions/coalesce/short-circuit-number-true.js83
-rw-r--r--js/src/tests/test262/language/expressions/coalesce/short-circuit-prevents-evaluation.js58
-rw-r--r--js/src/tests/test262/language/expressions/coalesce/tco-pos-null-strict.js30
-rw-r--r--js/src/tests/test262/language/expressions/coalesce/tco-pos-undefined-strict.js30
-rw-r--r--js/src/tests/test262/language/expressions/conditional/browser.js0
-rw-r--r--js/src/tests/test262/language/expressions/conditional/coalesce-expr-ternary.js75
-rw-r--r--js/src/tests/test262/language/expressions/conditional/shell.js0
-rw-r--r--js/src/vm/Interpreter.cpp10
-rw-r--r--js/src/vm/Opcodes.h16
57 files changed, 1949 insertions, 85 deletions
diff --git a/devtools/server/main.js b/devtools/server/main.js
index ac76adb831..4af1381896 100644
--- a/devtools/server/main.js
+++ b/devtools/server/main.js
@@ -1462,7 +1462,9 @@ DebuggerServerConnection.prototype = {
},
send(packet) {
- this.transport.send(packet);
+ if (this.transport) {
+ this.transport.send(packet);
+ }
},
/**
diff --git a/js/src/builtin/ReflectParse.cpp b/js/src/builtin/ReflectParse.cpp
index c4a5e81389..48437a1f1d 100644
--- a/js/src/builtin/ReflectParse.cpp
+++ b/js/src/builtin/ReflectParse.cpp
@@ -77,7 +77,7 @@ enum BinaryOperator {
/* binary */
BINOP_BITOR, BINOP_BITXOR, BINOP_BITAND,
/* misc */
- BINOP_IN, BINOP_INSTANCEOF,
+ BINOP_IN, BINOP_INSTANCEOF, BINOP_COALESCE,
BINOP_LIMIT
};
@@ -153,6 +153,7 @@ static const char* const binopNames[] = {
"&", /* BINOP_BITAND */
"in", /* BINOP_IN */
"instanceof", /* BINOP_INSTANCEOF */
+ "??" /* BINOP_COALESCE */
};
static const char* const unopNames[] = {
@@ -564,7 +565,7 @@ class NodeBuilder
MOZ_MUST_USE bool updateExpression(HandleValue expr, bool incr, bool prefix, TokenPos* pos,
MutableHandleValue dst);
- MOZ_MUST_USE bool logicalExpression(bool lor, HandleValue left, HandleValue right, TokenPos* pos,
+ MOZ_MUST_USE bool logicalExpression(ParseNodeKind kind, HandleValue left, HandleValue right, TokenPos* pos,
MutableHandleValue dst);
MOZ_MUST_USE bool conditionalExpression(HandleValue test, HandleValue cons, HandleValue alt,
@@ -1086,12 +1087,35 @@ NodeBuilder::updateExpression(HandleValue expr, bool incr, bool prefix, TokenPos
}
bool
-NodeBuilder::logicalExpression(bool lor, HandleValue left, HandleValue right, TokenPos* pos,
+NodeBuilder::logicalExpression(ParseNodeKind kind,
+ HandleValue left,
+ HandleValue right,
+ TokenPos* pos,
MutableHandleValue dst)
{
RootedValue opName(cx);
- if (!atomValue(lor ? "||" : "&&", &opName))
- return false;
+ switch (kind) {
+ case PNK_COALESCE:
+ if (!atomValue("??", &opName)) {
+ return false;
+ }
+ break;
+
+ case PNK_OR:
+ if (!atomValue("||", &opName)) {
+ return false;
+ }
+ break;
+
+ case PNK_AND:
+ if (!atomValue("&&", &opName)) {
+ return false;
+ }
+ break;
+
+ default:
+ LOCAL_NOT_REACHED("unexpected logical operator type");
+ }
RootedValue cb(cx, callbacks[AST_LOGICAL_EXPR]);
if (!cb.isNull())
@@ -1989,6 +2013,8 @@ ASTSerializer::binop(ParseNodeKind kind, JSOp op)
return BINOP_IN;
case PNK_INSTANCEOF:
return BINOP_INSTANCEOF;
+ case PNK_COALESCE:
+ return BINOP_COALESCE;
default:
return BINOP_ERR;
}
@@ -2648,8 +2674,9 @@ ASTSerializer::leftAssociate(ParseNode* pn, MutableHandleValue dst)
MOZ_ASSERT(pn->pn_count >= 1);
ParseNodeKind kind = pn->getKind();
- bool lor = kind == PNK_OR;
- bool logop = lor || (kind == PNK_AND);
+ bool logop = (kind == PNK_COALESCE ||
+ kind == PNK_OR ||
+ kind == PNK_AND);
ParseNode* head = pn->pn_head;
RootedValue left(cx);
@@ -2663,7 +2690,7 @@ ASTSerializer::leftAssociate(ParseNode* pn, MutableHandleValue dst)
TokenPos subpos(pn->pn_pos.begin, next->pn_pos.end);
if (logop) {
- if (!builder.logicalExpression(lor, left, right, &subpos, &left))
+ if (!builder.logicalExpression(kind, left, right, &subpos, &left))
return false;
} else {
BinaryOperator op = binop(pn->getKind(), pn->getOp());
@@ -2876,6 +2903,7 @@ ASTSerializer::expression(ParseNode* pn, MutableHandleValue dst)
builder.conditionalExpression(test, cons, alt, &pn->pn_pos, dst);
}
+ case PNK_COALESCE:
case PNK_OR:
case PNK_AND:
return leftAssociate(pn, dst);
diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp
index b5a96f32d9..e21ab72407 100644
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -3334,6 +3334,7 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer)
case PNK_CATCHLIST:
// Strict equality operations and logical operators are well-behaved and
// perform no conversions.
+ case PNK_COALESCE:
case PNK_OR:
case PNK_AND:
case PNK_STRICTEQ:
@@ -11355,6 +11356,7 @@ BytecodeEmitter::emitTree(ParseNode* pn, ValueUsage valueUsage /* = ValueUsage::
return false;
break;
+ case PNK_COALESCE:
case PNK_OR:
case PNK_AND:
if (!emitLogical(pn))
diff --git a/js/src/frontend/FoldConstants.cpp b/js/src/frontend/FoldConstants.cpp
index 979af29b42..c3354e44a4 100644
--- a/js/src/frontend/FoldConstants.cpp
+++ b/js/src/frontend/FoldConstants.cpp
@@ -326,6 +326,7 @@ ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result)
case PNK_POSTINCREMENT:
case PNK_PREDECREMENT:
case PNK_POSTDECREMENT:
+ case PNK_COALESCE:
case PNK_OR:
case PNK_AND:
case PNK_BITOR:
@@ -482,15 +483,19 @@ IsEffectless(ParseNode* node)
enum Truthiness { Truthy, Falsy, Unknown };
static Truthiness
-Boolish(ParseNode* pn)
+Boolish(ParseNode* pn, bool isNullish = false)
{
switch (pn->getKind()) {
- case PNK_NUMBER:
- return (pn->pn_dval != 0 && !IsNaN(pn->pn_dval)) ? Truthy : Falsy;
+ case PNK_NUMBER: {
+ bool isNonZeroNumber = (pn->pn_dval != 0 && !IsNaN(pn->pn_dval));
+ return (isNullish || isNonZeroNumber) ? Truthy : Falsy;
+ }
case PNK_STRING:
- case PNK_TEMPLATE_STRING:
- return (pn->pn_atom->length() > 0) ? Truthy : Falsy;
+ case PNK_TEMPLATE_STRING: {
+ bool isNonZeroLengthString = (pn->pn_atom->length() > 0);
+ return (isNullish || isNonZeroLengthString) ? Truthy : Falsy;
+ }
case PNK_TRUE:
case PNK_FUNCTION:
@@ -498,6 +503,8 @@ Boolish(ParseNode* pn)
return Truthy;
case PNK_FALSE:
+ return isNullish ? Truthy : Falsy;
+
case PNK_NULL:
case PNK_RAW_UNDEFINED:
return Falsy;
@@ -752,21 +759,24 @@ FoldIncrementDecrement(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHa
}
static bool
-FoldAndOr(ExclusiveContext* cx, ParseNode** nodePtr, Parser<FullParseHandler>& parser,
+FoldLogical(ExclusiveContext* cx, ParseNode** nodePtr, Parser<FullParseHandler>& parser,
bool inGenexpLambda)
{
ParseNode* node = *nodePtr;
- MOZ_ASSERT(node->isKind(PNK_AND) || node->isKind(PNK_OR));
+ bool isCoalesceNode = node->isKind(PNK_COALESCE);
+ bool isOrNode = node->isKind(PNK_OR);
+ bool isAndNode = node->isKind(PNK_AND);
+
+ MOZ_ASSERT(isCoalesceNode || isOrNode || isAndNode);
MOZ_ASSERT(node->isArity(PN_LIST));
- bool isOrNode = node->isKind(PNK_OR);
ParseNode** elem = &node->pn_head;
do {
if (!Fold(cx, elem, parser, inGenexpLambda))
return false;
- Truthiness t = Boolish(*elem);
+ Truthiness t = Boolish(*elem, isCoalesceNode);
// If we don't know the constant-folded node's truthiness, we can't
// reduce this node with its surroundings. Continue folding any
@@ -776,11 +786,16 @@ FoldAndOr(ExclusiveContext* cx, ParseNode** nodePtr, Parser<FullParseHandler>& p
continue;
}
+ bool terminateEarly = (isOrNode && t == Truthy) ||
+ (isAndNode && t == Falsy) ||
+ (isCoalesceNode && t == Truthy);
+
// If the constant-folded node's truthiness will terminate the
- // condition -- `a || true || expr` or |b && false && expr| -- then
- // trailing nodes will never be evaluated. Truncate the list after
- // the known-truthiness node, as it's the overall result.
- if ((t == Truthy) == isOrNode) {
+ // condition -- `a || true || expr` or `b && false && expr` or
+ // `false ?? c ?? expr` -- then trailing nodes will never be
+ // evaluated. Truncate the list after the known-truthiness node,
+ // as it's the overall result.
+ if (terminateEarly) {
ParseNode* afterNext;
for (ParseNode* next = (*elem)->pn_next; next; next = afterNext) {
afterNext = next->pn_next;
@@ -795,8 +810,6 @@ FoldAndOr(ExclusiveContext* cx, ParseNode** nodePtr, Parser<FullParseHandler>& p
break;
}
- MOZ_ASSERT((t == Truthy) == !isOrNode);
-
// We've encountered a vacuous node that'll never short- circuit
// evaluation.
if ((*elem)->pn_next) {
@@ -1738,9 +1751,10 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>& parser, bo
return Fold(cx, &expr, parser, inGenexpLambda);
return true;
+ case PNK_COALESCE:
case PNK_AND:
case PNK_OR:
- return FoldAndOr(cx, pnp, parser, inGenexpLambda);
+ return FoldLogical(cx, pnp, parser, inGenexpLambda);
case PNK_FUNCTION:
return FoldFunction(cx, pn, parser, inGenexpLambda);
diff --git a/js/src/frontend/NameFunctions.cpp b/js/src/frontend/NameFunctions.cpp
index a36427d835..ebf83fb235 100644
--- a/js/src/frontend/NameFunctions.cpp
+++ b/js/src/frontend/NameFunctions.cpp
@@ -663,6 +663,7 @@ class NameResolver
break;
// Nodes with arbitrary-expression children.
+ case PNK_COALESCE:
case PNK_OR:
case PNK_AND:
case PNK_BITOR:
diff --git a/js/src/frontend/ParseNode.cpp b/js/src/frontend/ParseNode.cpp
index dfbd724e2e..0b6278c7eb 100644
--- a/js/src/frontend/ParseNode.cpp
+++ b/js/src/frontend/ParseNode.cpp
@@ -426,6 +426,7 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack)
}
// List nodes with all non-null children.
+ case PNK_COALESCE:
case PNK_OR:
case PNK_AND:
case PNK_BITOR:
diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h
index 0ad0fe0885..377eb999d9 100644
--- a/js/src/frontend/ParseNode.h
+++ b/js/src/frontend/ParseNode.h
@@ -130,8 +130,12 @@ class ObjectBox;
\
/* \
* Binary operators. \
- * These must be in the same order as TOK_OR and friends in TokenStream.h. \
+ * These must be in the same order in several places: \
+ * - the precedence table and JSOp code list in Parser.cpp \
+ * - the binary operators in TokenKind.h \
+ * - the first and last binary operator markers in ParseNode.h \
*/ \
+ F(COALESCE) \
F(OR) \
F(AND) \
F(BITOR) \
@@ -189,7 +193,13 @@ enum ParseNodeKind
FOR_EACH_PARSE_NODE_KIND(EMIT_ENUM)
#undef EMIT_ENUM
PNK_LIMIT, /* domain size */
- PNK_BINOP_FIRST = PNK_OR,
+ /*
+ * Binary operator markers.
+ * These must be in the same order in several places:
+ * - the precedence table and JSOp code list in Parser.cpp
+ * - the binary operators in ParseNode.h and TokenKind.h
+ */
+ PNK_BINOP_FIRST = PNK_COALESCE,
PNK_BINOP_LAST = PNK_POW,
PNK_ASSIGNMENT_START = PNK_ASSIGN,
PNK_ASSIGNMENT_LAST = PNK_POWASSIGN
@@ -329,8 +339,9 @@ IsTypeofKind(ParseNodeKind kind)
* PNK_POWASSIGN
* PNK_CONDITIONAL ternary (cond ? trueExpr : falseExpr)
* pn_kid1: cond, pn_kid2: then, pn_kid3: else
- * PNK_OR, list pn_head; list of pn_count subexpressions
- * PNK_AND, All of these operators are left-associative except (**).
+ * PNK_COALESCE, list pn_head; list of pn_count subexpressions
+ * PNK_OR, All of these operators are left-associative except (**).
+ * PNK_AND
* PNK_BITOR,
* PNK_BITXOR,
* PNK_BITAND,
diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp
index 5202b71545..a66183b4a8 100644
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -18,6 +18,7 @@
#include "frontend/Parser.h"
+#include "mozilla/ArrayUtils.h" // mozilla::ArrayLength
#include "mozilla/Sprintf.h"
#include <new>
@@ -46,6 +47,7 @@
using namespace js;
using namespace js::gc;
+using mozilla::ArrayLength;
using mozilla::Maybe;
using mozilla::Move;
using mozilla::Nothing;
@@ -7842,7 +7844,13 @@ Parser<ParseHandler>::expr(InHandling inHandling, YieldHandling yieldHandling,
return seq;
}
+/* These must be in the same order in several places:
+ * - the precedence table in Parser.cpp
+ * - the binary operators in ParseNode.h and TokenKind.h
+ * - the first and last binary operator markers in ParseNode.h
+ */
static const JSOp ParseNodeKindToJSOp[] = {
+ JSOP_COALESCE,
JSOP_OR,
JSOP_AND,
JSOP_BITOR,
@@ -7874,6 +7882,11 @@ BinaryOpParseNodeKindToJSOp(ParseNodeKind pnk)
{
MOZ_ASSERT(pnk >= PNK_BINOP_FIRST);
MOZ_ASSERT(pnk <= PNK_BINOP_LAST);
+#ifdef DEBUG
+ int jsopArraySize = ArrayLength(ParseNodeKindToJSOp);
+ int parseNodeKindListSize = PNK_BINOP_LAST - PNK_BINOP_FIRST + 1;
+ MOZ_ASSERT(jsopArraySize == parseNodeKindListSize);
+#endif
return ParseNodeKindToJSOp[pnk - PNK_BINOP_FIRST];
}
@@ -7884,34 +7897,39 @@ BinaryOpTokenKindToParseNodeKind(TokenKind tok)
return ParseNodeKind(PNK_BINOP_FIRST + (tok - TOK_BINOP_FIRST));
}
+/* These must be in the same order in several places:
+ * - the JSOp code list in Parser.cpp
+ * - the binary operators in ParseNode.h and TokenKind.h
+ */
static const int PrecedenceTable[] = {
- 1, /* PNK_OR */
- 2, /* PNK_AND */
- 3, /* PNK_BITOR */
- 4, /* PNK_BITXOR */
- 5, /* PNK_BITAND */
- 6, /* PNK_STRICTEQ */
- 6, /* PNK_EQ */
- 6, /* PNK_STRICTNE */
- 6, /* PNK_NE */
- 7, /* PNK_LT */
- 7, /* PNK_LE */
- 7, /* PNK_GT */
- 7, /* PNK_GE */
- 7, /* PNK_INSTANCEOF */
- 7, /* PNK_IN */
- 8, /* PNK_LSH */
- 8, /* PNK_RSH */
- 8, /* PNK_URSH */
- 9, /* PNK_ADD */
- 9, /* PNK_SUB */
- 10, /* PNK_STAR */
- 10, /* PNK_DIV */
- 10, /* PNK_MOD */
- 11 /* PNK_POW */
+ 1, /* PNK_COALESCE */
+ 2, /* PNK_OR */
+ 3, /* PNK_AND */
+ 4, /* PNK_BITOR */
+ 5, /* PNK_BITXOR */
+ 6, /* PNK_BITAND */
+ 7, /* PNK_STRICTEQ */
+ 7, /* PNK_EQ */
+ 7, /* PNK_STRICTNE */
+ 7, /* PNK_NE */
+ 8, /* PNK_LT */
+ 8, /* PNK_LE */
+ 8, /* PNK_GT */
+ 8, /* PNK_GE */
+ 8, /* PNK_INSTANCEOF */
+ 8, /* PNK_IN */
+ 9, /* PNK_LSH */
+ 9, /* PNK_RSH */
+ 9, /* PNK_URSH */
+ 10, /* PNK_ADD */
+ 10, /* PNK_SUB */
+ 11, /* PNK_STAR */
+ 11, /* PNK_DIV */
+ 11, /* PNK_MOD */
+ 12 /* PNK_POW */
};
-static const int PRECEDENCE_CLASSES = 11;
+static const int PRECEDENCE_CLASSES = 12;
static int
Precedence(ParseNodeKind pnk) {
@@ -7926,6 +7944,8 @@ Precedence(ParseNodeKind pnk) {
return PrecedenceTable[pnk - PNK_BINOP_FIRST];
}
+enum class EnforcedParentheses : uint8_t { CoalesceExpr, AndOrExpr, None };
+
template <typename ParseHandler>
MOZ_ALWAYS_INLINE typename ParseHandler::Node
Parser<ParseHandler>::orExpr1(InHandling inHandling, YieldHandling yieldHandling,
@@ -7942,6 +7962,7 @@ Parser<ParseHandler>::orExpr1(InHandling inHandling, YieldHandling yieldHandling
ParseNodeKind kindStack[PRECEDENCE_CLASSES];
int depth = 0;
Node pn;
+ EnforcedParentheses unparenthesizedExpression = EnforcedParentheses::None;
for (;;) {
pn = unaryExpr(yieldHandling, tripledotHandling, possibleError, invoked);
if (!pn)
@@ -7959,11 +7980,42 @@ Parser<ParseHandler>::orExpr1(InHandling inHandling, YieldHandling yieldHandling
// pending expression error now.
if (possibleError && !possibleError->checkForExpressionError())
return null();
- // Report an error for unary expressions on the LHS of **.
- if (tok == TOK_POW && handler.isUnparenthesizedUnaryExpression(pn)) {
- error(JSMSG_BAD_POW_LEFTSIDE);
- return null();
+
+ switch (tok) {
+ // Report an error for unary expressions on the LHS of **.
+ case TOK_POW:
+ if (handler.isUnparenthesizedUnaryExpression(pn)) {
+ error(JSMSG_BAD_POW_LEFTSIDE);
+ return null();
+ }
+ break;
+ case TOK_OR:
+ case TOK_AND:
+ // Report an error if ?? is on the LHS of the expression.
+ // Mixing other logical operators with the nullish coalescing
+ // operator is disallowed unless one expression is parenthesized.
+ if (unparenthesizedExpression == EnforcedParentheses::CoalesceExpr) {
+ error(JSMSG_BAD_COALESCE_MIXING);
+ return null();
+ }
+ // If we have not detected a mixing error at this point, record that
+ // we have an unparenthesized expression, in case we have one later.
+ unparenthesizedExpression = EnforcedParentheses::AndOrExpr;
+ break;
+ case TOK_COALESCE:
+ if (unparenthesizedExpression == EnforcedParentheses::AndOrExpr) {
+ error(JSMSG_BAD_COALESCE_MIXING);
+ return null();
+ }
+ // If we have not detected a mixing error at this point, record that
+ // we have an unparenthesized expression, in case we have one later.
+ unparenthesizedExpression = EnforcedParentheses::CoalesceExpr;
+ break;
+ default:
+ // Do nothing in other cases.
+ break;
}
+
pnk = BinaryOpTokenKindToParseNodeKind(tok);
} else {
tok = TOK_EOF;
diff --git a/js/src/frontend/TokenKind.h b/js/src/frontend/TokenKind.h
index 27da8ecf3e..f11ceda33e 100644
--- a/js/src/frontend/TokenKind.h
+++ b/js/src/frontend/TokenKind.h
@@ -152,11 +152,15 @@
* range-testing. \
*/ \
/* \
- * Binary operators tokens, TOK_OR thru TOK_POW. These must be in the same \
- * order as F(OR) and friends in FOR_EACH_PARSE_NODE_KIND in ParseNode.h. \
+ * Binary operators tokens. \
+ * These must be in the same order in several places: \
+ * - the precedence table and JSOp code list in Parser.cpp \
+ * - the binary operators in ParseNode.h \
+ * - the first and last binary operator markers in ParseNode.h \
*/ \
+ macro(COALESCE, "'\?\?'") /* escapes to avoid trigraphs warning */ \
+ range(BINOP_FIRST, COALESCE) \
macro(OR, "'||'") /* logical or */ \
- range(BINOP_FIRST, OR) \
macro(AND, "'&&'") /* logical and */ \
macro(BITOR, "'|'") /* bitwise-or */ \
macro(BITXOR, "'^'") /* bitwise-xor */ \
@@ -196,7 +200,7 @@
macro(DIV, "'/'") \
macro(MOD, "'%'") \
macro(POW, "'**'") \
- range(BINOP_LAST, POW) \
+ range(BINOP_LAST, POW) \
\
/* Unary operation tokens. */ \
macro(TYPEOF, "keyword 'typeof'") \
diff --git a/js/src/frontend/TokenStream.cpp b/js/src/frontend/TokenStream.cpp
index 8f9e206d9f..b464b23048 100644
--- a/js/src/frontend/TokenStream.cpp
+++ b/js/src/frontend/TokenStream.cpp
@@ -1260,8 +1260,8 @@ enum FirstCharKind {
LastCharKind = Other
};
-// OneChar: 40, 41, 44, 58, 59, 63, 91, 93, 123, 125, 126:
-// '(', ')', ',', ':', ';', '?', '[', ']', '{', '}', '~'
+// OneChar: 40, 41, 44, 58, 59, 91, 93, 123, 125, 126:
+// '(', ')', ',', ':', ';', '[', ']', '{', '}', '~'
// Ident: 36, 65..90, 95, 97..122: '$', 'A'..'Z', '_', 'a'..'z'
// Dot: 46: '.'
// Equals: 61: '='
@@ -1811,7 +1811,7 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier)
tp->type = TOK_OPTCHAIN;
}
} else {
- tp->type = TOK_HOOK;
+ tp->type = matchChar('?') ? TOK_COALESCE : TOK_HOOK;
}
goto out;
diff --git a/js/src/jit/BaselineCompiler.cpp b/js/src/jit/BaselineCompiler.cpp
index fd85ec00ed..53254718c0 100644
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -1253,6 +1253,25 @@ BaselineCompiler::emit_JSOP_IFNE()
}
bool
+BaselineCompiler::emit_JSOP_COALESCE() {
+ // COALESCE leaves the original value on the stack.
+ frame.syncStack(0);
+
+ masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R0);
+
+ Label undefinedOrNull;
+
+ masm.branchTestUndefined(Assembler::Equal, R0, &undefinedOrNull);
+ masm.branchTestNull(Assembler::Equal, R0, &undefinedOrNull);
+
+ jsbytecode* target = pc + GET_JUMP_OFFSET(pc);
+ masm.jump(labelOf(target));
+
+ masm.bind(&undefinedOrNull);
+ return true;
+}
+
+bool
BaselineCompiler::emitAndOr(bool branchIfTrue)
{
bool knownBoolean = frame.peek(-1)->isKnownBoolean();
diff --git a/js/src/jit/BaselineCompiler.h b/js/src/jit/BaselineCompiler.h
index f975162a6a..d3ab58aa4e 100644
--- a/js/src/jit/BaselineCompiler.h
+++ b/js/src/jit/BaselineCompiler.h
@@ -45,6 +45,7 @@ namespace jit {
_(JSOP_GOTO) \
_(JSOP_IFEQ) \
_(JSOP_IFNE) \
+ _(JSOP_COALESCE) \
_(JSOP_AND) \
_(JSOP_OR) \
_(JSOP_NOT) \
diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp
index ec3d35ff0b..66e8e25ddf 100644
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -11518,6 +11518,24 @@ CodeGenerator::visitIsObjectAndBranch(LIsObjectAndBranch* ins)
}
void
+CodeGenerator::visitIsNullOrUndefined(LIsNullOrUndefined* ins)
+{
+ Register output = ToRegister(ins->output());
+ ValueOperand value = ToValue(ins, LIsNullOrUndefined::Input);
+
+ Label isNotNull, done;
+ masm.branchTestNull(Assembler::NotEqual, value, &isNotNull);
+
+ masm.move32(Imm32(1), output);
+ masm.jump(&done);
+
+ masm.bind(&isNotNull);
+ masm.testUndefinedSet(Assembler::Equal, value, output);
+
+ masm.bind(&done);
+}
+
+void
CodeGenerator::loadOutermostJSScript(Register reg)
{
// The "outermost" JSScript means the script that we are compiling
diff --git a/js/src/jit/CodeGenerator.h b/js/src/jit/CodeGenerator.h
index b8aecc8ba8..ff09222f17 100644
--- a/js/src/jit/CodeGenerator.h
+++ b/js/src/jit/CodeGenerator.h
@@ -376,6 +376,7 @@ class CodeGenerator final : public CodeGeneratorSpecific
void visitOutOfLineIsConstructor(OutOfLineIsConstructor* ool);
void visitIsObject(LIsObject* lir);
void visitIsObjectAndBranch(LIsObjectAndBranch* lir);
+ void visitIsNullOrUndefined(LIsNullOrUndefined* ins);
void visitHasClass(LHasClass* lir);
void visitGuardToClass(LGuardToClass* lir);
void visitWasmParameter(LWasmParameter* lir);
diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp
index dbecec2a7e..62470a2b81 100644
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -276,10 +276,10 @@ IonBuilder::CFGState::IfElse(jsbytecode* trueEnd, jsbytecode* falseEnd, MTest* t
}
IonBuilder::CFGState
-IonBuilder::CFGState::AndOr(jsbytecode* join, MBasicBlock* lhs)
+IonBuilder::CFGState::Logical(jsbytecode* join, MBasicBlock* lhs)
{
CFGState state;
- state.state = AND_OR;
+ state.state = LOGICAL;
state.stopAt = join;
state.branch.ifFalse = lhs;
state.branch.test = nullptr;
@@ -1737,9 +1737,10 @@ IonBuilder::inspectOpcode(JSOp op)
case JSOP_TOSTRING:
return jsop_tostring();
+ case JSOP_COALESCE:
case JSOP_AND:
case JSOP_OR:
- return jsop_andor(op);
+ return jsop_logical(op);
case JSOP_DEFVAR:
return jsop_defvar(GET_UINT32_INDEX(pc));
@@ -2345,8 +2346,8 @@ IonBuilder::processCfgEntry(CFGState& state)
case CFGState::COND_SWITCH_BODY:
return processCondSwitchBody(state);
- case CFGState::AND_OR:
- return processAndOrEnd(state);
+ case CFGState::LOGICAL:
+ return processLogicalEnd(state);
case CFGState::LABEL:
return processLabelEnd(state);
@@ -2948,7 +2949,7 @@ IonBuilder::processNextTableSwitchCase(CFGState& state)
}
IonBuilder::ControlStatus
-IonBuilder::processAndOrEnd(CFGState& state)
+IonBuilder::processLogicalEnd(CFGState& state)
{
MOZ_ASSERT(current);
MBasicBlock* lhs = state.branch.ifFalse;
@@ -4413,9 +4414,9 @@ IonBuilder::processCondSwitchBody(CFGState& state)
}
bool
-IonBuilder::jsop_andor(JSOp op)
+IonBuilder::jsop_logical(JSOp op)
{
- MOZ_ASSERT(op == JSOP_AND || op == JSOP_OR);
+ MOZ_ASSERT(op == JSOP_AND || op == JSOP_OR || op == JSOP_COALESCE);
jsbytecode* rhsStart = pc + CodeSpec[op].length;
jsbytecode* joinStart = pc + GetJumpOffset(pc);
@@ -4429,9 +4430,24 @@ IonBuilder::jsop_andor(JSOp op)
if (!evalLhs || !evalRhs)
return false;
- MTest* test = (op == JSOP_AND)
- ? newTest(lhs, evalRhs, evalLhs)
- : newTest(lhs, evalLhs, evalRhs);
+ MTest* test;
+ switch (op) {
+ case JSOP_COALESCE: {
+ MIsNullOrUndefined* isNullOrUndefined =
+ MIsNullOrUndefined::New(alloc(), lhs);
+ current->add(isNullOrUndefined);
+ test = newTest(isNullOrUndefined, evalLhs, evalRhs);
+ break;
+ }
+
+ case JSOP_AND:
+ test = newTest(lhs, evalRhs, evalLhs);
+ break;
+
+ case JSOP_OR:
+ test = newTest(lhs, evalLhs, evalRhs);
+ break;
+ }
current->end(test);
// Create the lhs block and specialize.
@@ -4442,7 +4458,7 @@ IonBuilder::jsop_andor(JSOp op)
return false;
// Create the rhs block.
- if (!cfgStack_.append(CFGState::AndOr(joinStart, evalLhs)))
+ if (!cfgStack_.append(CFGState::Logical(joinStart, evalLhs)))
return false;
if (!setCurrentAndSpecializePhis(evalRhs))
diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h
index 402fbbf1af..a07020a4e8 100644
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -90,7 +90,7 @@ class IonBuilder
TABLE_SWITCH, // switch() { x }
COND_SWITCH_CASE, // switch() { case X: ... }
COND_SWITCH_BODY, // switch() { case ...: X }
- AND_OR, // && x, || x
+ LOGICAL, // && x, || x, ?? x
LABEL, // label: x
TRY // try { x } catch(e) { }
};
@@ -197,7 +197,7 @@ class IonBuilder
static CFGState If(jsbytecode* join, MTest* test);
static CFGState IfElse(jsbytecode* trueEnd, jsbytecode* falseEnd, MTest* test);
- static CFGState AndOr(jsbytecode* join, MBasicBlock* lhs);
+ static CFGState Logical(jsbytecode* join, MBasicBlock* lhs);
static CFGState TableSwitch(jsbytecode* exitpc, MTableSwitch* ins);
static CFGState CondSwitch(IonBuilder* builder, jsbytecode* exitpc, jsbytecode* defaultTarget);
static CFGState Label(jsbytecode* exitpc);
@@ -257,7 +257,7 @@ class IonBuilder
ControlStatus processCondSwitchBody(CFGState& state);
ControlStatus processSwitchBreak(JSOp op);
ControlStatus processSwitchEnd(DeferredEdge* breaks, jsbytecode* exitpc);
- ControlStatus processAndOrEnd(CFGState& state);
+ ControlStatus processLogicalEnd(CFGState& state);
ControlStatus processLabelEnd(CFGState& state);
ControlStatus processTryEnd(CFGState& state);
ControlStatus processReturn(JSOp op);
@@ -712,7 +712,7 @@ class IonBuilder
MOZ_MUST_USE bool jsop_try();
MOZ_MUST_USE bool jsop_label();
MOZ_MUST_USE bool jsop_condswitch();
- MOZ_MUST_USE bool jsop_andor(JSOp op);
+ MOZ_MUST_USE bool jsop_logical(JSOp op);
MOZ_MUST_USE bool jsop_dup2();
MOZ_MUST_USE bool jsop_loophead(jsbytecode* pc);
MOZ_MUST_USE bool jsop_compare(JSOp op);
diff --git a/js/src/jit/Lowering.cpp b/js/src/jit/Lowering.cpp
index 0347154675..f143ff9e04 100644
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -4163,6 +4163,16 @@ LIRGenerator::visitIsObject(MIsObject* ins)
}
void
+LIRGenerator::visitIsNullOrUndefined(MIsNullOrUndefined* ins)
+{
+ MDefinition* opd = ins->input();
+ MOZ_ASSERT(opd->type() == MIRType::Value);
+ LIsNullOrUndefined* lir =
+ new(alloc()) LIsNullOrUndefined(useBoxAtStart(opd));
+ define(lir, ins);
+}
+
+void
LIRGenerator::visitHasClass(MHasClass* ins)
{
MOZ_ASSERT(ins->object()->type() == MIRType::Object);
diff --git a/js/src/jit/Lowering.h b/js/src/jit/Lowering.h
index d5d0c3af20..5340fce762 100644
--- a/js/src/jit/Lowering.h
+++ b/js/src/jit/Lowering.h
@@ -288,6 +288,7 @@ class LIRGenerator : public LIRGeneratorSpecific
void visitIsCallable(MIsCallable* ins);
void visitIsConstructor(MIsConstructor* ins);
void visitIsObject(MIsObject* ins);
+ void visitIsNullOrUndefined(MIsNullOrUndefined* ins);
void visitHasClass(MHasClass* ins);
void visitGuardToClass(MGuardToClass* ins);
void visitWasmAddOffset(MWasmAddOffset* ins);
diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h
index a398ef3344..ea00529e09 100644
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -13156,6 +13156,30 @@ class MIsObject
return AliasSet::None();
}
};
+
+class MIsNullOrUndefined
+ : public MUnaryInstruction,
+ public BoxInputsPolicy::Data
+{
+ explicit MIsNullOrUndefined(MDefinition* object)
+ : MUnaryInstruction(object)
+ {
+ setResultType(MIRType::Boolean);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(IsNullOrUndefined)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object))
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+};
class MHasClass
: public MUnaryInstruction,
diff --git a/js/src/jit/MOpcodes.h b/js/src/jit/MOpcodes.h
index 3185d0a836..c1644ba8b3 100644
--- a/js/src/jit/MOpcodes.h
+++ b/js/src/jit/MOpcodes.h
@@ -271,6 +271,7 @@ namespace jit {
_(IsConstructor) \
_(IsCallable) \
_(IsObject) \
+ _(IsNullOrUndefined) \
_(HasClass) \
_(GuardToClass) \
_(CopySign) \
diff --git a/js/src/jit/shared/LIR-shared.h b/js/src/jit/shared/LIR-shared.h
index 62dc574b6c..f97dc1534f 100644
--- a/js/src/jit/shared/LIR-shared.h
+++ b/js/src/jit/shared/LIR-shared.h
@@ -7851,6 +7851,21 @@ class LIsObjectAndBranch : public LControlInstructionHelper<2, BOX_PIECES, 0>
}
};
+class LIsNullOrUndefined : public LInstructionHelper<1, BOX_PIECES, 0>
+{
+ public:
+ LIR_HEADER(IsNullOrUndefined);
+ static const size_t Input = 0;
+
+ explicit LIsNullOrUndefined(const LBoxAllocation& input) {
+ setBoxOperand(Input, input);
+ }
+
+ MIsNullOrUndefined* mir() const {
+ return mir_->toIsNullOrUndefined();
+ }
+};
+
class LHasClass : public LInstructionHelper<1, 1, 0>
{
public:
diff --git a/js/src/jit/shared/LOpcodes-shared.h b/js/src/jit/shared/LOpcodes-shared.h
index d0651af1d2..7f4cd770ac 100644
--- a/js/src/jit/shared/LOpcodes-shared.h
+++ b/js/src/jit/shared/LOpcodes-shared.h
@@ -385,6 +385,7 @@
_(IsConstructor) \
_(IsObject) \
_(IsObjectAndBranch) \
+ _(IsNullOrUndefined) \
_(HasClass) \
_(GuardToClass) \
_(RecompileCheck) \
diff --git a/js/src/js.msg b/js/src/js.msg
index 566b55479b..51854fc398 100644
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -189,6 +189,7 @@ MSG_DEF(JSMSG_AWAIT_IN_DEFAULT, 0, JSEXN_SYNTAXERR, "await can't be used
MSG_DEF(JSMSG_AWAIT_OUTSIDE_ASYNC, 0, JSEXN_SYNTAXERR, "await is only valid in async functions")
MSG_DEF(JSMSG_BAD_ARROW_ARGS, 0, JSEXN_SYNTAXERR, "invalid arrow-function arguments (parentheses around the arrow-function may help)")
MSG_DEF(JSMSG_BAD_BINDING, 1, JSEXN_SYNTAXERR, "redefining {0} is deprecated")
+MSG_DEF(JSMSG_BAD_COALESCE_MIXING, 0, JSEXN_SYNTAXERR, "cannot use `??` unparenthesized within `||` and `&&` expressions")
MSG_DEF(JSMSG_BAD_CONST_DECL, 0, JSEXN_SYNTAXERR, "missing = in const declaration")
MSG_DEF(JSMSG_BAD_CONTINUE, 0, JSEXN_SYNTAXERR, "continue must be inside loop")
MSG_DEF(JSMSG_BAD_DESTRUCT_ASS, 0, JSEXN_REFERENCEERR, "invalid destructuring assignment operator")
diff --git a/js/src/jsopcodeinlines.h b/js/src/jsopcodeinlines.h
index b7c1011079..a516df3a69 100644
--- a/js/src/jsopcodeinlines.h
+++ b/js/src/jsopcodeinlines.h
@@ -22,6 +22,7 @@ GetDefCount(JSScript* script, unsigned offset)
* in the pushed array of stack values for type inference.
*/
switch (JSOp(*pc)) {
+ case JSOP_COALESCE:
case JSOP_OR:
case JSOP_AND:
return 1;
diff --git a/js/src/tests/js1_8_5/reflect-parse/expression.js b/js/src/tests/js1_8_5/reflect-parse/expression.js
index 55d85a0293..6ee208915b 100644
--- a/js/src/tests/js1_8_5/reflect-parse/expression.js
+++ b/js/src/tests/js1_8_5/reflect-parse/expression.js
@@ -15,6 +15,31 @@ assertExpr("foo[bar]", memExpr(ident("foo"), ident("bar")));
assertExpr("foo['bar']", memExpr(ident("foo"), lit("bar")));
assertExpr("foo[42]", memExpr(ident("foo"), lit(42)));
+// optional member expressions
+assertExpr("foo?.bar", optExpr(optDotExpr(ident("foo"), ident("bar"))));
+assertExpr("foo?.bar.baz", optExpr(dotExpr(optDotExpr(ident("foo"), ident("bar")), ident("baz"))));
+assertExpr("foo.bar?.baz", optExpr(optDotExpr(dotExpr(ident("foo"), ident("bar")), ident("baz"))));
+assertExpr("foo?.bar?.baz", optExpr(optDotExpr(optDotExpr(ident("foo"), ident("bar")), ident("baz"))));
+assertExpr("foo?.[bar].baz", optExpr(dotExpr(optMemExpr(ident("foo"), ident("bar")), ident("baz"))));
+assertExpr("foo.bar?.[baz]", optExpr(optMemExpr(dotExpr(ident("foo"), ident("bar")), ident("baz"))));
+assertExpr("foo[bar]?.[baz]", optExpr(optMemExpr(memExpr(ident("foo"), ident("bar")), ident("baz"))));
+assertExpr("foo?.[bar][baz]", optExpr(memExpr(optMemExpr(ident("foo"), ident("bar")), ident("baz"))));
+assertExpr("foo?.['bar']?.['baz']", optExpr(optMemExpr(optMemExpr(ident("foo"), lit("bar")), lit("baz"))));
+assertExpr("foo?.[bar]?.baz", optExpr(optDotExpr(optMemExpr(ident("foo"), ident("bar")), ident("baz"))));
+
+// delete optional expressions
+assertExpr("\ndelete [] ?. [1]", delOptExpr(optMemExpr(arrExpr([]), lit(1))));
+assertExpr("delete foo?.bar", delOptExpr(optDotExpr(ident("foo"), ident("bar"))));
+assertExpr("delete foo?.bar.baz", delOptExpr(dotExpr(optDotExpr(ident("foo"), ident("bar")), ident("baz"))));
+assertExpr("delete foo.bar?.baz", delOptExpr(optDotExpr(dotExpr(ident("foo"), ident("bar")), ident("baz"))));
+assertExpr("delete foo?.bar?.baz", delOptExpr(optDotExpr(optDotExpr(ident("foo"), ident("bar")), ident("baz"))));
+assertExpr("delete foo?.[bar].baz", delOptExpr(dotExpr(optMemExpr(ident("foo"), ident("bar")), ident("baz"))));
+assertExpr("delete foo.bar?.[baz]", delOptExpr(optMemExpr(dotExpr(ident("foo"), ident("bar")), ident("baz"))));
+assertExpr("delete foo[bar]?.[baz]", delOptExpr(optMemExpr(memExpr(ident("foo"), ident("bar")), ident("baz"))));
+assertExpr("delete foo?.[bar][baz]", delOptExpr(memExpr(optMemExpr(ident("foo"), ident("bar")), ident("baz"))));
+assertExpr("delete foo?.['bar']?.['baz']", delOptExpr(optMemExpr(optMemExpr(ident("foo"), lit("bar")), lit("baz"))));
+assertExpr("delete foo?.[bar]?.baz", delOptExpr(optDotExpr(optMemExpr(ident("foo"), ident("bar")), ident("baz"))));
+
// function expressions
assertExpr("(function(){})", funExpr(null, [], blockStmt([])));
assertExpr("(function f() {})", funExpr(ident("f"), [], blockStmt([])));
@@ -86,6 +111,7 @@ assertExpr("(x &= y)", aExpr("&=", ident("x"), ident("y")));
// Conditional expressions
assertExpr("(x || y)", logExpr("||", ident("x"), ident("y")));
assertExpr("(x && y)", logExpr("&&", ident("x"), ident("y")));
+assertExpr("(x ?? y)", logExpr("??", ident("x"), ident("y")));
assertExpr("(w || x || y || z)", logExpr("||", logExpr("||", logExpr("||", ident("w"), ident("x")), ident("y")), ident("z")))
assertExpr("(x ? y : z)", condExpr(ident("x"), ident("y"), ident("z")));
@@ -103,6 +129,15 @@ assertExpr("(String())", callExpr(ident("String"), []));
assertExpr("(String(42))", callExpr(ident("String"), [lit(42)]));
assertExpr("(String(1,2,3))", callExpr(ident("String"), [lit(1),lit(2),lit(3)]));
+// Optional Call expressions
+assertExpr("(String?.())", optExpr(optCallExpr(ident("String"), [])));
+assertExpr("(String?.(42))", optExpr(optCallExpr(ident("String"), [lit(42)])));
+assertExpr("(String?.(1,2,3))", optExpr(optCallExpr(ident("String"), [lit(1),lit(2),lit(3)])));
+assertExpr("(String?.foo?.())", optExpr(optCallExpr(optDotExpr(ident("String"), ident("foo")), [])));
+assertExpr("(String.foo?.())", optExpr(optCallExpr(dotExpr(ident("String"), ident("foo")), [])));
+assertExpr("(String?.foo)()", callExpr(optExpr(optDotExpr(ident("String"), ident("foo"))), []));
+assertExpr("(String?.foo)?.()", optExpr(optCallExpr(optExpr(optDotExpr(ident("String"), ident("foo"))), [])));
+
// Array expressions
assertExpr("[]", arrExpr([]));
assertExpr("[1]", arrExpr([lit(1)]));
diff --git a/js/src/tests/non262/expressions/nullish-coalescing.js b/js/src/tests/non262/expressions/nullish-coalescing.js
new file mode 100644
index 0000000000..0ee491680a
--- /dev/null
+++ b/js/src/tests/non262/expressions/nullish-coalescing.js
@@ -0,0 +1,112 @@
+var BUGNUMBER = 1566141;
+var summary = "Implement the Nullish Coalescing operator (??) proposal";
+
+print(BUGNUMBER + ": " + summary);
+
+// These tests are originally from webkit.
+// webkit specifics have been removed and a test for `document.all` has
+// been added.
+function shouldBe(actual, expected) {
+ if (actual !== expected)
+ throw new Error(`expected ${expected} but got ${actual}`);
+}
+
+function shouldNotThrow(script) {
+ eval(script);
+}
+
+function shouldThrowSyntaxError(script) {
+ let error;
+ try {
+ eval(script);
+ } catch (e) {
+ error = e;
+ }
+
+ if (!(error instanceof SyntaxError))
+ throw new Error('Expected SyntaxError!');
+}
+
+function testBasicCases() {
+ shouldBe(undefined ?? 3, 3);
+ shouldBe(null ?? 3, 3);
+ shouldBe(true ?? 3, true);
+ shouldBe(false ?? 3, false);
+ shouldBe(0 ?? 3, 0);
+ shouldBe(1 ?? 3, 1);
+ shouldBe('' ?? 3, '');
+ shouldBe('hi' ?? 3, 'hi');
+ shouldBe(({} ?? 3) instanceof Object, true);
+ shouldBe(({ x: 'hi' } ?? 3).x, 'hi');
+ shouldBe(([] ?? 3) instanceof Array, true);
+ shouldBe((['hi'] ?? 3)[0], 'hi');
+ // test document.all, which has odd behavior
+ //shouldBe(typeof(createIsHTMLDDA() ?? 3), "undefined");
+}
+
+for (let i = 0; i < 1e5; i++)
+ testBasicCases();
+
+shouldBe(1 | null ?? 3, 1);
+shouldBe(1 ^ null ?? 3, 1);
+shouldBe(1 & null ?? 3, 0);
+shouldBe(3 == null ?? 3, false);
+shouldBe(3 != null ?? 3, true);
+shouldBe(3 === null ?? 3, false);
+shouldBe(3 !== null ?? 3, true);
+shouldBe(1 < null ?? 3, false);
+shouldBe(1 > null ?? 3, true);
+shouldBe(1 <= null ?? 3, false);
+shouldBe(1 >= null ?? 3, true);
+shouldBe(1 << null ?? 3, 1);
+shouldBe(1 >> null ?? 3, 1);
+shouldBe(1 >>> null ?? 3, 1);
+shouldBe(1 + null ?? 3, 1);
+shouldBe(1 - null ?? 3, 1);
+shouldBe(1 * null ?? 3, 0);
+shouldBe(1 / null ?? 3, Infinity);
+shouldBe(isNaN(1 % null ?? 3), true);
+shouldBe(1 ** null ?? 3, 1);
+shouldBe((void 0) ?? 3, 3);
+
+const obj = {
+ count: 0,
+ get x() { this.count++; return 'x'; }
+};
+false ?? obj.x;
+shouldBe(obj.count, 0);
+null ?? obj.x;
+shouldBe(obj.count, 1);
+obj.x ?? obj.x;
+shouldBe(obj.count, 2);
+
+shouldThrowSyntaxError('0 || 1 ?? 2');
+shouldThrowSyntaxError('0 && 1 ?? 2');
+shouldThrowSyntaxError('0 ?? 1 || 2');
+shouldThrowSyntaxError('0 ?? 1 && 2');
+shouldNotThrow('(0 || 1) ?? 2');
+shouldNotThrow('0 || (1 ?? 2)');
+shouldNotThrow('(0 && 1) ?? 2');
+shouldNotThrow('0 && (1 ?? 2)');
+shouldNotThrow('(0 ?? 1) || 2');
+shouldNotThrow('0 ?? (1 || 2)');
+shouldNotThrow('(0 ?? 1) && 2');
+shouldNotThrow('0 ?? (1 && 2)');
+
+shouldNotThrow('0 || 1 && 2 | 3 ^ 4 & 5 == 6 != 7 === 8 !== 9 < 0 > 1 <= 2 >= 3 << 4 >> 5 >>> 6 + 7 - 8 * 9 / 0 % 1 ** 2');
+shouldThrowSyntaxError('0 || 1 && 2 | 3 ^ 4 & 5 == 6 != 7 === 8 !== 9 < 0 > 1 <= 2 >= 3 << 4 >> 5 >>> 6 + 7 - 8 * 9 / 0 % 1 ** 2 ?? 3');
+shouldThrowSyntaxError('3 ?? 2 ** 1 % 0 / 9 * 8 - 7 + 6 >>> 5 >> 4 << 3 >= 2 <= 1 > 0 < 9 !== 8 === 7 != 6 == 5 & 4 ^ 3 | 2 && 1 || 0');
+
+shouldBe(null?.x ?? 3, 3);
+shouldBe(({})?.x ?? 3, 3);
+shouldBe(({ x: 0 })?.x ?? 3, 0);
+shouldBe(null?.() ?? 3, 3);
+shouldBe((() => 0)?.() ?? 3, 0);
+shouldBe(({ x: 0 })?.[null?.a ?? 'x'] ?? 3, 0);
+shouldBe((() => 0)?.(null?.a ?? 'x') ?? 3, 0);
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
+
+print("Tests complete");
+
diff --git a/js/src/tests/test262/language/expressions/coalesce/abrupt-is-a-short-circuit.js b/js/src/tests/test262/language/expressions/coalesce/abrupt-is-a-short-circuit.js
new file mode 100644
index 0000000000..fb04d2c0ac
--- /dev/null
+++ b/js/src/tests/test262/language/expressions/coalesce/abrupt-is-a-short-circuit.js
@@ -0,0 +1,60 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ Abrupt completions are also a Short circuit and prevent evaluation of the right-side expressions
+esid: sec-conditional-operator
+info: |
+ ConditionalExpression :
+ ShortCircuitExpression
+ ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+
+ ShortCircuitExpression :
+ LogicalORExpression
+ CoalesceExpression
+
+ CoalesceExpression :
+ CoalesceExpressionHead ?? BitwiseORExpression
+
+ CoalesceExpressionHead :
+ CoalesceExpression
+ BitwiseORExpression
+
+ Runtime Semantics: Evaluation
+
+ CoalesceExpression:CoalesceExpressionHead??BitwiseORExpression
+
+ 1. Let lref be the result of evaluating CoalesceExpressionHead.
+ 2. Let lval be ? GetValue(lref).
+ 3. If lval is undefined or null,
+ a. Let rref be the result of evaluating BitwiseORExpression.
+ b. Return ? GetValue(rref).
+ 4. Otherwise, return lval.
+features: [coalesce-expression]
+---*/
+
+var x;
+function poison() {
+ throw new Test262Error('poison handled');
+}
+
+function morePoison() {
+ throw 'poison!!!!';
+}
+
+x = undefined;
+assert.throws(Test262Error, function() {
+ undefined ?? poison() ?? morePoison();
+}, 'undefined ?? poison() ?? morePoison();');
+
+x = undefined;
+assert.throws(Test262Error, function() {
+ null ?? poison() ?? morePoison();
+}, 'null ?? poison() ?? morePoison();');
+
+assert.throws(Test262Error, function() {
+ poison() ?? morePoison();
+}, 'poison() ?? morePoison();');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/language/expressions/coalesce/browser.js b/js/src/tests/test262/language/expressions/coalesce/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/language/expressions/coalesce/browser.js
diff --git a/js/src/tests/test262/language/expressions/coalesce/cannot-chain-head-with-logical-and.js b/js/src/tests/test262/language/expressions/coalesce/cannot-chain-head-with-logical-and.js
new file mode 100644
index 0000000000..7d59d2aee2
--- /dev/null
+++ b/js/src/tests/test262/language/expressions/coalesce/cannot-chain-head-with-logical-and.js
@@ -0,0 +1,30 @@
+// |reftest| error:SyntaxError
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ Cannot immediately contain, or be contained within, an && or || operation.
+esid: sec-conditional-operator
+info: |
+ ConditionalExpression :
+ ShortCircuitExpression
+ ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+
+ ShortCircuitExpression :
+ LogicalORExpression
+ CoalesceExpression
+
+ CoalesceExpression :
+ CoalesceExpressionHead ?? BitwiseORExpression
+
+ CoalesceExpressionHead :
+ CoalesceExpression
+ BitwiseORExpression
+features: [coalesce-expression]
+negative:
+ phase: early
+ type: SyntaxError
+---*/
+
+0 && 0 ?? true; \ No newline at end of file
diff --git a/js/src/tests/test262/language/expressions/coalesce/cannot-chain-head-with-logical-or.js b/js/src/tests/test262/language/expressions/coalesce/cannot-chain-head-with-logical-or.js
new file mode 100644
index 0000000000..8086667f1b
--- /dev/null
+++ b/js/src/tests/test262/language/expressions/coalesce/cannot-chain-head-with-logical-or.js
@@ -0,0 +1,30 @@
+// |reftest| error:SyntaxError
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ Cannot immediately contain, or be contained within, an && or || operation.
+esid: sec-conditional-operator
+info: |
+ ConditionalExpression :
+ ShortCircuitExpression
+ ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+
+ ShortCircuitExpression :
+ LogicalORExpression
+ CoalesceExpression
+
+ CoalesceExpression :
+ CoalesceExpressionHead ?? BitwiseORExpression
+
+ CoalesceExpressionHead :
+ CoalesceExpression
+ BitwiseORExpression
+features: [coalesce-expression]
+negative:
+ phase: early
+ type: SyntaxError
+---*/
+
+0 || 0 ?? true; \ No newline at end of file
diff --git a/js/src/tests/test262/language/expressions/coalesce/cannot-chain-tail-with-logical-and.js b/js/src/tests/test262/language/expressions/coalesce/cannot-chain-tail-with-logical-and.js
new file mode 100644
index 0000000000..a85faef116
--- /dev/null
+++ b/js/src/tests/test262/language/expressions/coalesce/cannot-chain-tail-with-logical-and.js
@@ -0,0 +1,31 @@
+// |reftest| error:SyntaxError
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ If the CoalesceExpressionHead is undefined or null, follow return the right-side value.
+ Otherwise, return the left-side value.
+esid: sec-conditional-operator
+info: |
+ ConditionalExpression :
+ ShortCircuitExpression
+ ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+
+ ShortCircuitExpression :
+ LogicalORExpression
+ CoalesceExpression
+
+ CoalesceExpression :
+ CoalesceExpressionHead ?? BitwiseORExpression
+
+ CoalesceExpressionHead :
+ CoalesceExpression
+ BitwiseORExpression
+features: [coalesce-expression]
+negative:
+ phase: early
+ type: SyntaxError
+---*/
+
+0 ?? 0 && true; \ No newline at end of file
diff --git a/js/src/tests/test262/language/expressions/coalesce/cannot-chain-tail-with-logical-or.js b/js/src/tests/test262/language/expressions/coalesce/cannot-chain-tail-with-logical-or.js
new file mode 100644
index 0000000000..352a966215
--- /dev/null
+++ b/js/src/tests/test262/language/expressions/coalesce/cannot-chain-tail-with-logical-or.js
@@ -0,0 +1,31 @@
+// |reftest| error:SyntaxError
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ If the CoalesceExpressionHead is undefined or null, follow return the right-side value.
+ Otherwise, return the left-side value.
+esid: sec-conditional-operator
+info: |
+ ConditionalExpression :
+ ShortCircuitExpression
+ ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+
+ ShortCircuitExpression :
+ LogicalORExpression
+ CoalesceExpression
+
+ CoalesceExpression :
+ CoalesceExpressionHead ?? BitwiseORExpression
+
+ CoalesceExpressionHead :
+ CoalesceExpression
+ BitwiseORExpression
+features: [coalesce-expression]
+negative:
+ phase: early
+ type: SyntaxError
+---*/
+
+0 ?? 0 || true; \ No newline at end of file
diff --git a/js/src/tests/test262/language/expressions/coalesce/chainable-if-parenthesis-covered-logical-and.js b/js/src/tests/test262/language/expressions/coalesce/chainable-if-parenthesis-covered-logical-and.js
new file mode 100644
index 0000000000..edcf93b557
--- /dev/null
+++ b/js/src/tests/test262/language/expressions/coalesce/chainable-if-parenthesis-covered-logical-and.js
@@ -0,0 +1,55 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ CoalesceExpression is chainable with the LogicalANDExpression is any is covered.
+esid: sec-conditional-operator
+info: |
+ ConditionalExpression :
+ ShortCircuitExpression
+ ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+
+ ShortCircuitExpression :
+ LogicalORExpression
+ CoalesceExpression
+
+ CoalesceExpression :
+ CoalesceExpressionHead ?? BitwiseORExpression
+
+ CoalesceExpressionHead :
+ CoalesceExpression
+ BitwiseORExpression
+
+ Runtime Semantics: Evaluation
+
+ CoalesceExpression:CoalesceExpressionHead??BitwiseORExpression
+
+ 1. Let lref be the result of evaluating CoalesceExpressionHead.
+ 2. Let lval be ? GetValue(lref).
+ 3. If lval is undefined or null,
+ a. Let rref be the result of evaluating BitwiseORExpression.
+ b. Return ? GetValue(rref).
+ 4. Otherwise, return lval.
+features: [coalesce-expression]
+---*/
+
+var x;
+
+x = undefined;
+x = (null ?? 41) && 42;
+assert.sameValue(x, 42, '(null ?? 41) && 42');
+
+x = undefined;
+x = null ?? (41 && 42);
+assert.sameValue(x, 42, 'null ?? (41 && 42)`');
+
+x = undefined;
+x = (41 && 42) ?? null;
+assert.sameValue(x, 42, '(41 && 42) ?? null');
+
+x = undefined;
+x = 41 && (null ?? 42);
+assert.sameValue(x, 42, '41 && (null ?? 42)`');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/language/expressions/coalesce/chainable-if-parenthesis-covered-logical-or.js b/js/src/tests/test262/language/expressions/coalesce/chainable-if-parenthesis-covered-logical-or.js
new file mode 100644
index 0000000000..79e9a6dd9e
--- /dev/null
+++ b/js/src/tests/test262/language/expressions/coalesce/chainable-if-parenthesis-covered-logical-or.js
@@ -0,0 +1,63 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ CoalesceExpression is chainable with the LogicalORExpression is any is covered.
+esid: sec-conditional-operator
+info: |
+ ConditionalExpression :
+ ShortCircuitExpression
+ ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+
+ ShortCircuitExpression :
+ LogicalORExpression
+ CoalesceExpression
+
+ CoalesceExpression :
+ CoalesceExpressionHead ?? BitwiseORExpression
+
+ CoalesceExpressionHead :
+ CoalesceExpression
+ BitwiseORExpression
+
+ Runtime Semantics: Evaluation
+
+ CoalesceExpression:CoalesceExpressionHead??BitwiseORExpression
+
+ 1. Let lref be the result of evaluating CoalesceExpressionHead.
+ 2. Let lval be ? GetValue(lref).
+ 3. If lval is undefined or null,
+ a. Let rref be the result of evaluating BitwiseORExpression.
+ b. Return ? GetValue(rref).
+ 4. Otherwise, return lval.
+features: [coalesce-expression]
+---*/
+
+var x;
+
+x = undefined;
+x = (null ?? 42) || 43;
+assert.sameValue(x, 42, '(null ?? 42) || 43');
+
+x = undefined;
+x = null ?? (42 || 43);
+assert.sameValue(x, 42, 'null ?? (42 || 43)`');
+
+x = undefined;
+x = (null || 42) ?? 43;
+assert.sameValue(x, 42, '(null || 42) ?? 43');
+
+x = undefined;
+x = null || (42 ?? 43);
+assert.sameValue(x, 42, 'null || (42 ?? 43)`');
+
+x = undefined;
+x = (42 || 43) ?? null;
+assert.sameValue(x, 42, '(42 || 43) ?? null');
+
+x = undefined;
+x = 42 || (null ?? 43);
+assert.sameValue(x, 42, '42 || (null ?? 43)');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/language/expressions/coalesce/chainable-with-bitwise-and.js b/js/src/tests/test262/language/expressions/coalesce/chainable-with-bitwise-and.js
new file mode 100644
index 0000000000..dac7e04fc4
--- /dev/null
+++ b/js/src/tests/test262/language/expressions/coalesce/chainable-with-bitwise-and.js
@@ -0,0 +1,51 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ CoalesceExpression is chainable with the BitwiseANDExpression
+esid: sec-conditional-operator
+info: |
+ ConditionalExpression :
+ ShortCircuitExpression
+ ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+
+ ShortCircuitExpression :
+ LogicalORExpression
+ CoalesceExpression
+
+ CoalesceExpression :
+ CoalesceExpressionHead ?? BitwiseORExpression
+
+ CoalesceExpressionHead :
+ CoalesceExpression
+ BitwiseORExpression
+
+ Runtime Semantics: Evaluation
+
+ CoalesceExpression:CoalesceExpressionHead??BitwiseORExpression
+
+ 1. Let lref be the result of evaluating CoalesceExpressionHead.
+ 2. Let lval be ? GetValue(lref).
+ 3. If lval is undefined or null,
+ a. Let rref be the result of evaluating BitwiseORExpression.
+ b. Return ? GetValue(rref).
+ 4. Otherwise, return lval.
+features: [coalesce-expression]
+---*/
+
+var x;
+
+x = null ?? 42 & 43;
+assert.sameValue(x, 42, 'null ?? 42 & 43');
+
+x = undefined ?? 42 & 43;
+assert.sameValue(x, 42, 'null ?? 42 & 43');
+
+x = false ?? 42 & 43;
+assert.sameValue(x, false, 'false ?? 42 & 43');
+
+x = true ?? 42 & 43;
+assert.sameValue(x, true, 'true ?? 42 & 43');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/language/expressions/coalesce/chainable-with-bitwise-or.js b/js/src/tests/test262/language/expressions/coalesce/chainable-with-bitwise-or.js
new file mode 100644
index 0000000000..214d4fd9aa
--- /dev/null
+++ b/js/src/tests/test262/language/expressions/coalesce/chainable-with-bitwise-or.js
@@ -0,0 +1,51 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ CoalesceExpression is chainable with the BitwiseORExpression
+esid: sec-conditional-operator
+info: |
+ ConditionalExpression :
+ ShortCircuitExpression
+ ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+
+ ShortCircuitExpression :
+ LogicalORExpression
+ CoalesceExpression
+
+ CoalesceExpression :
+ CoalesceExpressionHead ?? BitwiseORExpression
+
+ CoalesceExpressionHead :
+ CoalesceExpression
+ BitwiseORExpression
+
+ Runtime Semantics: Evaluation
+
+ CoalesceExpression:CoalesceExpressionHead??BitwiseORExpression
+
+ 1. Let lref be the result of evaluating CoalesceExpressionHead.
+ 2. Let lval be ? GetValue(lref).
+ 3. If lval is undefined or null,
+ a. Let rref be the result of evaluating BitwiseORExpression.
+ b. Return ? GetValue(rref).
+ 4. Otherwise, return lval.
+features: [coalesce-expression]
+---*/
+
+var x;
+
+x = null ?? 1 | 42;
+assert.sameValue(x, 43, 'null ?? 1 | 42');
+
+x = undefined ?? 1 | 42;
+assert.sameValue(x, 43, 'null ?? 1 | 42');
+
+x = false ?? 1 | 42;
+assert.sameValue(x, false, 'false ?? 1 | 42');
+
+x = true ?? 1 | 42;
+assert.sameValue(x, true, 'true ?? 1 | 42');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/language/expressions/coalesce/chainable-with-bitwise-xor.js b/js/src/tests/test262/language/expressions/coalesce/chainable-with-bitwise-xor.js
new file mode 100644
index 0000000000..a3b6ad4ab0
--- /dev/null
+++ b/js/src/tests/test262/language/expressions/coalesce/chainable-with-bitwise-xor.js
@@ -0,0 +1,51 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ CoalesceExpression is chainable with the BitwiseXORExpression
+esid: sec-conditional-operator
+info: |
+ ConditionalExpression :
+ ShortCircuitExpression
+ ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+
+ ShortCircuitExpression :
+ LogicalORExpression
+ CoalesceExpression
+
+ CoalesceExpression :
+ CoalesceExpressionHead ?? BitwiseORExpression
+
+ CoalesceExpressionHead :
+ CoalesceExpression
+ BitwiseORExpression
+
+ Runtime Semantics: Evaluation
+
+ CoalesceExpression:CoalesceExpressionHead??BitwiseORExpression
+
+ 1. Let lref be the result of evaluating CoalesceExpressionHead.
+ 2. Let lval be ? GetValue(lref).
+ 3. If lval is undefined or null,
+ a. Let rref be the result of evaluating BitwiseORExpression.
+ b. Return ? GetValue(rref).
+ 4. Otherwise, return lval.
+features: [coalesce-expression]
+---*/
+
+var x;
+
+x = null ?? 1 ^ 42;
+assert.sameValue(x, 43, 'null ?? 1 ^ 42');
+
+x = undefined ?? 1 ^ 42;
+assert.sameValue(x, 43, 'null ?? 1 ^ 42');
+
+x = false ?? 1 ^ 42;
+assert.sameValue(x, false, 'false ?? 1 ^ 42');
+
+x = true ?? 1 ^ 42;
+assert.sameValue(x, true, 'true ?? 1 ^ 42');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/language/expressions/coalesce/chainable.js b/js/src/tests/test262/language/expressions/coalesce/chainable.js
new file mode 100644
index 0000000000..09ad90452a
--- /dev/null
+++ b/js/src/tests/test262/language/expressions/coalesce/chainable.js
@@ -0,0 +1,52 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ If the CoalesceExpressionHead is undefined or null, follow return the right-side value.
+ Otherwise, return the left-side value.
+esid: sec-conditional-operator
+info: |
+ ConditionalExpression :
+ ShortCircuitExpression
+ ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+
+ ShortCircuitExpression :
+ LogicalORExpression
+ CoalesceExpression
+
+ CoalesceExpression :
+ CoalesceExpressionHead ?? BitwiseORExpression
+
+ CoalesceExpressionHead :
+ CoalesceExpression
+ BitwiseORExpression
+
+ Runtime Semantics: Evaluation
+
+ CoalesceExpression:CoalesceExpressionHead??BitwiseORExpression
+
+ 1. Let lref be the result of evaluating CoalesceExpressionHead.
+ 2. Let lval be ? GetValue(lref).
+ 3. If lval is undefined or null,
+ a. Let rref be the result of evaluating BitwiseORExpression.
+ b. Return ? GetValue(rref).
+ 4. Otherwise, return lval.
+features: [coalesce-expression]
+---*/
+
+var x;
+
+x = null ?? undefined ?? 42;
+assert.sameValue(x, 42, 'null ?? undefined ?? 42');
+
+x = undefined ?? null ?? 42;
+assert.sameValue(x, 42, 'undefined ?? null ?? 42');
+
+x = null ?? null ?? 42;
+assert.sameValue(x, 42, 'null ?? null ?? 42');
+
+x = undefined ?? undefined ?? 42;
+assert.sameValue(x, 42, 'null ?? null ?? 42');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/language/expressions/coalesce/follows-null.js b/js/src/tests/test262/language/expressions/coalesce/follows-null.js
new file mode 100644
index 0000000000..82a95419ef
--- /dev/null
+++ b/js/src/tests/test262/language/expressions/coalesce/follows-null.js
@@ -0,0 +1,51 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ If the CoalesceExpressionHead is null, follow return the right-side eval.
+esid: sec-conditional-operator
+info: |
+ ConditionalExpression :
+ ShortCircuitExpression
+ ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+
+ ShortCircuitExpression :
+ LogicalORExpression
+ CoalesceExpression
+
+ CoalesceExpression :
+ CoalesceExpressionHead ?? BitwiseORExpression
+
+ CoalesceExpressionHead :
+ CoalesceExpression
+ BitwiseORExpression
+
+ Runtime Semantics: Evaluation
+
+ CoalesceExpression:CoalesceExpressionHead??BitwiseORExpression
+
+ 1. Let lref be the result of evaluating CoalesceExpressionHead.
+ 2. Let lval be ? GetValue(lref).
+ 3. If lval is undefined or null,
+ a. Let rref be the result of evaluating BitwiseORExpression.
+ b. Return ? GetValue(rref).
+ 4. Otherwise, return lval.
+features: [coalesce-expression]
+---*/
+
+var x;
+
+x = null ?? 42;
+assert.sameValue(x, 42, 'null ?? 42');
+
+x = null ?? undefined;
+assert.sameValue(x, undefined, 'null ?? undefined');
+
+x = null ?? null;
+assert.sameValue(x, null, 'null ?? null');
+
+x = null ?? false;
+assert.sameValue(x, false, 'null ?? false');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/language/expressions/coalesce/follows-undefined.js b/js/src/tests/test262/language/expressions/coalesce/follows-undefined.js
new file mode 100644
index 0000000000..0728ed1ff2
--- /dev/null
+++ b/js/src/tests/test262/language/expressions/coalesce/follows-undefined.js
@@ -0,0 +1,51 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ If the CoalesceExpressionHead is undefined, follow return the right-side eval.
+esid: sec-conditional-operator
+info: |
+ ConditionalExpression :
+ ShortCircuitExpression
+ ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+
+ ShortCircuitExpression :
+ LogicalORExpression
+ CoalesceExpression
+
+ CoalesceExpression :
+ CoalesceExpressionHead ?? BitwiseORExpression
+
+ CoalesceExpressionHead :
+ CoalesceExpression
+ BitwiseORExpression
+
+ Runtime Semantics: Evaluation
+
+ CoalesceExpression:CoalesceExpressionHead??BitwiseORExpression
+
+ 1. Let lref be the result of evaluating CoalesceExpressionHead.
+ 2. Let lval be ? GetValue(lref).
+ 3. If lval is undefined or null,
+ a. Let rref be the result of evaluating BitwiseORExpression.
+ b. Return ? GetValue(rref).
+ 4. Otherwise, return lval.
+features: [coalesce-expression]
+---*/
+
+var x;
+
+x = undefined ?? 42;
+assert.sameValue(x, 42, 'undefined ?? 42');
+
+x = undefined ?? undefined;
+assert.sameValue(x, undefined, 'undefined ?? undefined');
+
+x = undefined ?? null;
+assert.sameValue(x, null, 'undefined ?? null');
+
+x = undefined ?? false;
+assert.sameValue(x, false, 'undefined ?? false');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/language/expressions/coalesce/shell.js b/js/src/tests/test262/language/expressions/coalesce/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/language/expressions/coalesce/shell.js
diff --git a/js/src/tests/test262/language/expressions/coalesce/short-circuit-number-0.js b/js/src/tests/test262/language/expressions/coalesce/short-circuit-number-0.js
new file mode 100644
index 0000000000..547f62a1cf
--- /dev/null
+++ b/js/src/tests/test262/language/expressions/coalesce/short-circuit-number-0.js
@@ -0,0 +1,83 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ Short circuit if the CoalesceExpressionHead is not undefined or null (0)
+esid: sec-conditional-operator
+info: |
+ ConditionalExpression :
+ ShortCircuitExpression
+ ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+
+ ShortCircuitExpression :
+ LogicalORExpression
+ CoalesceExpression
+
+ CoalesceExpression :
+ CoalesceExpressionHead ?? BitwiseORExpression
+
+ CoalesceExpressionHead :
+ CoalesceExpression
+ BitwiseORExpression
+
+ Runtime Semantics: Evaluation
+
+ CoalesceExpression:CoalesceExpressionHead??BitwiseORExpression
+
+ 1. Let lref be the result of evaluating CoalesceExpressionHead.
+ 2. Let lval be ? GetValue(lref).
+ 3. If lval is undefined or null,
+ a. Let rref be the result of evaluating BitwiseORExpression.
+ b. Return ? GetValue(rref).
+ 4. Otherwise, return lval.
+features: [coalesce-expression]
+---*/
+
+var x;
+
+x = undefined;
+x = 0 ?? 1;
+assert.sameValue(x, 0, '0 ?? 1');
+
+x = undefined;
+x = 0 ?? null;
+assert.sameValue(x, 0, '0 ?? null');
+
+x = undefined;
+x = 0 ?? undefined;
+assert.sameValue(x, 0, '0 ?? undefined');
+
+x = undefined;
+x = 0 ?? null ?? undefined;
+assert.sameValue(x, 0, '0 ?? null ?? undefined');
+
+x = undefined;
+x = 0 ?? undefined ?? null;
+assert.sameValue(x, 0, '0 ?? undefined ?? null');
+
+x = undefined;
+x = 0 ?? null ?? null;
+assert.sameValue(x, 0, '0 ?? null ?? null');
+
+x = undefined;
+x = 0 ?? undefined ?? undefined;
+assert.sameValue(x, 0, '0 ?? null ?? null');
+
+x = undefined;
+x = null ?? 0 ?? null;
+assert.sameValue(x, 0, 'null ?? 0 ?? null');
+
+x = undefined;
+x = null ?? 0 ?? undefined;
+assert.sameValue(x, 0, 'null ?? 0 ?? undefined');
+
+x = undefined;
+x = undefined ?? 0 ?? null;
+assert.sameValue(x, 0, 'undefined ?? 0 ?? null');
+
+x = undefined;
+x = undefined ?? 0 ?? undefined;
+assert.sameValue(x, 0, 'undefined ?? 0 ?? undefined');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/language/expressions/coalesce/short-circuit-number-42.js b/js/src/tests/test262/language/expressions/coalesce/short-circuit-number-42.js
new file mode 100644
index 0000000000..b30d905f2f
--- /dev/null
+++ b/js/src/tests/test262/language/expressions/coalesce/short-circuit-number-42.js
@@ -0,0 +1,83 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ Short circuit if the CoalesceExpressionHead is not undefined or null (42)
+esid: sec-conditional-operator
+info: |
+ ConditionalExpression :
+ ShortCircuitExpression
+ ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+
+ ShortCircuitExpression :
+ LogicalORExpression
+ CoalesceExpression
+
+ CoalesceExpression :
+ CoalesceExpressionHead ?? BitwiseORExpression
+
+ CoalesceExpressionHead :
+ CoalesceExpression
+ BitwiseORExpression
+
+ Runtime Semantics: Evaluation
+
+ CoalesceExpression:CoalesceExpressionHead??BitwiseORExpression
+
+ 1. Let lref be the result of evaluating CoalesceExpressionHead.
+ 2. Let lval be ? GetValue(lref).
+ 3. If lval is undefined or null,
+ a. Let rref be the result of evaluating BitwiseORExpression.
+ b. Return ? GetValue(rref).
+ 4. Otherwise, return lval.
+features: [coalesce-expression]
+---*/
+
+var x;
+
+x = undefined;
+x = 42 ?? 1;
+assert.sameValue(x, 42, '42 ?? 1');
+
+x = undefined;
+x = 42 ?? null;
+assert.sameValue(x, 42, '42 ?? null');
+
+x = undefined;
+x = 42 ?? undefined;
+assert.sameValue(x, 42, '42 ?? undefined');
+
+x = undefined;
+x = 42 ?? null ?? undefined;
+assert.sameValue(x, 42, '42 ?? null ?? undefined');
+
+x = undefined;
+x = 42 ?? undefined ?? null;
+assert.sameValue(x, 42, '42 ?? undefined ?? null');
+
+x = undefined;
+x = 42 ?? null ?? null;
+assert.sameValue(x, 42, '42 ?? null ?? null');
+
+x = undefined;
+x = 42 ?? undefined ?? undefined;
+assert.sameValue(x, 42, '42 ?? null ?? null');
+
+x = undefined;
+x = null ?? 42 ?? null;
+assert.sameValue(x, 42, 'null ?? 42 ?? null');
+
+x = undefined;
+x = null ?? 42 ?? undefined;
+assert.sameValue(x, 42, 'null ?? 42 ?? undefined');
+
+x = undefined;
+x = undefined ?? 42 ?? null;
+assert.sameValue(x, 42, 'undefined ?? 42 ?? null');
+
+x = undefined;
+x = undefined ?? 42 ?? undefined;
+assert.sameValue(x, 42, 'undefined ?? 42 ?? undefined');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/language/expressions/coalesce/short-circuit-number-empty-string.js b/js/src/tests/test262/language/expressions/coalesce/short-circuit-number-empty-string.js
new file mode 100644
index 0000000000..c513e2cbc7
--- /dev/null
+++ b/js/src/tests/test262/language/expressions/coalesce/short-circuit-number-empty-string.js
@@ -0,0 +1,84 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ Short circuit if the CoalesceExpressionHead is not undefined or null (the empty string)
+esid: sec-conditional-operator
+info: |
+ ConditionalExpression :
+ ShortCircuitExpression
+ ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+
+ ShortCircuitExpression :
+ LogicalORExpression
+ CoalesceExpression
+
+ CoalesceExpression :
+ CoalesceExpressionHead ?? BitwiseORExpression
+
+ CoalesceExpressionHead :
+ CoalesceExpression
+ BitwiseORExpression
+
+ Runtime Semantics: Evaluation
+
+ CoalesceExpression:CoalesceExpressionHead??BitwiseORExpression
+
+ 1. Let lref be the result of evaluating CoalesceExpressionHead.
+ 2. Let lval be ? GetValue(lref).
+ 3. If lval is undefined or null,
+ a. Let rref be the result of evaluating BitwiseORExpression.
+ b. Return ? GetValue(rref).
+ 4. Otherwise, return lval.
+features: [coalesce-expression]
+---*/
+
+var x;
+var str = '';
+
+x = undefined;
+x = str ?? 1;
+assert.sameValue(x, str, 'str ?? 1');
+
+x = undefined;
+x = str ?? null;
+assert.sameValue(x, str, 'str ?? null');
+
+x = undefined;
+x = str ?? undefined;
+assert.sameValue(x, str, 'str ?? undefined');
+
+x = undefined;
+x = str ?? null ?? undefined;
+assert.sameValue(x, str, 'str ?? null ?? undefined');
+
+x = undefined;
+x = str ?? undefined ?? null;
+assert.sameValue(x, str, 'str ?? undefined ?? null');
+
+x = undefined;
+x = str ?? null ?? null;
+assert.sameValue(x, str, 'str ?? null ?? null');
+
+x = undefined;
+x = str ?? undefined ?? undefined;
+assert.sameValue(x, str, 'str ?? null ?? null');
+
+x = undefined;
+x = null ?? str ?? null;
+assert.sameValue(x, str, 'null ?? str ?? null');
+
+x = undefined;
+x = null ?? str ?? undefined;
+assert.sameValue(x, str, 'null ?? str ?? undefined');
+
+x = undefined;
+x = undefined ?? str ?? null;
+assert.sameValue(x, str, 'undefined ?? str ?? null');
+
+x = undefined;
+x = undefined ?? str ?? undefined;
+assert.sameValue(x, str, 'undefined ?? str ?? undefined');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/language/expressions/coalesce/short-circuit-number-false.js b/js/src/tests/test262/language/expressions/coalesce/short-circuit-number-false.js
new file mode 100644
index 0000000000..d235729393
--- /dev/null
+++ b/js/src/tests/test262/language/expressions/coalesce/short-circuit-number-false.js
@@ -0,0 +1,83 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ Short circuit if the CoalesceExpressionHead is not undefined or null (false)
+esid: sec-conditional-operator
+info: |
+ ConditionalExpression :
+ ShortCircuitExpression
+ ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+
+ ShortCircuitExpression :
+ LogicalORExpression
+ CoalesceExpression
+
+ CoalesceExpression :
+ CoalesceExpressionHead ?? BitwiseORExpression
+
+ CoalesceExpressionHead :
+ CoalesceExpression
+ BitwiseORExpression
+
+ Runtime Semantics: Evaluation
+
+ CoalesceExpression:CoalesceExpressionHead??BitwiseORExpression
+
+ 1. Let lref be the result of evaluating CoalesceExpressionHead.
+ 2. Let lval be ? GetValue(lref).
+ 3. If lval is undefined or null,
+ a. Let rref be the result of evaluating BitwiseORExpression.
+ b. Return ? GetValue(rref).
+ 4. Otherwise, return lval.
+features: [coalesce-expression]
+---*/
+
+var x;
+
+x = undefined;
+x = false ?? 1;
+assert.sameValue(x, false, 'false ?? 1');
+
+x = undefined;
+x = false ?? null;
+assert.sameValue(x, false, 'false ?? null');
+
+x = undefined;
+x = false ?? undefined;
+assert.sameValue(x, false, 'false ?? undefined');
+
+x = undefined;
+x = false ?? null ?? undefined;
+assert.sameValue(x, false, 'false ?? null ?? undefined');
+
+x = undefined;
+x = false ?? undefined ?? null;
+assert.sameValue(x, false, 'false ?? undefined ?? null');
+
+x = undefined;
+x = false ?? null ?? null;
+assert.sameValue(x, false, 'false ?? null ?? null');
+
+x = undefined;
+x = false ?? undefined ?? undefined;
+assert.sameValue(x, false, 'false ?? null ?? null');
+
+x = undefined;
+x = null ?? false ?? null;
+assert.sameValue(x, false, 'null ?? false ?? null');
+
+x = undefined;
+x = null ?? false ?? undefined;
+assert.sameValue(x, false, 'null ?? false ?? undefined');
+
+x = undefined;
+x = undefined ?? false ?? null;
+assert.sameValue(x, false, 'undefined ?? false ?? null');
+
+x = undefined;
+x = undefined ?? false ?? undefined;
+assert.sameValue(x, false, 'undefined ?? false ?? undefined');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/language/expressions/coalesce/short-circuit-number-object.js b/js/src/tests/test262/language/expressions/coalesce/short-circuit-number-object.js
new file mode 100644
index 0000000000..5f2eee11e7
--- /dev/null
+++ b/js/src/tests/test262/language/expressions/coalesce/short-circuit-number-object.js
@@ -0,0 +1,91 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ Short circuit if the CoalesceExpressionHead is not undefined or null (object)
+esid: sec-conditional-operator
+info: |
+ ConditionalExpression :
+ ShortCircuitExpression
+ ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+
+ ShortCircuitExpression :
+ LogicalORExpression
+ CoalesceExpression
+
+ CoalesceExpression :
+ CoalesceExpressionHead ?? BitwiseORExpression
+
+ CoalesceExpressionHead :
+ CoalesceExpression
+ BitwiseORExpression
+
+ Runtime Semantics: Evaluation
+
+ CoalesceExpression:CoalesceExpressionHead??BitwiseORExpression
+
+ 1. Let lref be the result of evaluating CoalesceExpressionHead.
+ 2. Let lval be ? GetValue(lref).
+ 3. If lval is undefined or null,
+ a. Let rref be the result of evaluating BitwiseORExpression.
+ b. Return ? GetValue(rref).
+ 4. Otherwise, return lval.
+features: [coalesce-expression]
+---*/
+
+var x;
+var obj = {
+ toString() {
+ return null;
+ },
+ valueOf() {
+ return null;
+ }
+};
+
+x = undefined;
+x = obj ?? 1;
+assert.sameValue(x, obj, 'obj ?? 1');
+
+x = undefined;
+x = obj ?? null;
+assert.sameValue(x, obj, 'obj ?? null');
+
+x = undefined;
+x = obj ?? undefined;
+assert.sameValue(x, obj, 'obj ?? undefined');
+
+x = undefined;
+x = obj ?? null ?? undefined;
+assert.sameValue(x, obj, 'obj ?? null ?? undefined');
+
+x = undefined;
+x = obj ?? undefined ?? null;
+assert.sameValue(x, obj, 'obj ?? undefined ?? null');
+
+x = undefined;
+x = obj ?? null ?? null;
+assert.sameValue(x, obj, 'obj ?? null ?? null');
+
+x = undefined;
+x = obj ?? undefined ?? undefined;
+assert.sameValue(x, obj, 'obj ?? null ?? null');
+
+x = undefined;
+x = null ?? obj ?? null;
+assert.sameValue(x, obj, 'null ?? obj ?? null');
+
+x = undefined;
+x = null ?? obj ?? undefined;
+assert.sameValue(x, obj, 'null ?? obj ?? undefined');
+
+x = undefined;
+x = undefined ?? obj ?? null;
+assert.sameValue(x, obj, 'undefined ?? obj ?? null');
+
+x = undefined;
+x = undefined ?? obj ?? undefined;
+assert.sameValue(x, obj, 'undefined ?? obj ?? undefined');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/language/expressions/coalesce/short-circuit-number-string.js b/js/src/tests/test262/language/expressions/coalesce/short-circuit-number-string.js
new file mode 100644
index 0000000000..9979a685cc
--- /dev/null
+++ b/js/src/tests/test262/language/expressions/coalesce/short-circuit-number-string.js
@@ -0,0 +1,84 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ Short circuit if the CoalesceExpressionHead is not undefined or null (string)
+esid: sec-conditional-operator
+info: |
+ ConditionalExpression :
+ ShortCircuitExpression
+ ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+
+ ShortCircuitExpression :
+ LogicalORExpression
+ CoalesceExpression
+
+ CoalesceExpression :
+ CoalesceExpressionHead ?? BitwiseORExpression
+
+ CoalesceExpressionHead :
+ CoalesceExpression
+ BitwiseORExpression
+
+ Runtime Semantics: Evaluation
+
+ CoalesceExpression:CoalesceExpressionHead??BitwiseORExpression
+
+ 1. Let lref be the result of evaluating CoalesceExpressionHead.
+ 2. Let lval be ? GetValue(lref).
+ 3. If lval is undefined or null,
+ a. Let rref be the result of evaluating BitwiseORExpression.
+ b. Return ? GetValue(rref).
+ 4. Otherwise, return lval.
+features: [coalesce-expression]
+---*/
+
+var x;
+var str = 'undefined';
+
+x = undefined;
+x = str ?? 1;
+assert.sameValue(x, str, 'str ?? 1');
+
+x = undefined;
+x = str ?? null;
+assert.sameValue(x, str, 'str ?? null');
+
+x = undefined;
+x = str ?? undefined;
+assert.sameValue(x, str, 'str ?? undefined');
+
+x = undefined;
+x = str ?? null ?? undefined;
+assert.sameValue(x, str, 'str ?? null ?? undefined');
+
+x = undefined;
+x = str ?? undefined ?? null;
+assert.sameValue(x, str, 'str ?? undefined ?? null');
+
+x = undefined;
+x = str ?? null ?? null;
+assert.sameValue(x, str, 'str ?? null ?? null');
+
+x = undefined;
+x = str ?? undefined ?? undefined;
+assert.sameValue(x, str, 'str ?? null ?? null');
+
+x = undefined;
+x = null ?? str ?? null;
+assert.sameValue(x, str, 'null ?? str ?? null');
+
+x = undefined;
+x = null ?? str ?? undefined;
+assert.sameValue(x, str, 'null ?? str ?? undefined');
+
+x = undefined;
+x = undefined ?? str ?? null;
+assert.sameValue(x, str, 'undefined ?? str ?? null');
+
+x = undefined;
+x = undefined ?? str ?? undefined;
+assert.sameValue(x, str, 'undefined ?? str ?? undefined');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/language/expressions/coalesce/short-circuit-number-symbol.js b/js/src/tests/test262/language/expressions/coalesce/short-circuit-number-symbol.js
new file mode 100644
index 0000000000..827c6e8535
--- /dev/null
+++ b/js/src/tests/test262/language/expressions/coalesce/short-circuit-number-symbol.js
@@ -0,0 +1,84 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ Short circuit if the CoalesceExpressionHead is not undefined or null (Symbol)
+esid: sec-conditional-operator
+info: |
+ ConditionalExpression :
+ ShortCircuitExpression
+ ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+
+ ShortCircuitExpression :
+ LogicalORExpression
+ CoalesceExpression
+
+ CoalesceExpression :
+ CoalesceExpressionHead ?? BitwiseORExpression
+
+ CoalesceExpressionHead :
+ CoalesceExpression
+ BitwiseORExpression
+
+ Runtime Semantics: Evaluation
+
+ CoalesceExpression:CoalesceExpressionHead??BitwiseORExpression
+
+ 1. Let lref be the result of evaluating CoalesceExpressionHead.
+ 2. Let lval be ? GetValue(lref).
+ 3. If lval is undefined or null,
+ a. Let rref be the result of evaluating BitwiseORExpression.
+ b. Return ? GetValue(rref).
+ 4. Otherwise, return lval.
+features: [coalesce-expression]
+---*/
+
+var x;
+var s = Symbol();
+
+x = undefined;
+x = s ?? 1;
+assert.sameValue(x, s, 's ?? null');
+
+x = undefined;
+x = s ?? null;
+assert.sameValue(x, s, 's ?? null');
+
+x = undefined;
+x = s ?? undefined;
+assert.sameValue(x, s, 's ?? undefined');
+
+x = undefined;
+x = s ?? null ?? undefined;
+assert.sameValue(x, s, 's ?? null ?? undefined');
+
+x = undefined;
+x = s ?? undefined ?? null;
+assert.sameValue(x, s, 's ?? undefined ?? null');
+
+x = undefined;
+x = s ?? null ?? null;
+assert.sameValue(x, s, 's ?? null ?? null');
+
+x = undefined;
+x = s ?? undefined ?? undefined;
+assert.sameValue(x, s, 's ?? null ?? null');
+
+x = undefined;
+x = null ?? s ?? null;
+assert.sameValue(x, s, 'null ?? s ?? null');
+
+x = undefined;
+x = null ?? s ?? undefined;
+assert.sameValue(x, s, 'null ?? s ?? undefined');
+
+x = undefined;
+x = undefined ?? s ?? null;
+assert.sameValue(x, s, 'undefined ?? s ?? null');
+
+x = undefined;
+x = undefined ?? s ?? undefined;
+assert.sameValue(x, s, 'undefined ?? s ?? undefined');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/language/expressions/coalesce/short-circuit-number-true.js b/js/src/tests/test262/language/expressions/coalesce/short-circuit-number-true.js
new file mode 100644
index 0000000000..9b24181ab3
--- /dev/null
+++ b/js/src/tests/test262/language/expressions/coalesce/short-circuit-number-true.js
@@ -0,0 +1,83 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ Short circuit if the CoalesceExpressionHead is not undefined or null (true)
+esid: sec-conditional-operator
+info: |
+ ConditionalExpression :
+ ShortCircuitExpression
+ ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+
+ ShortCircuitExpression :
+ LogicalORExpression
+ CoalesceExpression
+
+ CoalesceExpression :
+ CoalesceExpressionHead ?? BitwiseORExpression
+
+ CoalesceExpressionHead :
+ CoalesceExpression
+ BitwiseORExpression
+
+ Runtime Semantics: Evaluation
+
+ CoalesceExpression:CoalesceExpressionHead??BitwiseORExpression
+
+ 1. Let lref be the result of evaluating CoalesceExpressionHead.
+ 2. Let lval be ? GetValue(lref).
+ 3. If lval is undefined or null,
+ a. Let rref be the result of evaluating BitwiseORExpression.
+ b. Return ? GetValue(rref).
+ 4. Otherwise, return lval.
+features: [coalesce-expression]
+---*/
+
+var x;
+
+x = undefined;
+x = true ?? 1;
+assert.sameValue(x, true, 'true ?? null');
+
+x = undefined;
+x = true ?? null;
+assert.sameValue(x, true, 'true ?? null');
+
+x = undefined;
+x = true ?? undefined;
+assert.sameValue(x, true, 'true ?? undefined');
+
+x = undefined;
+x = true ?? null ?? undefined;
+assert.sameValue(x, true, 'true ?? null ?? undefined');
+
+x = undefined;
+x = true ?? undefined ?? null;
+assert.sameValue(x, true, 'true ?? undefined ?? null');
+
+x = undefined;
+x = true ?? null ?? null;
+assert.sameValue(x, true, 'true ?? null ?? null');
+
+x = undefined;
+x = true ?? undefined ?? undefined;
+assert.sameValue(x, true, 'true ?? null ?? null');
+
+x = undefined;
+x = null ?? true ?? null;
+assert.sameValue(x, true, 'null ?? true ?? null');
+
+x = undefined;
+x = null ?? true ?? undefined;
+assert.sameValue(x, true, 'null ?? true ?? undefined');
+
+x = undefined;
+x = undefined ?? true ?? null;
+assert.sameValue(x, true, 'undefined ?? true ?? null');
+
+x = undefined;
+x = undefined ?? true ?? undefined;
+assert.sameValue(x, true, 'undefined ?? true ?? undefined');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/language/expressions/coalesce/short-circuit-prevents-evaluation.js b/js/src/tests/test262/language/expressions/coalesce/short-circuit-prevents-evaluation.js
new file mode 100644
index 0000000000..14cbd109e4
--- /dev/null
+++ b/js/src/tests/test262/language/expressions/coalesce/short-circuit-prevents-evaluation.js
@@ -0,0 +1,58 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ Short circuit can prevent evaluation of the right-side expressions
+esid: sec-conditional-operator
+info: |
+ ConditionalExpression :
+ ShortCircuitExpression
+ ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+
+ ShortCircuitExpression :
+ LogicalORExpression
+ CoalesceExpression
+
+ CoalesceExpression :
+ CoalesceExpressionHead ?? BitwiseORExpression
+
+ CoalesceExpressionHead :
+ CoalesceExpression
+ BitwiseORExpression
+
+ Runtime Semantics: Evaluation
+
+ CoalesceExpression:CoalesceExpressionHead??BitwiseORExpression
+
+ 1. Let lref be the result of evaluating CoalesceExpressionHead.
+ 2. Let lval be ? GetValue(lref).
+ 3. If lval is undefined or null,
+ a. Let rref be the result of evaluating BitwiseORExpression.
+ b. Return ? GetValue(rref).
+ 4. Otherwise, return lval.
+features: [coalesce-expression]
+---*/
+
+var x;
+function poison() {
+ throw new Test262Error('should not evaluate poison');
+}
+
+x = undefined;
+x = undefined ?? 42 ?? undefined ?? poison();
+assert.sameValue(x, 42);
+
+x = undefined;
+x = 42 ?? undefined ?? poison();
+assert.sameValue(x, 42);
+
+x = undefined;
+x = undefined ?? 42 ?? poison();
+assert.sameValue(x, 42);
+
+x = undefined;
+x = 42 ?? poison();
+assert.sameValue(x, 42);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/language/expressions/coalesce/tco-pos-null-strict.js b/js/src/tests/test262/language/expressions/coalesce/tco-pos-null-strict.js
new file mode 100644
index 0000000000..9030e2227c
--- /dev/null
+++ b/js/src/tests/test262/language/expressions/coalesce/tco-pos-null-strict.js
@@ -0,0 +1,30 @@
+// |reftest| skip -- tail-call-optimization is not supported
+'use strict';
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Expression is a candidate for tail-call optimization.
+esid: sec-static-semantics-hascallintailposition
+info: |
+ Expression Rules
+
+ CoalesceExpression : CoalesceExpressionHead ?? BitwiseORExpression
+
+ 1. Return HasCallInTailPosition of BitwiseORExpression with argument call.
+flags: [onlyStrict]
+features: [tail-call-optimization, coalesce-expression]
+includes: [tcoHelper.js]
+---*/
+
+var callCount = 0;
+(function f(n) {
+ if (n === 0) {
+ callCount += 1
+ return;
+ }
+ return null ?? f(n - 1);
+}($MAX_ITERATIONS));
+assert.sameValue(callCount, 1);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/language/expressions/coalesce/tco-pos-undefined-strict.js b/js/src/tests/test262/language/expressions/coalesce/tco-pos-undefined-strict.js
new file mode 100644
index 0000000000..70f6ff59ec
--- /dev/null
+++ b/js/src/tests/test262/language/expressions/coalesce/tco-pos-undefined-strict.js
@@ -0,0 +1,30 @@
+// |reftest| skip -- tail-call-optimization is not supported
+'use strict';
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Expression is a candidate for tail-call optimization.
+esid: sec-static-semantics-hascallintailposition
+info: |
+ Expression Rules
+
+ CoalesceExpression : CoalesceExpressionHead ?? BitwiseORExpression
+
+ 1. Return HasCallInTailPosition of BitwiseORExpression with argument call.
+flags: [onlyStrict]
+features: [tail-call-optimization, coalesce-expression]
+includes: [tcoHelper.js]
+---*/
+
+var callCount = 0;
+(function f(n) {
+ if (n === 0) {
+ callCount += 1
+ return;
+ }
+ return undefined ?? f(n - 1);
+}($MAX_ITERATIONS));
+assert.sameValue(callCount, 1);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/language/expressions/conditional/browser.js b/js/src/tests/test262/language/expressions/conditional/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/language/expressions/conditional/browser.js
diff --git a/js/src/tests/test262/language/expressions/conditional/coalesce-expr-ternary.js b/js/src/tests/test262/language/expressions/conditional/coalesce-expr-ternary.js
new file mode 100644
index 0000000000..543f22127a
--- /dev/null
+++ b/js/src/tests/test262/language/expressions/conditional/coalesce-expr-ternary.js
@@ -0,0 +1,75 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ ShortCircuitExpression in the Conditional Expression (? :)
+esid: sec-conditional-operator
+info: |
+ ShortCircuitExpression :
+ LogicalORExpression
+ CoalesceExpression
+
+ CoalesceExpression :
+ CoalesceExpressionHead ?? BitwiseORExpression
+
+ CoalesceExpressionHead :
+ CoalesceExpression
+ BitwiseORExpression
+
+ ConditionalExpression :
+ ShortCircuitExpression
+ ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+features: [coalesce-expression]
+---*/
+
+var x;
+
+x = undefined ?? true ? 0 : 42;
+assert.sameValue(x, 0, 'undefined ?? true ? 0 : 42');
+
+x = undefined;
+x = null ?? true ? 0 : 42;
+assert.sameValue(x, 0, 'null ?? true ? 0 : 42');
+
+x = undefined;
+x = undefined ?? false ? 0 : 42;
+assert.sameValue(x, 42, 'undefined ?? false ? 0 : 42');
+
+x = undefined;
+x = null ?? false ? 0 : 42;
+assert.sameValue(x, 42, 'null ?? false ? 0 : 42');
+
+x = undefined;
+x = false ?? true ? 0 : 42;
+assert.sameValue(x, 42, 'false ?? true ? 0 : 42');
+
+x = undefined;
+x = 0 ?? true ? 0 : 42;
+assert.sameValue(x, 42, '0 ?? true ? 0 : 42');
+
+x = undefined;
+x = 1 ?? false ? 0 : 42;
+assert.sameValue(x, 0, '1 ?? false ? 0 : 42');
+
+x = undefined;
+x = true ?? false ? 0 : 42;
+assert.sameValue(x, 0, 'true ?? false ? 0 : 42');
+
+x = undefined;
+x = true ?? true ? 0 : 42;
+assert.sameValue(x, 0, 'true ?? true ? 0 : 42');
+
+x = undefined;
+x = '' ?? true ? 0 : 42;
+assert.sameValue(x, 42, '"" ?? true ? 0 : 42');
+
+x = undefined;
+x = Symbol() ?? false ? 0 : 42;
+assert.sameValue(x, 0, 'Symbol() ?? false ? 0 : 42');
+
+x = undefined;
+x = {} ?? false ? 0 : 42;
+assert.sameValue(x, 0, 'object ?? false ? 0 : 42');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/language/expressions/conditional/shell.js b/js/src/tests/test262/language/expressions/conditional/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/language/expressions/conditional/shell.js
diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp
index 93542e4d7d..56f715e2be 100644
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -2128,6 +2128,16 @@ CASE(JSOP_IFNE)
}
END_CASE(JSOP_IFNE)
+CASE(JSOP_COALESCE)
+{
+ MutableHandleValue res = REGS.stackHandleAt(-1);
+ bool cond = !res.isNullOrUndefined();
+ if (cond) {
+ ADVANCE_AND_DISPATCH(GET_JUMP_OFFSET(REGS.pc));
+ }
+}
+END_CASE(JSOP_COALESCE)
+
CASE(JSOP_OR)
{
bool cond = ToBoolean(REGS.stackHandleAt(-1));
diff --git a/js/src/vm/Opcodes.h b/js/src/vm/Opcodes.h
index 58b5d75776..8328dfc994 100644
--- a/js/src/vm/Opcodes.h
+++ b/js/src/vm/Opcodes.h
@@ -2315,7 +2315,7 @@
* Operands:
* Stack: =>
*/ \
- macro(JSOP_JUMPTARGET, 230, "jumptarget", NULL, 1, 0, 0, JOF_BYTE)\
+ macro(JSOP_JUMPTARGET, 230, "jumptarget", NULL, 1, 0, 0, JOF_BYTE) \
/*
* Like JSOP_CALL, but tells the function that the return value is ignored.
* stack.
@@ -2325,14 +2325,22 @@
* Stack: callee, this, args[0], ..., args[argc-1] => rval
* nuses: (argc+2)
*/ \
- macro(JSOP_CALL_IGNORES_RV, 231, "call-ignores-rv", NULL, 3, -1, 1, JOF_UINT16|JOF_INVOKE|JOF_TYPESET)
-
+ macro(JSOP_CALL_IGNORES_RV, 231, "call-ignores-rv", NULL, 3, -1, 1, JOF_UINT16|JOF_INVOKE|JOF_TYPESET) \
+ /*
+ * If the value on top of the stack is not null or undefined, jumps to a 32-bit offset from the
+ * current bytecode.
+ *
+ * Category: Statements
+ * Type: Jumps
+ * Operands: int32_t offset
+ * Stack: cond => cond
+ */ \
+ macro(JSOP_COALESCE, 232, "coalesce", NULL, 5, 1, 1, JOF_JUMP|JOF_DETECTING)
/*
* In certain circumstances it may be useful to "pad out" the opcode space to
* a power of two. Use this macro to do so.
*/
#define FOR_EACH_TRAILING_UNUSED_OPCODE(macro) \
- macro(232) \
macro(233) \
macro(234) \
macro(235) \