in lib/Parser/JSParserImpl.cpp [5459:5768]
Optional<ESTree::Node *> JSParserImpl::parseAssignmentExpression(
Param param,
AllowTypedArrowFunction allowTypedArrowFunction,
CoverTypedParameters coverTypedParameters,
ESTree::Node *typeParams) {
struct State {
SMLoc leftStartLoc = {};
Optional<ESTree::Node *> optLeftExpr = llvh::None;
UniqueString *op = nullptr;
SMLoc debugLoc = {};
explicit State() {}
};
auto parseHelper = [this](
State &state,
Param param,
AllowTypedArrowFunction allowTypedArrowFunction,
CoverTypedParameters coverTypedParameters,
ESTree::Node *typeParams) -> Optional<ESTree::Node *> {
// Check for yield, which may be lexed as a reserved word, but only in
// strict mode.
if (paramYield_ && check(TokenKind::rw_yield, TokenKind::identifier) &&
tok_->getResWordOrIdentifier() == yieldIdent_) {
auto optYieldExpr = parseYieldExpression(param.get(ParamIn));
if (!optYieldExpr)
return None;
ESTree::YieldExpressionNode *yieldExpr = *optYieldExpr;
if (yieldExpr->_argument && !checkEndAssignmentExpression()) {
error(tok_->getStartLoc(), "unexpected token after yield expression");
return None;
}
return yieldExpr;
}
SMLoc startLoc = tok_->getStartLoc();
bool forceAsync = false;
if (check(asyncIdent_)) {
OptValue<TokenKind> optNext = lexer_.lookahead1(TokenKind::identifier);
if (optNext.hasValue() && *optNext == TokenKind::identifier) {
forceAsync = true;
}
#if HERMES_PARSE_FLOW
if (context_.getParseFlow() && optNext.hasValue() &&
(*optNext == TokenKind::less || *optNext == TokenKind::l_paren)) {
auto optAsyncArrow = tryParseTypedAsyncArrowFunction(param);
if (optAsyncArrow.hasValue()) {
return *optAsyncArrow;
}
}
#endif
}
#if HERMES_PARSE_FLOW
if (context_.getParseFlow() &&
allowTypedArrowFunction == AllowTypedArrowFunction::Yes &&
!typeParams && check(TokenKind::less)) {
JSLexer::SavePoint savePoint{&lexer_};
// Suppress messages from the parser while still displaying lexer
// messages.
CollectMessagesRAII collect{&sm_, true};
// Do as the flow parser does due to JSX ambiguities.
// First we try and parse as an assignment expression disallowing
// typed arrow functions. If that fails, then try again while allowing
// typed arrow functions and attach the type parameters after the fact.
auto optAssign = parseAssignmentExpression(
param,
AllowTypedArrowFunction::No,
CoverTypedParameters::No,
nullptr);
if (optAssign) {
// That worked, so just return it directly.
collect.setDiscardMessages(false);
return *optAssign;
} else {
// Consume the type parameters and try again.
savePoint.restore();
auto optTypeParams = parseTypeParamsFlow();
// Type parameters must be followed by a '(' to be meaningful.
if (optTypeParams && check(TokenKind::l_paren)) {
typeParams = *optTypeParams;
optAssign = parseAssignmentExpression(
param,
AllowTypedArrowFunction::Yes,
CoverTypedParameters::No,
typeParams);
if (optAssign) {
// We've got the arrow function now, return it directly.
return *optAssign;
} else {
// That's everything we can try.
error(
typeParams->getSourceRange(),
"type parameters must be used in an arrow function expression");
return None;
}
} else {
// Invalid type params, and also invalid JSX. Bail.
savePoint.restore();
}
}
}
#endif
state.leftStartLoc = tok_->getStartLoc();
state.optLeftExpr = parseConditionalExpression(param, coverTypedParameters);
if (!state.optLeftExpr)
return None;
ESTree::Node *returnType = nullptr;
ESTree::Node *predicate = nullptr;
#if HERMES_PARSE_FLOW
if (context_.getParseFlow()) {
if (allowTypedArrowFunction == AllowTypedArrowFunction::Yes &&
((*state.optLeftExpr)->getParens() != 0 ||
isa<ESTree::CoverEmptyArgsNode>(*state.optLeftExpr)) &&
check(TokenKind::colon)) {
JSLexer::SavePoint savePoint{&lexer_};
// Defer our decision on whether to show or suppress messages for this
// next section.
// If we are unsuccessful during the parse, it can mean that we need to
// start parsing JSX children inside tags, instead of function type
// parameters. We need to suppress lexer messages because the lexing
// rules inside JSX are quite different from JS/Flow. For example: x ?
// (1) : <tag>#{foo}</tag>;
// ^
// and
// x ? (1) : <tag>"</tag>;
// ^
// must be able to handle the lexer errors that would occur if we lexed
// the inside of the JSX tags as JS.
CollectMessagesRAII collect{&sm_, true};
SMLoc annotStart = advance(JSLexer::GrammarContext::Type).Start;
bool startsWithPredicate = check(checksIdent_);
auto optType = startsWithPredicate
? llvh::None
: parseTypeAnnotationFlow(annotStart, AllowAnonFunctionType::No);
if (optType)
returnType = *optType;
if (optType || startsWithPredicate) {
if (check(TokenKind::equalgreater)) {
assert(
!startsWithPredicate && "no returnType if startsWithPredicate");
// Done parsing the return type and predicate.
// Successful parse, show any messages that the lexer emitted.
collect.setDiscardMessages(false);
} else if (check(checksIdent_)) {
auto optPred = parsePredicateFlow();
if (optPred && check(TokenKind::equalgreater)) {
// Done parsing the return type and predicate.
predicate = *optPred;
// Successful parse, show any messages that the lexer emitted.
collect.setDiscardMessages(false);
} else {
savePoint.restore();
}
} else {
savePoint.restore();
}
} else {
savePoint.restore();
}
}
}
#endif
#if HERMES_PARSE_TS
if (context_.getParseTS()) {
// Separate logic for TS parsing here, because the semantics don't
// require as much complexity as Flow due to a lack of predicates.
if (allowTypedArrowFunction == AllowTypedArrowFunction::Yes &&
((*state.optLeftExpr)->getParens() != 0 ||
isa<ESTree::CoverEmptyArgsNode>(*state.optLeftExpr)) &&
check(TokenKind::colon)) {
JSLexer::SavePoint savePoint{&lexer_};
// Defer our decision on whether to show or suppress messages for this
// next section.
// If we are unsuccessful during the parse, it can mean that we need to
// start parsing JSX children inside tags, instead of function type
// parameters. We need to suppress lexer messages because the lexing
// rules inside JSX are quite different from TS. For example: x ? (1) :
// <tag>#{foo}</tag>;
// ^
// and
// x ? (1) : <tag>"</tag>;
// ^
// must be able to handle the lexer errors that would occur if we lexed
// the inside of the JSX tags as JS.
CollectMessagesRAII collect{&sm_, true};
SMLoc annotStart = advance(JSLexer::GrammarContext::Type).Start;
auto optType = parseTypeAnnotationTS(annotStart);
if (optType)
returnType = *optType;
if (optType) {
if (check(TokenKind::equalgreater)) {
// Done parsing the return type.
// Successful parse, show any messages that the lexer emitted.
collect.setDiscardMessages(false);
} else {
savePoint.restore();
}
} else {
savePoint.restore();
}
}
}
#endif
// Check for ArrowFunction.
// ArrowFunction : ArrowParameters [no line terminator] => ConciseBody.
// AsyncArrowFunction :
// async [no line terminator] ArrowParameters [no line terminator] =>
// ConciseBody.
if (check(TokenKind::equalgreater) &&
!lexer_.isNewLineBeforeCurrentToken()) {
return parseArrowFunctionExpression(
param,
*state.optLeftExpr,
typeParams,
returnType,
predicate,
typeParams ? typeParams->getStartLoc() : startLoc,
allowTypedArrowFunction,
forceAsync);
}
#if HERMES_PARSE_FLOW
if (typeParams) {
errorExpected(
TokenKind::equalgreater,
"in generic arrow function",
"start of function",
typeParams->getStartLoc());
return None;
}
#endif
if (!checkAssign())
return *state.optLeftExpr;
// Check for destructuring assignment.
if (check(TokenKind::equal) &&
(isa<ESTree::ArrayExpressionNode>(*state.optLeftExpr) ||
isa<ESTree::ObjectExpressionNode>(*state.optLeftExpr))) {
state.optLeftExpr = reparseAssignmentPattern(*state.optLeftExpr, false);
if (!state.optLeftExpr)
return None;
}
state.op = getTokenIdent(tok_->getKind());
state.debugLoc = advance().Start;
return nullptr;
};
llvh::SmallVector<State, 2> stack;
stack.emplace_back();
auto optRes = parseHelper(
stack.back(),
param,
allowTypedArrowFunction,
coverTypedParameters,
typeParams);
for (;;) {
if (!optRes)
return None;
if (!stack.back().op) {
stack.pop_back();
break;
}
if (stack.size() > ESTree::MAX_NESTED_ASSIGNMENTS) {
recursionDepthExceeded();
return None;
}
stack.emplace_back();
optRes = parseHelper(
stack.back(),
param,
AllowTypedArrowFunction::Yes,
CoverTypedParameters::No,
nullptr);
}
assert(optRes.getValue() != nullptr);
while (!stack.empty()) {
if (!checkEndAssignmentExpression()) {
// Note: We don't assert the valid end of an AssignmentExpression here
// because we do not know yet whether the entire file is well-formed.
// This check errors here to ensure that we still catch missing elements
// in `checkEndAssignmentExpression` while allowing us to avoid actually
// asserting and crashing.
error(
tok_->getStartLoc(), "unexpected token after assignment expression");
return None;
}
auto &top = stack.back();
optRes = setLocation(
top.leftStartLoc,
getPrevTokenEndLoc(),
top.debugLoc,
new (context_) ESTree::AssignmentExpressionNode(
top.op, top.optLeftExpr.getValue(), optRes.getValue()));
stack.pop_back();
}
return optRes.getValue();
}