summaryrefslogtreecommitdiff
path: root/js/src/frontend
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/frontend')
-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
8 files changed, 141 insertions, 56 deletions
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;