From 53926d7fbf4232cdef0449b5b515404085fea3f1 Mon Sep 17 00:00:00 2001 From: FranklinDM Date: Fri, 27 May 2022 16:21:50 -0500 Subject: [JS:Engine] Implement support for nullish coalescing in the JS parser - UXP Parts 1, 2, 4, 5, and 7 based on Bug 1566141 - UXP Part 3 partially based on Bugs 1566141 and 1593415 - UXP Part 6 partly based on Bugs 1566141 and 1599163 with a different approach by modifying the `Boolish` function directly to act differently if we're checking for nullish values. --- js/src/builtin/ReflectParse.cpp | 44 ++++++-- js/src/frontend/BytecodeEmitter.cpp | 2 + js/src/frontend/FoldConstants.cpp | 46 ++++++--- js/src/frontend/NameFunctions.cpp | 1 + js/src/frontend/ParseNode.cpp | 1 + js/src/frontend/ParseNode.h | 19 +++- js/src/frontend/Parser.cpp | 110 ++++++++++++++------ js/src/frontend/TokenKind.h | 12 ++- js/src/frontend/TokenStream.cpp | 6 +- js/src/jit/BaselineCompiler.cpp | 19 ++++ js/src/jit/BaselineCompiler.h | 1 + js/src/jit/CodeGenerator.cpp | 18 ++++ js/src/jit/CodeGenerator.h | 1 + js/src/jit/IonBuilder.cpp | 40 +++++--- js/src/jit/IonBuilder.h | 8 +- js/src/jit/Lowering.cpp | 10 ++ js/src/jit/Lowering.h | 1 + js/src/jit/MIR.h | 24 +++++ js/src/jit/MOpcodes.h | 1 + js/src/jit/shared/LIR-shared.h | 15 +++ js/src/jit/shared/LOpcodes-shared.h | 1 + js/src/js.msg | 1 + js/src/jsopcodeinlines.h | 1 + js/src/tests/js1_8_5/reflect-parse/expression.js | 35 +++++++ .../tests/non262/expressions/nullish-coalescing.js | 112 +++++++++++++++++++++ .../coalesce/abrupt-is-a-short-circuit.js | 60 +++++++++++ .../language/expressions/coalesce/browser.js | 0 .../coalesce/cannot-chain-head-with-logical-and.js | 30 ++++++ .../coalesce/cannot-chain-head-with-logical-or.js | 30 ++++++ .../coalesce/cannot-chain-tail-with-logical-and.js | 31 ++++++ .../coalesce/cannot-chain-tail-with-logical-or.js | 31 ++++++ ...chainable-if-parenthesis-covered-logical-and.js | 55 ++++++++++ .../chainable-if-parenthesis-covered-logical-or.js | 63 ++++++++++++ .../coalesce/chainable-with-bitwise-and.js | 51 ++++++++++ .../coalesce/chainable-with-bitwise-or.js | 51 ++++++++++ .../coalesce/chainable-with-bitwise-xor.js | 51 ++++++++++ .../language/expressions/coalesce/chainable.js | 52 ++++++++++ .../language/expressions/coalesce/follows-null.js | 51 ++++++++++ .../expressions/coalesce/follows-undefined.js | 51 ++++++++++ .../test262/language/expressions/coalesce/shell.js | 0 .../expressions/coalesce/short-circuit-number-0.js | 83 +++++++++++++++ .../coalesce/short-circuit-number-42.js | 83 +++++++++++++++ .../coalesce/short-circuit-number-empty-string.js | 84 ++++++++++++++++ .../coalesce/short-circuit-number-false.js | 83 +++++++++++++++ .../coalesce/short-circuit-number-object.js | 91 +++++++++++++++++ .../coalesce/short-circuit-number-string.js | 84 ++++++++++++++++ .../coalesce/short-circuit-number-symbol.js | 84 ++++++++++++++++ .../coalesce/short-circuit-number-true.js | 83 +++++++++++++++ .../coalesce/short-circuit-prevents-evaluation.js | 58 +++++++++++ .../expressions/coalesce/tco-pos-null-strict.js | 30 ++++++ .../coalesce/tco-pos-undefined-strict.js | 30 ++++++ .../language/expressions/conditional/browser.js | 0 .../conditional/coalesce-expr-ternary.js | 75 ++++++++++++++ .../language/expressions/conditional/shell.js | 0 js/src/vm/Interpreter.cpp | 10 ++ js/src/vm/Opcodes.h | 16 ++- 56 files changed, 1946 insertions(+), 84 deletions(-) create mode 100644 js/src/tests/non262/expressions/nullish-coalescing.js create mode 100644 js/src/tests/test262/language/expressions/coalesce/abrupt-is-a-short-circuit.js create mode 100644 js/src/tests/test262/language/expressions/coalesce/browser.js create mode 100644 js/src/tests/test262/language/expressions/coalesce/cannot-chain-head-with-logical-and.js create mode 100644 js/src/tests/test262/language/expressions/coalesce/cannot-chain-head-with-logical-or.js create mode 100644 js/src/tests/test262/language/expressions/coalesce/cannot-chain-tail-with-logical-and.js create mode 100644 js/src/tests/test262/language/expressions/coalesce/cannot-chain-tail-with-logical-or.js create mode 100644 js/src/tests/test262/language/expressions/coalesce/chainable-if-parenthesis-covered-logical-and.js create mode 100644 js/src/tests/test262/language/expressions/coalesce/chainable-if-parenthesis-covered-logical-or.js create mode 100644 js/src/tests/test262/language/expressions/coalesce/chainable-with-bitwise-and.js create mode 100644 js/src/tests/test262/language/expressions/coalesce/chainable-with-bitwise-or.js create mode 100644 js/src/tests/test262/language/expressions/coalesce/chainable-with-bitwise-xor.js create mode 100644 js/src/tests/test262/language/expressions/coalesce/chainable.js create mode 100644 js/src/tests/test262/language/expressions/coalesce/follows-null.js create mode 100644 js/src/tests/test262/language/expressions/coalesce/follows-undefined.js create mode 100644 js/src/tests/test262/language/expressions/coalesce/shell.js create mode 100644 js/src/tests/test262/language/expressions/coalesce/short-circuit-number-0.js create mode 100644 js/src/tests/test262/language/expressions/coalesce/short-circuit-number-42.js create mode 100644 js/src/tests/test262/language/expressions/coalesce/short-circuit-number-empty-string.js create mode 100644 js/src/tests/test262/language/expressions/coalesce/short-circuit-number-false.js create mode 100644 js/src/tests/test262/language/expressions/coalesce/short-circuit-number-object.js create mode 100644 js/src/tests/test262/language/expressions/coalesce/short-circuit-number-string.js create mode 100644 js/src/tests/test262/language/expressions/coalesce/short-circuit-number-symbol.js create mode 100644 js/src/tests/test262/language/expressions/coalesce/short-circuit-number-true.js create mode 100644 js/src/tests/test262/language/expressions/coalesce/short-circuit-prevents-evaluation.js create mode 100644 js/src/tests/test262/language/expressions/coalesce/tco-pos-null-strict.js create mode 100644 js/src/tests/test262/language/expressions/coalesce/tco-pos-undefined-strict.js create mode 100644 js/src/tests/test262/language/expressions/conditional/browser.js create mode 100644 js/src/tests/test262/language/expressions/conditional/coalesce-expr-ternary.js create mode 100644 js/src/tests/test262/language/expressions/conditional/shell.js (limited to 'js') diff --git a/js/src/builtin/ReflectParse.cpp b/js/src/builtin/ReflectParse.cpp index c4a5e8138..48437a1f1 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 b5a96f32d..e21ab7240 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 979af29b4..c3354e44a 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& parser, +FoldLogical(ExclusiveContext* cx, ParseNode** nodePtr, Parser& 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& 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& 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& 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 a36427d83..ebf83fb23 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 dfbd724e2..0b6278c7e 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 0ad0fe088..377eb999d 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 5202b7154..a66183b4a 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 @@ -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::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 MOZ_ALWAYS_INLINE typename ParseHandler::Node Parser::orExpr1(InHandling inHandling, YieldHandling yieldHandling, @@ -7942,6 +7962,7 @@ Parser::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::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 27da8ecf3..f11ceda33 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 8f9e206d9..b464b2304 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 fd85ec00e..53254718c 100644 --- a/js/src/jit/BaselineCompiler.cpp +++ b/js/src/jit/BaselineCompiler.cpp @@ -1252,6 +1252,25 @@ BaselineCompiler::emit_JSOP_IFNE() return emitTest(true); } +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) { diff --git a/js/src/jit/BaselineCompiler.h b/js/src/jit/BaselineCompiler.h index f975162a6..d3ab58aa4 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 ec3d35ff0..66e8e25dd 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -11517,6 +11517,24 @@ CodeGenerator::visitIsObjectAndBranch(LIsObjectAndBranch* ins) testObjectEmitBranch(Assembler::Equal, value, ins->ifTrue(), ins->ifFalse()); } +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) { diff --git a/js/src/jit/CodeGenerator.h b/js/src/jit/CodeGenerator.h index b8aecc8ba..ff09222f1 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 dbecec2a7..62470a2b8 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 402fbbf1a..a07020a4e 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 034715467..f143ff9e0 100644 --- a/js/src/jit/Lowering.cpp +++ b/js/src/jit/Lowering.cpp @@ -4162,6 +4162,16 @@ LIRGenerator::visitIsObject(MIsObject* ins) define(lir, 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) { diff --git a/js/src/jit/Lowering.h b/js/src/jit/Lowering.h index d5d0c3af2..5340fce76 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 a398ef334..ea00529e0 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 3185d0a83..c1644ba8b 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 62dc574b6..f97dc1534 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 d0651af1d..7f4cd770a 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 566b55479..51854fc39 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 b7c101107..a516df3a6 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 55d85a029..6ee208915 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 000000000..0ee491680 --- /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 000000000..fb04d2c0a --- /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 000000000..e69de29bb 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 000000000..7d59d2aee --- /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 000000000..8086667f1 --- /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 000000000..a85faef11 --- /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 000000000..352a96621 --- /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 000000000..edcf93b55 --- /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 000000000..79e9a6dd9 --- /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 000000000..dac7e04fc --- /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 000000000..214d4fd9a --- /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 000000000..a3b6ad4ab --- /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 000000000..09ad90452 --- /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 000000000..82a95419e --- /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 000000000..0728ed1ff --- /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 000000000..e69de29bb 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 000000000..547f62a1c --- /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 000000000..b30d905f2 --- /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 000000000..c513e2cbc --- /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 000000000..d23572939 --- /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 000000000..5f2eee11e --- /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 000000000..9979a685c --- /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 000000000..827c6e853 --- /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 000000000..9b24181ab --- /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 000000000..14cbd109e --- /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 000000000..9030e2227 --- /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 000000000..70f6ff59e --- /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 000000000..e69de29bb 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 000000000..543f22127 --- /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 000000000..e69de29bb diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 93542e4d7..56f715e2b 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 58b5d7577..8328dfc99 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) \ -- cgit v1.2.3