mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-03 22:38:41 +09:00
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
This commit is contained in:
parent
9816e341b2
commit
e04a23ff98
10 changed files with 483 additions and 104 deletions
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
PropertyByValue(ParseNode* lhs, ParseNode* propExpr, uint32_t begin, uint32_t end)
|
||||
: ParseNode(PNK_ELEM, JSOP_NOP, PN_BINARY, TokenPos(begin, end))
|
||||
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:
|
||||
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.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -8361,6 +8361,109 @@ Parser<ParseHandler>::unaryOpExpr(YieldHandling yieldHandling, ParseNodeKind kin
|
|||
return handler.newUnary(kind, op, begin, kid);
|
||||
}
|
||||
|
||||
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,
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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, "'{'") \
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue