summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFranklinDM <mrmineshafter17@gmail.com>2022-04-24 21:05:43 +0800
committerFranklinDM <mrmineshafter17@gmail.com>2022-05-04 14:57:15 +0800
commite1fc99d6efdddb3ed0eef82281a8662bf3b1a344 (patch)
tree78472bd4926f00e983fa2a936b091d216b6e5792
parent0de24cc207b9fbb198d2eaf34e5bae21089d20bd (diff)
downloaduxp-e1fc99d6efdddb3ed0eef82281a8662bf3b1a344.tar.gz
Issue #1658 - Part 1: Implement support for optional chaining in the JS Parser
Partially based on: https://bugzilla.mozilla.org/show_bug.cgi?id=1566143
-rw-r--r--js/src/frontend/FoldConstants.cpp24
-rw-r--r--js/src/frontend/FullParseHandler.h29
-rw-r--r--js/src/frontend/ParseNode.cpp5
-rw-r--r--js/src/frontend/ParseNode.h110
-rw-r--r--js/src/frontend/Parser.cpp363
-rw-r--r--js/src/frontend/Parser.h17
-rw-r--r--js/src/frontend/SyntaxParseHandler.h15
-rw-r--r--js/src/frontend/TokenKind.h1
-rw-r--r--js/src/frontend/TokenStream.cpp21
-rw-r--r--js/src/js.msg2
10 files changed, 483 insertions, 104 deletions
diff --git a/js/src/frontend/FoldConstants.cpp b/js/src/frontend/FoldConstants.cpp
index c348056c45..d0e88581ea 100644
--- a/js/src/frontend/FoldConstants.cpp
+++ b/js/src/frontend/FoldConstants.cpp
@@ -319,6 +319,7 @@ ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result)
case PNK_DELETEPROP:
case PNK_DELETEELEM:
case PNK_DELETEEXPR:
+ case PNK_DELETEOPTCHAIN:
case PNK_POS:
case PNK_NEG:
case PNK_PREINCREMENT:
@@ -1304,7 +1305,7 @@ FoldElement(ExclusiveContext* cx, ParseNode** nodePtr, Parser<FullParseHandler>&
{
ParseNode* node = *nodePtr;
- MOZ_ASSERT(node->isKind(PNK_ELEM));
+ MOZ_ASSERT(node->isKind(PNK_ELEM) || node->isKind(PNK_OPTELEM));
MOZ_ASSERT(node->isArity(PN_BINARY));
ParseNode*& expr = node->pn_left;
@@ -1348,9 +1349,15 @@ FoldElement(ExclusiveContext* cx, ParseNode** nodePtr, Parser<FullParseHandler>&
// Optimization 3: We have expr["foo"] where foo is not an index. Convert
// to a property access (like expr.foo) that optimizes better downstream.
- ParseNode* dottedAccess = parser.handler.newPropertyAccess(expr, name, node->pn_pos.end);
- if (!dottedAccess)
+ ParseNode* dottedAccess;
+ if (node->isKind(PNK_OPTELEM)) {
+ dottedAccess = parser.handler.newOptionalPropertyAccess(expr, name, node->pn_pos.end);
+ } else {
+ dottedAccess = parser.handler.newPropertyAccess(expr, name, node->pn_pos.end);
+ }
+ if (!dottedAccess) {
return false;
+ }
dottedAccess->setInParens(node->isInParens());
ReplaceNode(nodePtr, dottedAccess);
@@ -1599,13 +1606,13 @@ static bool
FoldDottedProperty(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser,
bool inGenexpLambda)
{
- MOZ_ASSERT(node->isKind(PNK_DOT));
+ MOZ_ASSERT(node->isKind(PNK_DOT) || node->isKind(PNK_OPTDOT));
MOZ_ASSERT(node->isArity(PN_NAME));
// Iterate through a long chain of dotted property accesses to find the
// most-nested non-dotted property node, then fold that.
ParseNode** nested = &node->pn_expr;
- while ((*nested)->isKind(PNK_DOT)) {
+ while ((*nested)->isKind(PNK_DOT) || (*nested)->isKind(PNK_OPTDOT)) {
MOZ_ASSERT((*nested)->isArity(PN_NAME));
nested = &(*nested)->pn_expr;
}
@@ -1632,6 +1639,9 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>& parser, bo
JS_CHECK_RECURSION(cx, return false);
ParseNode* pn = *pnp;
+#ifdef DEBUG
+ ParseNodeKind kind = pn->getKind();
+#endif
switch (pn->getKind()) {
case PNK_NOP:
@@ -1713,6 +1723,8 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>& parser, bo
MOZ_ASSERT(pn->isArity(PN_BINARY));
return Fold(cx, &pn->pn_left, parser, inGenexpLambda);
+ case PNK_DELETEOPTCHAIN:
+ case PNK_OPTCHAIN:
case PNK_SEMI:
case PNK_THIS:
MOZ_ASSERT(pn->isArity(PN_UNARY));
@@ -1806,6 +1818,7 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>& parser, bo
case PNK_CLASS:
return FoldClass(cx, pn, parser, inGenexpLambda);
+ case PNK_OPTELEM:
case PNK_ELEM:
return FoldElement(cx, pnp, parser, inGenexpLambda);
@@ -1896,6 +1909,7 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>& parser, bo
MOZ_ASSERT(pn->isArity(PN_NAME));
return Fold(cx, &pn->pn_expr, parser, inGenexpLambda);
+ case PNK_OPTDOT:
case PNK_DOT:
return FoldDottedProperty(cx, pn, parser, inGenexpLambda);
diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h
index f346351255..a5a5bd16a0 100644
--- a/js/src/frontend/FullParseHandler.h
+++ b/js/src/frontend/FullParseHandler.h
@@ -213,6 +213,18 @@ class FullParseHandler
if (expr->isKind(PNK_ELEM))
return newUnary(PNK_DELETEELEM, JSOP_NOP, begin, expr);
+ if (expr->isKind(PNK_OPTCHAIN)) {
+ ParseNode* kid = expr->pn_kid;
+ // Handle property deletion explicitly. OptionalCall is handled
+ // via DeleteExpr.
+ if (kid->isKind(PNK_DOT) ||
+ kid->isKind(PNK_OPTDOT) ||
+ kid->isKind(PNK_ELEM) ||
+ kid->isKind(PNK_OPTELEM)) {
+ return newUnary(PNK_DELETEOPTCHAIN, JSOP_NOP, begin, kid);
+ }
+ }
+
return newUnary(PNK_DELETEEXPR, JSOP_NOP, begin, expr);
}
@@ -319,6 +331,10 @@ class FullParseHandler
return newList(PNK_CALL, JSOP_CALL);
}
+ ParseNode* newOptionalCall() {
+ return newList(PNK_OPTCALL, JSOP_CALL);
+ }
+
ParseNode* newTaggedTemplate() {
return newList(PNK_TAGGED_TEMPLATE, JSOP_CALL);
}
@@ -459,6 +475,11 @@ class FullParseHandler
return new_<UnaryNode>(PNK_AWAIT, JSOP_AWAIT, pos, value);
}
+ ParseNode* newOptionalChain(uint32_t begin, ParseNode* value) {
+ TokenPos pos(begin, value->pn_pos.end);
+ return new_<UnaryNode>(PNK_OPTCHAIN, JSOP_NOP, pos, value);
+ }
+
// Statements
ParseNode* newStatementList(const TokenPos& pos) {
@@ -678,6 +699,14 @@ class FullParseHandler
return new_<PropertyByValue>(lhs, index, lhs->pn_pos.begin, end);
}
+ ParseNode* newOptionalPropertyAccess(ParseNode* pn, PropertyName* name, uint32_t end) {
+ return new_<OptionalPropertyAccess>(pn, name, pn->pn_pos.begin, end);
+ }
+
+ ParseNode* newOptionalPropertyByValue(ParseNode* lhs, ParseNode* index, uint32_t end) {
+ return new_<OptionalPropertyByValue>(lhs, index, lhs->pn_pos.begin, end);
+ }
+
inline MOZ_MUST_USE bool addCatchBlock(ParseNode* catchList, ParseNode* lexicalScope,
ParseNode* catchName, ParseNode* catchGuard,
ParseNode* catchBody);
diff --git a/js/src/frontend/ParseNode.cpp b/js/src/frontend/ParseNode.cpp
index 6233465630..dfbd724e2e 100644
--- a/js/src/frontend/ParseNode.cpp
+++ b/js/src/frontend/ParseNode.cpp
@@ -228,6 +228,8 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack)
return PushUnaryNodeChild(pn, stack);
// Nodes with a single nullable child.
+ case PNK_OPTCHAIN:
+ case PNK_DELETEOPTCHAIN:
case PNK_THIS:
case PNK_SEMI: {
MOZ_ASSERT(pn->isArity(PN_UNARY));
@@ -253,6 +255,7 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack)
case PNK_MODASSIGN:
case PNK_POWASSIGN:
// ...and a few others.
+ case PNK_OPTELEM:
case PNK_ELEM:
case PNK_IMPORT_SPEC:
case PNK_EXPORT_SPEC:
@@ -449,6 +452,7 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack)
case PNK_POW:
case PNK_COMMA:
case PNK_NEW:
+ case PNK_OPTCALL:
case PNK_CALL:
case PNK_SUPERCALL:
case PNK_GENEXP:
@@ -483,6 +487,7 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack)
}
case PNK_LABEL:
+ case PNK_OPTDOT:
case PNK_DOT:
case PNK_NAME:
return PushNameNodeChildren(pn, stack);
diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h
index 8e5e74d3b7..aae2a691bb 100644
--- a/js/src/frontend/ParseNode.h
+++ b/js/src/frontend/ParseNode.h
@@ -34,6 +34,10 @@ class ObjectBox;
F(POSTDECREMENT) \
F(DOT) \
F(ELEM) \
+ F(OPTDOT) \
+ F(OPTCHAIN) \
+ F(OPTELEM) \
+ F(OPTCALL) \
F(ARRAY) \
F(ELISION) \
F(STATEMENTLIST) \
@@ -76,6 +80,7 @@ class ObjectBox;
F(DELETEPROP) \
F(DELETEELEM) \
F(DELETEEXPR) \
+ F(DELETEOPTCHAIN) \
F(TRY) \
F(CATCH) \
F(CATCHLIST) \
@@ -371,6 +376,29 @@ IsTypeofKind(ParseNodeKind kind)
* for a more-specific PNK_DELETE* unless constant
* folding (or a similar parse tree manipulation) has
* occurred
+ * PNK_DELETEOPTCHAIN unary pn_kid: MEMBER expr that's evaluated, then the
+ * overall delete evaluates to true; If constant
+ * folding occurs, PNK_ELEM may become PNK_DOT.
+ * PNK_OPTELEM does not get folded into PNK_OPTDOT.
+ * PNK_OPTCHAIN unary pn_kid: expression that is evaluated as a chain.
+ * Contains one or more optional nodes. Its first node
+ * is always an optional node, such as PNK_OPTELEM,
+ * PNK_OPTDOT, or PNK_OPTCALL. This will shortcircuit
+ * and return 'undefined' without evaluating the rest
+ * of the expression if any of the optional nodes it
+ * contains are nullish. An optional chain can also
+ * contain nodes such as PNK_DOT, PNK_ELEM, PNK_NAME,
+ * PNK_CALL, etc. These are evaluated normally.
+ * PNK_OPTDOT name pn_expr: MEMBER expr to left of .
+ * short circuits back to PNK_OPTCHAIN if nullish.
+ * pn_atom: name to right of .
+ * PNK_OPTELEM binary pn_left: MEMBER expr to left of [
+ * short circuits back to PNK_OPTCHAIN if nullish.
+ * pn_right: expr between [ and ]
+ * PNK_OPTCALL list pn_head: list of call, arg1, arg2, ... argN
+ * pn_count: 1 + N (where N is number of args)
+ * call is a MEMBER expr naming a callable object,
+ * short circuits back to PNK_OPTCHAIN if nullish.
* PNK_DOT name pn_expr: MEMBER expr to left of .
* pn_atom: name to right of .
* PNK_ELEM binary pn_left: MEMBER expr to left of [
@@ -1182,11 +1210,12 @@ class RegExpLiteral : public NullaryNode
}
};
-class PropertyAccess : public ParseNode
+class PropertyAccessBase : public ParseNode
{
public:
- PropertyAccess(ParseNode* lhs, PropertyName* name, uint32_t begin, uint32_t end)
- : ParseNode(PNK_DOT, JSOP_NOP, PN_NAME, TokenPos(begin, end))
+ PropertyAccessBase(ParseNodeKind kind, ParseNode* lhs, PropertyName* name,
+ uint32_t begin, uint32_t end)
+ : ParseNode(kind, JSOP_NOP, PN_NAME, TokenPos(begin, end))
{
MOZ_ASSERT(lhs != nullptr);
MOZ_ASSERT(name != nullptr);
@@ -1195,7 +1224,8 @@ class PropertyAccess : public ParseNode
}
static bool test(const ParseNode& node) {
- bool match = node.isKind(PNK_DOT);
+ bool match = node.isKind(PNK_DOT) ||
+ node.isKind(PNK_OPTDOT);
MOZ_ASSERT_IF(match, node.isArity(PN_NAME));
return match;
}
@@ -1214,18 +1244,56 @@ class PropertyAccess : public ParseNode
}
};
-class PropertyByValue : public ParseNode
+class PropertyAccess : public PropertyAccessBase
+{
+ public:
+ PropertyAccess(ParseNode* lhs, PropertyName* name, uint32_t begin,
+ uint32_t end)
+ : PropertyAccessBase(PNK_DOT, lhs, name, begin, end)
+ {
+ MOZ_ASSERT(lhs != nullptr);
+ MOZ_ASSERT(name != nullptr);
+ }
+
+ static bool test(const ParseNode& node) {
+ bool match = node.isKind(PNK_DOT);
+ MOZ_ASSERT_IF(match, node.isArity(PN_NAME));
+ return match;
+ }
+};
+
+class OptionalPropertyAccess : public PropertyAccessBase
+{
+ public:
+ OptionalPropertyAccess(ParseNode* lhs, PropertyName* name, uint32_t begin,
+ uint32_t end)
+ : PropertyAccessBase(PNK_OPTDOT, lhs, name, begin, end)
+ {
+ MOZ_ASSERT(lhs != nullptr);
+ MOZ_ASSERT(name != nullptr);
+ }
+
+ static bool test(const ParseNode& node) {
+ bool match = node.isKind(PNK_OPTDOT);
+ MOZ_ASSERT_IF(match, node.isArity(PN_NAME));
+ return match;
+ }
+};
+
+class PropertyByValueBase : public ParseNode
{
public:
- PropertyByValue(ParseNode* lhs, ParseNode* propExpr, uint32_t begin, uint32_t end)
- : ParseNode(PNK_ELEM, JSOP_NOP, PN_BINARY, TokenPos(begin, end))
+ PropertyByValueBase(ParseNodeKind kind, ParseNode* lhs, ParseNode* propExpr,
+ uint32_t begin, uint32_t end)
+ : ParseNode(kind, JSOP_NOP, PN_BINARY, TokenPos(begin, end))
{
pn_u.binary.left = lhs;
pn_u.binary.right = propExpr;
}
static bool test(const ParseNode& node) {
- bool match = node.isKind(PNK_ELEM);
+ bool match = node.isKind(PNK_ELEM) ||
+ node.isKind(PNK_OPTELEM);
MOZ_ASSERT_IF(match, node.isArity(PN_BINARY));
return match;
}
@@ -1235,6 +1303,32 @@ class PropertyByValue : public ParseNode
}
};
+class PropertyByValue : public PropertyByValueBase {
+ public:
+ PropertyByValue(ParseNode* lhs, ParseNode* propExpr, uint32_t begin,
+ uint32_t end)
+ : PropertyByValueBase(PNK_ELEM, lhs, propExpr, begin, end) {}
+
+ static bool test(const ParseNode& node) {
+ bool match = node.isKind(PNK_ELEM);
+ MOZ_ASSERT_IF(match, node.isArity(PN_BINARY));
+ return match;
+ }
+};
+
+class OptionalPropertyByValue : public PropertyByValueBase {
+ public:
+ OptionalPropertyByValue(ParseNode* lhs, ParseNode* propExpr,
+ uint32_t begin, uint32_t end)
+ : PropertyByValueBase(PNK_OPTELEM, lhs, propExpr, begin, end) {}
+
+ static bool test(const ParseNode& node) {
+ bool match = node.isKind(PNK_OPTELEM);
+ MOZ_ASSERT_IF(match, node.isArity(PN_BINARY));
+ return match;
+ }
+};
+
/*
* A CallSiteNode represents the implicit call site object argument in a TaggedTemplate.
*/
diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp
index 38bd77ead3..cced4a4ea9 100644
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -8363,6 +8363,109 @@ Parser<ParseHandler>::unaryOpExpr(YieldHandling yieldHandling, ParseNodeKind kin
template <typename ParseHandler>
typename ParseHandler::Node
+Parser<ParseHandler>::optionalExpr(
+ YieldHandling yieldHandling, TripledotHandling tripledotHandling,
+ TokenKind tt, bool allowCallSyntax /* = true */,
+ PossibleError* possibleError /* = nullptr */,
+ InvokedPrediction invoked /* = PredictUninvoked */)
+{
+ JS_CHECK_RECURSION(context, return null());
+
+ uint32_t begin = pos().begin;
+
+ Node lhs = memberExpr(yieldHandling, tripledotHandling, tt,
+ /* allowCallSyntax = */ true, possibleError, invoked);
+
+ if (!lhs) {
+ return null();
+ }
+
+ // Note: TokenStream::None is equivalent to TokenStream::SlashIsDiv
+ // in current Mozilla code, see bug 1539821.
+ if (!tokenStream.peekToken(&tt, TokenStream::None)) {
+ return null();
+ }
+
+ if (tt == TOK_EOF || tt != TOK_OPTCHAIN) {
+ return lhs;
+ }
+
+ while (true) {
+ if (!tokenStream.getToken(&tt)) {
+ return null();
+ }
+
+ if (tt == TOK_EOF) {
+ break;
+ }
+
+ Node nextMember;
+ if (tt == TOK_OPTCHAIN) {
+ if (!tokenStream.getToken(&tt)) {
+ return null();
+ }
+ if (TokenKindIsPossibleIdentifierName(tt)) {
+ nextMember = memberPropertyAccess(lhs, OptionalKind::Optional);
+ if (!nextMember) {
+ return null();
+ }
+ } else if (tt == TOK_LB) {
+ nextMember = memberElemAccess(lhs, yieldHandling,
+ OptionalKind::Optional);
+ if (!nextMember) {
+ return null();
+ }
+ } else if (tt == TOK_LP) {
+ nextMember = memberCall(tt, lhs, yieldHandling, possibleError,
+ OptionalKind::Optional);
+ if (!nextMember) {
+ return null();
+ }
+ } else {
+ error(JSMSG_NAME_AFTER_DOT);
+ return null();
+ }
+ } else if (tt == TOK_DOT) {
+ if (!tokenStream.getToken(&tt)) {
+ return null();
+ }
+ if (TokenKindIsPossibleIdentifierName(tt)) {
+ nextMember = memberPropertyAccess(lhs);
+ if (!nextMember) {
+ return null();
+ }
+ } else {
+ error(JSMSG_NAME_AFTER_DOT);
+ return null();
+ }
+ } else if (tt == TOK_LB) {
+ nextMember = memberElemAccess(lhs, yieldHandling);
+ if (!nextMember) {
+ return null();
+ }
+ } else if (allowCallSyntax && tt == TOK_LP) {
+ nextMember = memberCall(tt, lhs, yieldHandling, possibleError);
+ if (!nextMember) {
+ return null();
+ }
+ } else if (tt == TOK_TEMPLATE_HEAD || tt == TOK_NO_SUBS_TEMPLATE) {
+ error(JSMSG_BAD_OPTIONAL_TEMPLATE);
+ return null();
+ } else {
+ tokenStream.ungetToken();
+ break;
+ }
+
+ if (nextMember) {
+ lhs = nextMember;
+ }
+ }
+
+ return handler.newOptionalChain(begin, lhs);
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
Parser<ParseHandler>::unaryExpr(YieldHandling yieldHandling, TripledotHandling tripledotHandling,
PossibleError* possibleError /* = nullptr */,
InvokedPrediction invoked /* = PredictUninvoked */)
@@ -8454,8 +8557,9 @@ Parser<ParseHandler>::unaryExpr(YieldHandling yieldHandling, TripledotHandling t
MOZ_FALLTHROUGH;
default: {
- Node expr = memberExpr(yieldHandling, tripledotHandling, tt, /* allowCallSyntax = */ true,
- possibleError, invoked);
+ Node expr = optionalExpr(yieldHandling, tripledotHandling, tt,
+ /* allowCallSyntax = */ true,
+ possibleError, invoked);
if (!expr)
return null();
@@ -8904,6 +9008,18 @@ Parser<ParseHandler>::memberExpr(YieldHandling yieldHandling, TripledotHandling
handler.addList(lhs, ctorExpr);
+ // If we have encountered an optional chain, in the form of `new
+ // ClassName?.()` then we need to throw, as this is disallowed
+ // by the spec.
+ bool optionalToken;
+ if (!tokenStream.matchToken(&optionalToken, TOK_OPTCHAIN)) {
+ return null();
+ }
+ if (optionalToken) {
+ errorAt(newBegin, JSMSG_BAD_NEW_OPTIONAL);
+ return null();
+ }
+
bool matched;
if (!tokenStream.matchToken(&matched, TOK_LP))
return null();
@@ -8941,12 +9057,7 @@ Parser<ParseHandler>::memberExpr(YieldHandling yieldHandling, TripledotHandling
if (!tokenStream.getToken(&tt))
return null();
if (TokenKindIsPossibleIdentifierName(tt)) {
- PropertyName* field = tokenStream.currentName();
- if (handler.isSuperBase(lhs) && !checkAndMarkSuperScope()) {
- error(JSMSG_BAD_SUPERPROP, "property");
- return null();
- }
- nextMember = handler.newPropertyAccess(lhs, field, pos().end);
+ nextMember = memberPropertyAccess(lhs);
if (!nextMember)
return null();
} else {
@@ -8954,17 +9065,7 @@ Parser<ParseHandler>::memberExpr(YieldHandling yieldHandling, TripledotHandling
return null();
}
} else if (tt == TOK_LB) {
- Node propExpr = expr(InAllowed, yieldHandling, TripledotProhibited);
- if (!propExpr)
- return null();
-
- MUST_MATCH_TOKEN(TOK_RB, JSMSG_BRACKET_IN_INDEX);
-
- if (handler.isSuperBase(lhs) && !checkAndMarkSuperScope()) {
- error(JSMSG_BAD_SUPERPROP, "member");
- return null();
- }
- nextMember = handler.newPropertyByValue(lhs, propExpr, pos().end);
+ nextMember = memberElemAccess(lhs, yieldHandling);
if (!nextMember)
return null();
} else if ((allowCallSyntax && tt == TOK_LP) ||
@@ -9000,82 +9101,17 @@ Parser<ParseHandler>::memberExpr(YieldHandling yieldHandling, TripledotHandling
if (!thisName)
return null();
+ // XXX This was refactored out into memberSuperCall as part of
+ // bug 1566143, but was not used elsewhere aside from here, so
+ // I opted not to move this into a separate function. However,
+ // I'm unsure if this will be needed eventually in the future.
nextMember = handler.newSetThis(thisName, nextMember);
if (!nextMember)
return null();
} else {
- if (options().selfHostingMode && handler.isPropertyAccess(lhs)) {
- error(JSMSG_SELFHOSTED_METHOD_CALL);
- return null();
- }
-
- nextMember = tt == TOK_LP ? handler.newCall() : handler.newTaggedTemplate();
+ nextMember = memberCall(tt, lhs, yieldHandling, possibleError);
if (!nextMember)
return null();
-
- JSOp op = JSOP_CALL;
- bool maybeAsyncArrow = false;
- if (PropertyName* prop = handler.maybeDottedProperty(lhs)) {
- // Use the JSOP_FUN{APPLY,CALL} optimizations given the
- // right syntax.
- if (prop == context->names().apply) {
- op = JSOP_FUNAPPLY;
- if (pc->isFunctionBox()) {
- pc->functionBox()->usesApply = true;
- }
- } else if (prop == context->names().call) {
- op = JSOP_FUNCALL;
- }
- } else if (tt == TOK_LP) {
- if (handler.isAsyncKeyword(lhs, context)) {
- // |async (| can be the start of an async arrow
- // function, so we need to defer reporting possible
- // errors from destructuring syntax. To give better
- // error messages, we only allow the AsyncArrowHead
- // part of the CoverCallExpressionAndAsyncArrowHead
- // syntax when the initial name is "async".
- maybeAsyncArrow = true;
- } else if (handler.isEvalAnyParentheses(lhs, context)) {
- // Select the right EVAL op and flag pc as having a
- // direct eval.
- op = pc->sc()->strict() ? JSOP_STRICTEVAL : JSOP_EVAL;
- pc->sc()->setBindingsAccessedDynamically();
- pc->sc()->setHasDirectEval();
-
- // In non-strict mode code, direct calls to eval can
- // add variables to the call object.
- if (pc->isFunctionBox() && !pc->sc()->strict())
- pc->functionBox()->setHasExtensibleScope();
-
- // If we're in a method, mark the method as requiring
- // support for 'super', since direct eval code can use
- // it. (If we're not in a method, that's fine, so
- // ignore the return value.)
- checkAndMarkSuperScope();
- }
- }
-
- handler.setBeginPosition(nextMember, lhs);
- handler.addList(nextMember, lhs);
-
- if (tt == TOK_LP) {
- bool isSpread = false;
- PossibleError* asyncPossibleError = maybeAsyncArrow ? possibleError : nullptr;
- if (!argumentList(yieldHandling, nextMember, &isSpread, asyncPossibleError))
- return null();
- if (isSpread) {
- if (op == JSOP_EVAL)
- op = JSOP_SPREADEVAL;
- else if (op == JSOP_STRICTEVAL)
- op = JSOP_STRICTSPREADEVAL;
- else
- op = JSOP_SPREADCALL;
- }
- } else {
- if (!taggedTemplate(yieldHandling, nextMember, tt))
- return null();
- }
- handler.setOp(nextMember, op);
}
} else {
tokenStream.ungetToken();
@@ -9109,6 +9145,155 @@ Parser<ParseHandler>::newName(PropertyName* name, TokenPos pos)
return handler.newName(name, pos, context);
}
+template <class ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::memberPropertyAccess(
+ Node lhs, OptionalKind optionalKind /* = OptionalKind::NonOptional */)
+{
+ MOZ_ASSERT(TokenKindIsPossibleIdentifierName(tokenStream.currentToken().type));
+ PropertyName* field = tokenStream.currentName();
+ if (handler.isSuperBase(lhs) && !checkAndMarkSuperScope()) {
+ error(JSMSG_BAD_SUPERPROP, "property");
+ return null();
+ }
+ if (optionalKind == OptionalKind::Optional) {
+ return handler.newOptionalPropertyAccess(lhs, field, pos().end);
+ }
+ return handler.newPropertyAccess(lhs, field, pos().end);
+}
+
+template <class ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::memberElemAccess(
+ Node lhs, YieldHandling yieldHandling,
+ OptionalKind optionalKind /* = OptionalKind::NonOptional */)
+{
+ MOZ_ASSERT(tokenStream.currentToken().type == TOK_LB);
+ Node propExpr = expr(InAllowed, yieldHandling, TripledotProhibited);
+ if (!propExpr) {
+ return null();
+ }
+
+ MUST_MATCH_TOKEN(TOK_RB, JSMSG_BRACKET_IN_INDEX);
+
+ if (handler.isSuperBase(lhs) && !checkAndMarkSuperScope()) {
+ error(JSMSG_BAD_SUPERPROP, "member");
+ return null();
+ }
+
+ if (optionalKind == OptionalKind::Optional) {
+ return handler.newOptionalPropertyByValue(lhs, propExpr, pos().end);
+ }
+ return handler.newPropertyByValue(lhs, propExpr, pos().end);
+}
+
+template <class ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::memberCall(
+ TokenKind tt, Node lhs, YieldHandling yieldHandling,
+ PossibleError* possibleError /* = nullptr */,
+ OptionalKind optionalKind /* = OptionalKind::NonOptional */)
+{
+ if (options().selfHostingMode && handler.isPropertyAccess(lhs)) {
+ error(JSMSG_SELFHOSTED_METHOD_CALL);
+ return null();
+ }
+
+ MOZ_ASSERT(tt == TOK_LP ||
+ tt == TOK_TEMPLATE_HEAD ||
+ tt == TOK_NO_SUBS_TEMPLATE,
+ "Unexpected token kind for member call");
+
+ Node nextMember;
+ if (tt == TOK_LP) {
+ if (optionalKind == OptionalKind::Optional) {
+ nextMember = handler.newOptionalCall();
+ } else {
+ nextMember = handler.newCall();
+ }
+ } else {
+ nextMember = handler.newTaggedTemplate();
+ }
+
+ JSOp op = JSOP_CALL;
+ bool maybeAsyncArrow = false;
+ if (PropertyName* prop = handler.maybeDottedProperty(lhs)) {
+ // Use the JSOP_FUN{APPLY,CALL} optimizations given the
+ // right syntax.
+ if (prop == context->names().apply) {
+ op = JSOP_FUNAPPLY;
+ if (pc->isFunctionBox()) {
+ pc->functionBox()->usesApply = true;
+ }
+ } else if (prop == context->names().call) {
+ op = JSOP_FUNCALL;
+ }
+ } else if (tt == TOK_LP && optionalKind == OptionalKind::NonOptional) {
+ if (handler.isAsyncKeyword(lhs, context)) {
+ // |async (| can be the start of an async arrow
+ // function, so we need to defer reporting possible
+ // errors from destructuring syntax. To give better
+ // error messages, we only allow the AsyncArrowHead
+ // part of the CoverCallExpressionAndAsyncArrowHead
+ // syntax when the initial name is "async".
+ maybeAsyncArrow = true;
+ } else if (handler.isEvalAnyParentheses(lhs, context)) {
+ // Select the right EVAL op and flag pc as having a
+ // direct eval.
+ op = pc->sc()->strict() ? JSOP_STRICTEVAL : JSOP_EVAL;
+ pc->sc()->setBindingsAccessedDynamically();
+ pc->sc()->setHasDirectEval();
+
+ // In non-strict mode code, direct calls to eval can
+ // add variables to the call object.
+ if (pc->isFunctionBox() && !pc->sc()->strict()) {
+ pc->functionBox()->setHasExtensibleScope();
+ }
+
+ // If we're in a method, mark the method as requiring
+ // support for 'super', since direct eval code can use
+ // it. (If we're not in a method, that's fine, so
+ // ignore the return value.)
+ checkAndMarkSuperScope();
+ }
+ }
+
+ handler.setBeginPosition(nextMember, lhs);
+ handler.addList(nextMember, lhs);
+
+ if (tt == TOK_LP) {
+ bool isSpread = false;
+ PossibleError* asyncPossibleError = maybeAsyncArrow ?
+ possibleError :
+ nullptr;
+
+ if (!argumentList(yieldHandling, nextMember, &isSpread, asyncPossibleError)) {
+ return null();
+ }
+
+ if (isSpread) {
+ if (op == JSOP_EVAL) {
+ op = JSOP_SPREADEVAL;
+ } else if (op == JSOP_STRICTEVAL) {
+ op = JSOP_STRICTSPREADEVAL;
+ } else {
+ op = JSOP_SPREADCALL;
+ }
+ }
+ } else {
+ if (!taggedTemplate(yieldHandling, nextMember, tt)) {
+ return null();
+ }
+ if (optionalKind == OptionalKind::Optional) {
+ error(JSMSG_BAD_OPTIONAL_TEMPLATE);
+ return null();
+ }
+ }
+ handler.setOp(nextMember, op);
+
+ return nextMember;
+}
+
template <typename ParseHandler>
bool
Parser<ParseHandler>::checkLabelOrIdentifierReference(HandlePropertyName ident,
diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h
index 0e2f28fe88..97f6917bd6 100644
--- a/js/src/frontend/Parser.h
+++ b/js/src/frontend/Parser.h
@@ -1321,6 +1321,11 @@ class Parser final : public ParserBase, private JS::AutoGCRooter
Node unaryExpr(YieldHandling yieldHandling, TripledotHandling tripledotHandling,
PossibleError* possibleError = nullptr,
InvokedPrediction invoked = PredictUninvoked);
+ Node optionalExpr(YieldHandling yieldHandling,
+ TripledotHandling tripledotHandling, TokenKind tt,
+ bool allowCallSyntax = true,
+ PossibleError* possibleError = nullptr,
+ InvokedPrediction invoked = PredictUninvoked);
Node memberExpr(YieldHandling yieldHandling, TripledotHandling tripledotHandling,
TokenKind tt, bool allowCallSyntax = true,
PossibleError* possibleError = nullptr,
@@ -1532,6 +1537,18 @@ class Parser final : public ParserBase, private JS::AutoGCRooter
JSAtom* prefixAccessorName(PropertyType propType, HandleAtom propAtom);
bool asmJS(Node list);
+
+ enum class OptionalKind {
+ NonOptional = 0,
+ Optional,
+ };
+ Node memberPropertyAccess(
+ Node lhs, OptionalKind optionalKind = OptionalKind::NonOptional);
+ Node memberElemAccess(Node lhs, YieldHandling yieldHandling,
+ OptionalKind optionalKind = OptionalKind::NonOptional);
+ Node memberCall(TokenKind tt, Node lhs, YieldHandling yieldHandling,
+ PossibleError* possibleError,
+ OptionalKind optionalKind = OptionalKind::NonOptional);
};
template <typename ParseHandler>
diff --git a/js/src/frontend/SyntaxParseHandler.h b/js/src/frontend/SyntaxParseHandler.h
index 4fef3584c8..c55db41086 100644
--- a/js/src/frontend/SyntaxParseHandler.h
+++ b/js/src/frontend/SyntaxParseHandler.h
@@ -58,6 +58,7 @@ class SyntaxParseHandler
// in code not actually executed (or at least not executed enough to be
// noticed).
NodeFunctionCall,
+ NodeOptionalFunctionCall,
// Nodes representing *parenthesized* IsValidSimpleAssignmentTarget
// nodes. We can't simply treat all such parenthesized nodes
@@ -78,7 +79,9 @@ class SyntaxParseHandler
NodeParenthesizedName,
NodeDottedProperty,
+ NodeOptionalDottedProperty,
NodeElement,
+ NodeOptionalElement,
// Destructuring target patterns can't be parenthesized: |([a]) = [3];|
// must be a syntax error. (We can't use NodeGeneric instead of these
@@ -283,6 +286,7 @@ class SyntaxParseHandler
void addArrayElement(Node literal, Node element) { }
Node newCall() { return NodeFunctionCall; }
+ Node newOptionalCall() { return NodeOptionalFunctionCall; }
Node newTaggedTemplate() { return NodeGeneric; }
Node newObjectLiteral(uint32_t begin) { return NodeUnparenthesizedObject; }
@@ -303,6 +307,7 @@ class SyntaxParseHandler
Node newYieldExpression(uint32_t begin, Node value) { return NodeGeneric; }
Node newYieldStarExpression(uint32_t begin, Node value) { return NodeGeneric; }
Node newAwaitExpression(uint32_t begin, Node value) { return NodeGeneric; }
+ Node newOptionalChain(uint32_t begin, Node value) { return NodeGeneric; }
// Statements
@@ -353,8 +358,15 @@ class SyntaxParseHandler
return NodeDottedProperty;
}
+ Node newOptionalPropertyAccess(Node pn, PropertyName* name, uint32_t end) {
+ lastAtom = name;
+ return NodeOptionalDottedProperty;
+ }
+
Node newPropertyByValue(Node pn, Node kid, uint32_t end) { return NodeElement; }
+ Node newOptionalPropertyByValue(Node pn, Node kid, uint32_t end) { return NodeOptionalElement; }
+
MOZ_MUST_USE bool addCatchBlock(Node catchList, Node letBlock, Node catchName,
Node catchGuard, Node catchBody) { return true; }
@@ -471,7 +483,8 @@ class SyntaxParseHandler
list == NodeUnparenthesizedCommaExpr ||
list == NodeVarDeclaration ||
list == NodeLexicalDeclaration ||
- list == NodeFunctionCall);
+ list == NodeFunctionCall ||
+ list == NodeOptionalFunctionCall);
}
Node newAssignment(ParseNodeKind kind, Node lhs, Node rhs, JSOp op) {
diff --git a/js/src/frontend/TokenKind.h b/js/src/frontend/TokenKind.h
index f4e1afa328..27da8ecf3e 100644
--- a/js/src/frontend/TokenKind.h
+++ b/js/src/frontend/TokenKind.h
@@ -63,6 +63,7 @@
macro(DEC, "'--'") /* decrement */ \
macro(DOT, "'.'") /* member operator */ \
macro(TRIPLEDOT, "'...'") /* rest arguments and spread operator */ \
+ macro(OPTCHAIN, "'?.'") \
macro(LB, "'['") \
macro(RB, "']'") \
macro(LC, "'{'") \
diff --git a/js/src/frontend/TokenStream.cpp b/js/src/frontend/TokenStream.cpp
index 083bcd5045..8f9e206d9f 100644
--- a/js/src/frontend/TokenStream.cpp
+++ b/js/src/frontend/TokenStream.cpp
@@ -1285,7 +1285,7 @@ static const uint8_t firstCharKinds[] = {
/* 30+ */ _______, _______, Space, _______, String, _______, Ident, _______, _______, String,
/* 40+ */ TOK_LP, TOK_RP, _______, _______, T_COMMA,_______, _______, _______,BasePrefix, Dec,
/* 50+ */ Dec, Dec, Dec, Dec, Dec, Dec, Dec, Dec, T_COLON,TOK_SEMI,
-/* 60+ */ _______, _______, _______,TOK_HOOK, _______, Ident, Ident, Ident, Ident, Ident,
+/* 60+ */ _______, _______, _______, _______, _______, Ident, Ident, Ident, Ident, Ident,
/* 70+ */ Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident,
/* 80+ */ Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident,
/* 90+ */ Ident, TOK_LB, _______, TOK_RB, _______, Ident, Templat, Ident, Ident, Ident,
@@ -1796,6 +1796,25 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier)
tp->type = matchChar('=') ? TOK_BITANDASSIGN : TOK_BITAND;
goto out;
+ case '?':
+ if (matchChar('.')) {
+ c = getCharIgnoreEOL();
+ if (JS7_ISDEC(c)) {
+ // if the code unit is followed by a number, for example it has
+ // the following form `<...> ?.5 <..> then it should be treated
+ // as a ternary rather than as an optional chain
+ tp->type = TOK_HOOK;
+ ungetCharIgnoreEOL(c);
+ ungetChar('.');
+ } else {
+ ungetCharIgnoreEOL(c);
+ tp->type = TOK_OPTCHAIN;
+ }
+ } else {
+ tp->type = TOK_HOOK;
+ }
+ goto out;
+
case '!':
if (matchChar('='))
tp->type = matchChar('=') ? TOK_STRICTNE : TOK_NE;
diff --git a/js/src/js.msg b/js/src/js.msg
index b03da4153d..566b55479b 100644
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -354,6 +354,8 @@ MSG_DEF(JSMSG_BAD_COLUMN_NUMBER, 0, JSEXN_RANGEERR, "column number out of
MSG_DEF(JSMSG_COMPUTED_NAME_IN_PATTERN,0, JSEXN_SYNTAXERR, "computed property names aren't supported in this destructuring declaration")
MSG_DEF(JSMSG_DEFAULT_IN_PATTERN, 0, JSEXN_SYNTAXERR, "destructuring defaults aren't supported in this destructuring declaration")
MSG_DEF(JSMSG_BAD_NEWTARGET, 0, JSEXN_SYNTAXERR, "new.target only allowed within functions")
+MSG_DEF(JSMSG_BAD_NEW_OPTIONAL, 0, JSEXN_SYNTAXERR, "new keyword cannot be used with an optional chain")
+MSG_DEF(JSMSG_BAD_OPTIONAL_TEMPLATE, 0, JSEXN_SYNTAXERR, "tagged template cannot be used with optional chain")
MSG_DEF(JSMSG_ESCAPED_KEYWORD, 0, JSEXN_SYNTAXERR, "keywords must be written literally, without embedded escapes")
// asm.js