New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add optional chaining #204
Conversation
As planned, Optional Chaining and Nullish Coalescing have arrived at Stage 4. @dherman @RReverser @adrianheine @nzakas @ariya @michaelficarra @hzoo @loganfsmyth @danez Would you review this PR? |
I don't like this representation of optional chaining at all. I prefer @devsnek's design.
I do not value this at all. In fact, I think that is a negative aspect of this design. I do not want my tools to fail in a surprising and quiet way. I want them to fail fast and loudly. |
I agree that If it's acceptable that we use completely different representation for Let's see opinions from other members. |
For reference, the @devsnek's design is the following (if my understanding is correct): interface OptionalExpression <: Expression {
type: "OptionalExpression"
object: Expression
chain: OptionalChain
}
interface OptionalChain <: Node {
type: "OptionalChain"
base: OptionalChain
}
interface OptionalChainProperty <: OptionalChain {
property: Expression
computed: boolean
}
interface OptionalChainCall <: OptionalChain {
arguments: [ Expression ]
} Therefore, |
OptionalExpression is basically separating the normal member expression part from the optional chain part. OptionalChain is basically MemberExpression except that it also deals with the disjunct start by using a
Some examples: a.b?.c
OptionalExpression {
object: MemberExpression {
object: a,
property: b,
},
chain: OptionalChain {
base: null,
property: c,
},
} a.b?.c.d?.e.f
OptionalExpression {
object: OptionalExpression {
object: MemberExpression {
object: a,
property: b,
},
chain: OptionalChain {
base: OptionalChain {
base: null,
property: c,
},
property: d,
},
},
chain: OptionalChain {
base: OptionalChain {
base: null,
property: e,
},
property: f,
},
} a?.(x, y, z)
OptionalExpression {
object: a,
chain: OptionalChain {
base: null,
arguments: [x, y, z],
},
} You can see some more examples and a complete parsing implementation here: https://github.com/engine262/engine262/blob/21fb9197453f4d0f5509df498c79b65fc2c94a81/src/parse.mjs#L142-L250 Also, you can just call me "devsnek" instead of "the devsnek" :) |
I don't want any kind of "compatibility" between them. They are different, unrelated concepts. Any tooling that operated on an optional member access as if it was a member access would risk silently breaking: the worst kind of breaking. |
By the way, I like an array instead of the linked list of
I'm sorry. Articles are difficult for me because my primary language doesn't have that concept. (my intention was like "the design of devsnek.") About compatibility, I'm talking between non-optional member accesses inside/outside of short-circuited places. For example, in |
@mysticatea that would allow an empty array, which is not a valid tree. i realize this already exists for comma op, but imo a good AST can not represent invalid trees. |
@devsnek There are ...in that case, how about moving |
that would be difficult (or even impossible) to deal with for ast evaluators. i think it might also break the associativity of expressions of multiple chains. |
@devsnek Thank you for your answers. Hmm. I still like an array for In my first impression, I thought that the |
After I considered, I lean toward to keeping this PR as-is. I like devsnek's design, too. However, I have two concerns on it. (1) It's a severe concern that non-optional member accesses and function calls are getting two different representations. For example, |
I don't care if you specifically use my design or not, but please do not overload the existing AST types. perhaps introduce an OptionalMemberExpression or something. I'd also like to mention that the current design is basically impossible for analysis based on evaluation or control flow, since the break in the chain is at the bottom instead of the top, which is what my design fixes. The design babel chose is basically only good if you're trying to generate branches (jumps) as a byproduct of the AST, not use the AST itself. Also as far as I understand, |
In ESTree, it's very common cases that the flags of existing nodes represent additive semantics. For example,
Because the optional member access is still member access, I think proper that I think that we need breaking changes around
It needs to skip the evaluation of RHS until
As this section. In |
well in any case, i'd said what i hope the design is. if i could only choose thing, it would be that the chains are split at the top of the ast, not from the bottom. |
I thought a modified version of devsnek's design: https://gist.github.com/mysticatea/f3a87f3e02632797ec59d9b447fdf05e. It should resolve my second concern ( |
It looks to me that this commit doesn't quite follow the final functionality of the proposal in this line:
since that would appear to add optional chaining to a Maybe changing the line to
would bring it in line with the proposal. |
@julianjensen I'm guessing you are talking about another spec than ESTree because |
@dherman @RReverser @adrianheine @nzakas @ariya @michaelficarra @hzoo @loganfsmyth @danez Would you review this PR? Especially, there are two proposals about optional chaining. I'd like to ask for the direction we should go. The former is minimal changes, but it represents the short-circuit behavior in an ugly way. We have to traverse ancestor nodes to know the place of the end of the short-circuiting. (In other words, the tree doesn't represent the short-circuit behavior.) The latter represents the short-circuit behavior in a better way, but it forks the representation of member accesses and function calls. Those two semantics have two kinds of representations in the left/right of |
@dherman @RReverser @adrianheine @nzakas @ariya @michaelficarra @hzoo @loganfsmyth @danez Friendly ping. |
One thing I would like clarification on here: what's the problem with going with babel's representation? You mention:
In particular
What do you mean by this? Why does a Correct me if I'm wrong, but isn't this Similarly, from the POV of an AST, why does the "optional: true, shortCircuited: true" need to be encoded in the AST? Current solution has 5 different permutations:
babel's(/typescript-eslint's) solution has 3:
|
See those four examples. In the "New optional and shortCircuited properties" version, the AST cannot represent short-circuit behavior by binary tree: the RHS of the operators ( Also, the
Therefore, I believe that two flag properties (
I don't think there are 5 different permutations. There are 4 different permutations because two boolean flags have 2 bits information.
It doesn't need other patterns. |
I updated this PR for the latest discussion. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Let's ship this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me too, as already said above :) Waiting on response from Babel side.
We have discussed it in today's meeting. We, the Babel team, backs the current version. We are committed to support current version on Note that in current approach, to determine whether I think we have reached consensus and it should be merged as-is. Thanks for all the fish. |
Looks like you linked to the document without optional chaining - is that a mistake?
Oh, that makes sense, I don't think anyone suggested to modify Babel AST. The consensus and changes in this repo is specifically for ESTree; whether Babel can and/or wants to modify Babel AST as well in response to any such changes, is entirely up to the Babel team. Thanks for the approval! |
Thank you for the review and approval. I updated this PR to fix "where are" → "that are". |
It looks like consensus has been reached, so merging. Thanks everyone! |
When Babel originally implemented this, one of the suggestions was to use a trinary for the // obj.aaa.bbb
{
"type": "MemberExpression",
"optional": null, // regular member expression
"object": {
"type": "MemberExpression",
"optional": null, // regular member expression
"object": { "type": "Identifier", "name": "obj" },
"property": { "type": "Identifier", "name": "aaa" }
},
"property": { "type": "Identifier", "name": "bbb" }
} // obj?.aaa.bbb
{
"type": "ChainExpression",
"expression": {
"type": "MemberExpression",
"optional": false, // part of a optional chain
"object": {
"type": "MemberExpression",
"optional": true,
"object": { "type": "Identifier", "name": "obj" },
"property": { "type": "Identifier", "name": "aaa" }
},
"property": { "type": "Identifier", "name": "bbb" }
}
} // (obj?.aaa).bbb
{
"type": "MemberExpression",
"optional": null, // not part of the chain
"object": {
"type": "ChainExpression",
"expression": {
"type": "MemberExpression",
"optional": true,
"object": { "type": "Identifier", "name": "obj" },
"property": { "type": "Identifier", "name": "aaa" }
}
},
"property": { "type": "Identifier", "name": "bbb" }
} |
I just want to say special thanks to @mysticatea who explored and patiently adjusted a lot of variations for this proposal in response to the feedback. Thanks a lot! |
Good afternoon! Have you added support for optional chaining to eslint? If not added, which version will support it? Thanks! |
@sevor005 It landed in eslint v7.5.0 - July 18, 2020 through eslint/eslint#13416 |
…ndard OptionalMemberExpression type) see estree/estree#204
This is a draft to update
es2020.md
for the TC39 meeting in Dec 2019. The meeting contains the following items on its agenda (https://github.com/tc39/agendas/blob/master/2019/12.md).Therefore, this PR contains the AST proposal of those three syntaxes. If any of them didn't arrive at Stage 4, I will remove those from this PR.
(Because we needed over 2 months to merge a PR in the previous time, I wanted to start discussion earlier.)
Optional Chaining:
This proposal is inspired by #146 (comment). It adds two properties
optional
andshortCircuited
to two existing nodesCallExpression
andMemberExpression
.The following examples describe how the two properties represent the syntax.
In the following code blocks, the first comment is the source code and the rest is the AST of the code.
I don't see the advantages of introducing new node types such as
OptionalMemberExpression
because:optional
andshortCircuited
properties, I think that the behavior will be not so bad.Nullish Coalescing:
In #203, we look to have a consensus that we should represent the syntax by
LogicalExpression
. This PR follows it.This PR closes #146, #153, and #203.
??
#203