Skip to content

Commit

Permalink
Implement optional chaining.
Browse files Browse the repository at this point in the history
Summary:
Add the optional chaining operator `?.`, as specified in the stage 3 proposal:
https://tc39.es/proposal-optional-chaining

The JSLexer must check if the character after the `.` is a digit,
and avoid lexing `TokenKind::questiondot` if it is.

The optional nature of member access is encoded in ESTree as an `optional`
property on the new `OptionalMemberExpression` and `OptionalCallExpression`.
These nodes are emitted for every element of a member/call chain
after encountering `?.`.
There are some changes required to allow more ergonomic grouping
of `OptionalMemberExpression` and `MemberExpression`, as well as
of `OptionalCallExpression` and `CallExpression`.

Now, `parseLeftHandSizeExpression` must attempt to parse a
`NewExpression` or `OptionalExpression` (instead of a `MemberExpression`).
Within `parseNewExpressionOrOptionalExpression`, we must disallow `?.`
within a constructor call (`NewExpression`).
So, we add an `AllowOptionalChain` flag to distinguish between the two
cases.

Now, the first token during `parseMemberSelect` can be `.`, `[`, or `?.`.
Because `?.` can then be followed by `[`, `(`, *or* an identifier,
we handle it separately in `parseMemberSelect`.
We must also add a flag to `parseCallExpression` which indicates whether
the resultant `CallExpression` should be marked as `optional`.

IRGen results in some minor adjustments to avoid too much duplicate code.
In particular, we extract a new `genMemberExpression` which can
emit code for optional chains, but also provides access to the base object
being accessed, so that call expressions can populate `this` properly
when performing the call. We must also modify `genCallExpression` to handle
the `optional` semantics.

Then, we write new `genOptionalCallExpression` and `genOptionalMemberExpression`
functions to make it clearer which is being called. These new functions
also take a nullable `shortCircuitBB` argument, which is the block
to branch to if their optional check upon `?.` were to fail.
We special-case `OptionalCallExpression` and `OptionalMemberExpression`
to avoid threading the `shortCircuitBB` through `genExpression`.

Reviewed By: tmikov

Differential Revision: D18358853

fbshipit-source-id: 4ef00db0b8acbea1de0b79bf33a824f7a2575b9f
  • Loading branch information
avp authored and facebook-github-bot committed Dec 3, 2019
1 parent 18341ed commit f6577a4
Show file tree
Hide file tree
Showing 15 changed files with 1,221 additions and 67 deletions.
43 changes: 24 additions & 19 deletions include/hermes/AST/ESTree.def
Original file line number Diff line number Diff line change
Expand Up @@ -226,15 +226,18 @@ ESTREE_NODE_2_ARGS(
NodePtr, argument, true,
NodeBoolean, delegate, true)

ESTREE_FIRST(CallExpressionLike, Base)
ESTREE_NODE_2_ARGS(
CallExpression,
Base,
NodePtr,
callee,
false,
NodeList,
arguments,
false)
CallExpression, CallExpressionLike,
NodePtr, callee, false,
NodeList, arguments, false)

ESTREE_NODE_3_ARGS(
OptionalCallExpression, CallExpressionLike,
NodePtr, callee, false,
NodeList, arguments, false,
NodeBoolean, optional, false)
ESTREE_LAST(CallExpressionLike)

ESTREE_NODE_3_ARGS(
AssignmentExpression,
Expand Down Expand Up @@ -275,18 +278,20 @@ ESTREE_NODE_3_ARGS(
prefix,
false)

ESTREE_FIRST(MemberExpressionLike, Base)
ESTREE_NODE_3_ARGS(
MemberExpression,
Base,
NodePtr,
object,
false,
NodePtr,
property,
false,
NodeBoolean,
computed,
false)
MemberExpression, MemberExpressionLike,
NodePtr, object, false,
NodePtr, property, false,
NodeBoolean, computed, false)

ESTREE_NODE_4_ARGS(
OptionalMemberExpression, MemberExpressionLike,
NodePtr, object, false,
NodePtr, property, false,
NodeBoolean, computed, false,
NodeBoolean, optional, false)
ESTREE_LAST(MemberExpressionLike)

ESTREE_NODE_3_ARGS(
LogicalExpression,
Expand Down
18 changes: 18 additions & 0 deletions include/hermes/AST/ESTree.h
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,9 @@ class BlockStatementDecoration {
class PatternDecoration {};
class CoverDecoration {};

class CallExpressionLikeDecoration {};
class MemberExpressionLikeDecoration {};

namespace detail {
/// We need to to be able customize some ESTree types when passing them through
/// a constructor, so we create a simple template type mapper. Specifically, a
Expand Down Expand Up @@ -639,6 +642,21 @@ NodeList &getParams(FunctionLikeNode *node);
/// functions.
BlockStatementNode *getBlockStatement(FunctionLikeNode *node);

/// \return the object of the member expression node.
Node *getObject(MemberExpressionLikeNode *node);

/// \return the property of the member expression node.
Node *getProperty(MemberExpressionLikeNode *node);

/// \return whether the member expression node is computed.
NodeBoolean getComputed(MemberExpressionLikeNode *node);

/// \return the callee of the call.
Node *getCallee(CallExpressionLikeNode *node);

/// \return the arguments list of the call.
NodeList &getArguments(CallExpressionLikeNode *node);

} // namespace ESTree
} // namespace hermes

Expand Down
1 change: 1 addition & 0 deletions include/hermes/Parser/TokenKinds.def
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ PUNCTUATOR(r_paren, ")")
PUNCTUATOR(l_square, "[")
PUNCTUATOR(r_square, "]")
PUNCTUATOR(period, ".")
PUNCTUATOR(questiondot, "?.")
PUNCTUATOR(dotdotdot, "...")
PUNCTUATOR(semi, ";")
PUNCTUATOR(comma, ",")
Expand Down
60 changes: 60 additions & 0 deletions lib/AST/ESTree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,65 @@ BlockStatementNode *getBlockStatement(FunctionLikeNode *node) {
}
}

Node *getObject(MemberExpressionLikeNode *node) {
switch (node->getKind()) {
case NodeKind::MemberExpression:
return cast<MemberExpressionNode>(node)->_object;
case NodeKind::OptionalMemberExpression:
return cast<OptionalMemberExpressionNode>(node)->_object;
default:
break;
}
llvm_unreachable("invalid MemberExpressionLikeNode");
}

Node *getProperty(MemberExpressionLikeNode *node) {
switch (node->getKind()) {
case NodeKind::MemberExpression:
return cast<MemberExpressionNode>(node)->_property;
case NodeKind::OptionalMemberExpression:
return cast<OptionalMemberExpressionNode>(node)->_property;
default:
break;
}
llvm_unreachable("invalid MemberExpressionLikeNode");
}

NodeBoolean getComputed(MemberExpressionLikeNode *node) {
switch (node->getKind()) {
case NodeKind::MemberExpression:
return cast<MemberExpressionNode>(node)->_computed;
case NodeKind::OptionalMemberExpression:
return cast<OptionalMemberExpressionNode>(node)->_computed;
default:
break;
}
llvm_unreachable("invalid MemberExpressionLikeNode");
}

Node *getCallee(CallExpressionLikeNode *node) {
switch (node->getKind()) {
case NodeKind::CallExpression:
return cast<CallExpressionNode>(node)->_callee;
case NodeKind::OptionalCallExpression:
return cast<OptionalCallExpressionNode>(node)->_callee;
default:
break;
}
llvm_unreachable("invalid CallExpressionLikeNode");
}

NodeList &getArguments(CallExpressionLikeNode *node) {
switch (node->getKind()) {
case NodeKind::CallExpression:
return cast<CallExpressionNode>(node)->_arguments;
case NodeKind::OptionalCallExpression:
return cast<OptionalCallExpressionNode>(node)->_arguments;
default:
break;
}
llvm_unreachable("invalid CallExpressionLikeNode");
}

} // namespace ESTree
} // namespace hermes
Loading

1 comment on commit f6577a4

@Whobeu
Copy link

@Whobeu Whobeu commented on f6577a4 Dec 4, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beautiful!

Please sign in to comment.