-
Notifications
You must be signed in to change notification settings - Fork 674
Port ?.
emit
#1385
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
Port ?.
emit
#1385
Conversation
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.
Pull Request Overview
This PR completes the ES2020 optional chaining (?.
) emit transform by updating test baseline files to switch between concise ?.
syntax and the expanded null‐check form according to target settings.
- Normalizes emitted optional chaining patterns across conformance tests
- Reverts output to expanded nullish checks in
?.
emitless contexts - Aligns test baselines for static and instance property/method access and calls
Reviewed Changes
Copilot reviewed 130 out of 130 changed files in this pull request and generated 1 comment.
File | Description |
---|---|
testdata/baselines/reference/submodule/conformance/typeOfThisInstanceMemberNarrowedWithLoopAntecedent.js | Fixed optional‐chain emit for this.state.data access |
testdata/baselines/reference/submodule/conformance/thisTypeOptionalCall.js | Updated fn?.bind emit vs expanded form |
testdata/baselines/reference/submodule/conformance/* | ...many other conformance baseline updates |
...elines/reference/submodule/conformance/typeOfThisInstanceMemberNarrowedWithLoopAntecedent.js
Show resolved
Hide resolved
@@ -7,8 +7,4 @@ | |||
-"use strict"; |
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.
I wonder if this very common diff is an easy fix somewhere...
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.
This is a script file by strada logic. (no exports/imports, nothing forcing module mode parsing.) In corsa, the module transforms add use strict
if needed, but in strada, the ts
transform does (via visitLexicalEnvironment
in visitSourceFile
) so it's done for script files (and I think the module transforms, redundantly, also do). Obviously the module transforms don't touch script files, so they don't add the prologue here.
-var _a; | ||
-a === null || a === void 0 ? void 0 : a(); | ||
var _a; | ||
a === null || a === void 0 ? void 0 : a(); | ||
-(_a = (a)) === null || _a === void 0 ? void 0 : _a(); |
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.
Wow, a place where Strada over-parenthesized 😄
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.
Yeah, strada went "oh, this is a cast that became a partially emitted expression, gotta add some parens as we build it", but in corsa, with parens added on print, we're like "oh, we're in an assignment and this is a partially emitted expression of an identifier? Nah, no need for parens".
- ; | ||
-({ a: obj === null || obj === void 0 ? void 0 : obj["a"] } = { a: 1 }); | ||
-({ a: obj === null || obj === void 0 ? void 0 : obj.a["b"] } = { a: 1 }); | ||
+(obj === null || obj === void 0 ? void 0 : obj["a"]) = 1; |
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.
This seems to be incorrect, right? This is trying to assign 1 to the parenthesized expression?
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.
This is the structure we make in strada for these inputs, too, just without the parenthesis. The strada output does not roundtrip - the parens are actually required. And yes, they do make it clear that the output is... questionable. But that's fine, these all have a The left-hand side of an assignment expression may not be an optional expression
error, which'd be why it's not handled sensibly in the transform - it's completely invalid.
+((_a) => { var _b; var { [(_b = a()) === null || _b === void 0 ? void 0 : _b.d]: c = "" } = _a; })(); |
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.
This seems funky. (But maybe still equivalent?)
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.
This is just the >es5 output, since there's no es5 transform. In <=es5, we used to just emit a var _a
in the body following the parameter list, which, pre-es6, was valid.
func (ch *optionalChainTransformer) visitCallExpression(node *ast.CallExpression, captureThisArg bool) *ast.Node { | ||
if node.Flags&ast.NodeFlagsOptionalChain != 0 { | ||
// If `node` is an optional chain, then it is the outermost chain of an optional expression. | ||
return ch.visitOptionalExpression(node.AsNode(), captureThisArg, false) |
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.
Can you restore the isDelete
comments?
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.
I can... but the go formatting for it is weird af, placing the comment before the preceding comma. I've been avoiding most of those comments since I noticed it. With only two callsites that aren't named variables (both of which pass false
), I'm not too worried about this one being too confusing.
return ch.visitOptionalExpression(node.AsNode(), captureThisArg, isDelete) | ||
} | ||
expression := ch.Visitor().VisitNode(node.Expression()) | ||
// Debug.assertNotNode(expression, isSyntheticReference); // !!! |
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.
Does anything go wrong if you panic here?
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.
These asserts were to satisfy the TS typechecker and remove a type from a union type. Technically, they're not actually required anymore, but if they're added back, they should be in the same systematic debug-build-specific style, not as ad-hoc panics.
if unwrapped.Flags&ast.NodeFlagsOptionalChain != 0 { | ||
return ch.visitNonOptionalExpression(node.Expression, false, true) | ||
} | ||
return ch.Visitor().VisitEachChild(node.AsNode()) |
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.
Any reason you do this instead of the original-ish
return ch.Visitor().VisitEachChild(node.AsNode()) | |
deletedExpr := ch.Visitor().VisitNode(node.Expression) | |
return ch.Factory().UpdateDeleteExpression(node, deletedExpr) |
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.
Because they're equivalent (as best I can tell). Why manually pull out and update
the child of the node, when visitEachChild
does that already.
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.
True, though it's technically doing a bit more work under the hood.
links = append([]*ast.Node{chain}, links...) | ||
} |
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.
I know that this was the original but it seems so not-ideal - why didn't we just keep appending and reverse?
links = append([]*ast.Node{chain}, links...) | |
} | |
links = append(links, chain) | |
} | |
slices.Reverse(links) |
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.
More often than not, these flattened chains are single element - it's not often people do a a?.b?.c?.d
deal. Given that, I think the original is fine for the single or even two element cases. It's not something I'd think needs optimization, especially since reversing a slice isn't great. If anything, you'd do a quick decent to count the chain length, preallocate the slice (probably from a pool, if this was actually hot code), and then insert directly at target position as you descend.
if ast.IsSyntheticReferenceExpression(left) { | ||
leftThisArg = left.AsSyntheticReferenceExpression().ThisArg | ||
capturedLeft = left.AsSyntheticReferenceExpression().Expression |
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.
if ast.IsSyntheticReferenceExpression(left) { | |
leftThisArg = left.AsSyntheticReferenceExpression().ThisArg | |
capturedLeft = left.AsSyntheticReferenceExpression().Expression | |
if ast.IsSyntheticReferenceExpression(left) { | |
left := left.AsSyntheticReferenceExpression() | |
leftThisArg = left.ThisArg | |
capturedLeft = left.Expression |
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.
I don't think that kind of shadowing is a great idea (left
the node being shadowed by left
the node data) - don't we have a lint rule forbidding it? Two uses like this kinda aren't enough to bother, anyway, since it's not clarifying or simplifying much of anything. Unless someone shows me the casts having an outsized impact on perf, which is going to make me scream, since old casts were free-and-erased so never really needed perf scrutiny.
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.
All of the calls and casts have cost, I'm afraid
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.
(but really, don't think about it)
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.
It just gets a bit unwieldy, and I end up thinking about it :D
thisArg = rightExpression | ||
} | ||
} | ||
if segment.Kind == ast.KindElementAccessExpression { |
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.
nit - the check is inverted compared to the original code
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.
As are the bodies in the branches of the check. E
comes before P
, so E
comes first in the case
clause list, so handling the first case explicitly is a bit more natural looking.
rightExpression, | ||
) | ||
} | ||
target.Loc = node.Loc |
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.
Any reason you have to do this? I see you run SetOriginal
farther below. The ordering is a little bit different here though. I would have thought you'd run SetOriginal
here based on the original code.
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.
.Loc = .Loc
is equivalent to SetTextRange
, which is independent from SetOriginalNode
(text ranges control comment and source map spans, original nodes control emit resolver API nodes and in some cases fallback location spans) - and the original nodes are set in new places now because we now have an assert if we try and set original multiple times (to different things), so all these codepaths now reliably set originals when they're done constructing the new node, rather than sometimes doing it and sometimes letting the caller handle it.
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.
I haven't looked closely at the test cases, but the porting looks precise enough apart from comments I left.
Which is the remainder of the
es2020
transform.There are some discrepancies in parenthesis (we over-parenthesize in some cases) in the emit, seemingly caused by changes to how we parenthesize in general.