-
Notifications
You must be signed in to change notification settings - Fork 675
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -11,7 +11,222 @@ type optionalChainTransformer struct { | |||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
func (ch *optionalChainTransformer) visit(node *ast.Node) *ast.Node { | ||||||||||||||||
return node // !!! | ||||||||||||||||
if node.SubtreeFacts()&ast.SubtreeContainsOptionalChaining == 0 { | ||||||||||||||||
return node | ||||||||||||||||
} | ||||||||||||||||
switch node.Kind { | ||||||||||||||||
case ast.KindCallExpression: | ||||||||||||||||
return ch.visitCallExpression(node.AsCallExpression(), false) | ||||||||||||||||
case ast.KindPropertyAccessExpression, | ||||||||||||||||
ast.KindElementAccessExpression: | ||||||||||||||||
if node.Flags&ast.NodeFlagsOptionalChain != 0 { | ||||||||||||||||
return ch.visitOptionalExpression(node, false, false) | ||||||||||||||||
} | ||||||||||||||||
return ch.Visitor().VisitEachChild(node) | ||||||||||||||||
case ast.KindDeleteExpression: | ||||||||||||||||
return ch.visitDeleteExpression(node.AsDeleteExpression()) | ||||||||||||||||
default: | ||||||||||||||||
return ch.Visitor().VisitEachChild(node) | ||||||||||||||||
} | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
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) | ||||||||||||||||
} | ||||||||||||||||
if ast.IsParenthesizedExpression(node.Expression) { | ||||||||||||||||
unwrapped := ast.SkipParentheses(node.Expression) | ||||||||||||||||
if unwrapped.Flags&ast.NodeFlagsOptionalChain != 0 { | ||||||||||||||||
// capture thisArg for calls of parenthesized optional chains like `(foo?.bar)()` | ||||||||||||||||
expression := ch.visitParenthesizedExpression(node.Expression.AsParenthesizedExpression(), true, false) | ||||||||||||||||
args := ch.Visitor().VisitNodes(node.Arguments) | ||||||||||||||||
if ast.IsSyntheticReferenceExpression(expression) { | ||||||||||||||||
res := ch.Factory().NewFunctionCallCall(expression.AsSyntheticReferenceExpression().Expression, expression.AsSyntheticReferenceExpression().ThisArg, args.Nodes) | ||||||||||||||||
res.Loc = node.Loc | ||||||||||||||||
ch.EmitContext().SetOriginal(res, node.AsNode()) | ||||||||||||||||
return res | ||||||||||||||||
} | ||||||||||||||||
return ch.Factory().UpdateCallExpression(node, expression, nil, nil, args) | ||||||||||||||||
} | ||||||||||||||||
} | ||||||||||||||||
return ch.Visitor().VisitEachChild(node.AsNode()) | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
func (ch *optionalChainTransformer) visitParenthesizedExpression(node *ast.ParenthesizedExpression, captureThisArg bool, isDelete bool) *ast.Node { | ||||||||||||||||
expr := ch.visitNonOptionalExpression(node.Expression, captureThisArg, isDelete) | ||||||||||||||||
if ast.IsSyntheticReferenceExpression(expr) { | ||||||||||||||||
// `(a.b)` -> { expression `((_a = a).b)`, thisArg: `_a` } | ||||||||||||||||
// `(a[b])` -> { expression `((_a = a)[b])`, thisArg: `_a` } | ||||||||||||||||
synth := expr.AsSyntheticReferenceExpression() | ||||||||||||||||
res := ch.Factory().NewSyntheticReferenceExpression(ch.Factory().UpdateParenthesizedExpression(node, synth.Expression), synth.ThisArg) | ||||||||||||||||
ch.EmitContext().SetOriginal(res, node.AsNode()) | ||||||||||||||||
return res | ||||||||||||||||
} | ||||||||||||||||
return ch.Factory().UpdateParenthesizedExpression(node, expr) | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
func (ch *optionalChainTransformer) visitPropertyOrElementAccessExpression(node *ast.Expression, captureThisArg bool, isDelete bool) *ast.Expression { | ||||||||||||||||
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, isDelete) | ||||||||||||||||
} | ||||||||||||||||
expression := ch.Visitor().VisitNode(node.Expression()) | ||||||||||||||||
// Debug.assertNotNode(expression, isSyntheticReference); // !!! | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 commentThe 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. |
||||||||||||||||
|
||||||||||||||||
var thisArg *ast.Expression | ||||||||||||||||
if captureThisArg { | ||||||||||||||||
if !transformers.IsSimpleCopiableExpression(expression) { | ||||||||||||||||
thisArg = ch.Factory().NewTempVariable() | ||||||||||||||||
ch.EmitContext().AddVariableDeclaration(thisArg) | ||||||||||||||||
expression = ch.Factory().NewAssignmentExpression(thisArg, expression) | ||||||||||||||||
} else { | ||||||||||||||||
thisArg = expression | ||||||||||||||||
} | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
if node.Kind == ast.KindPropertyAccessExpression { | ||||||||||||||||
p := node.AsPropertyAccessExpression() | ||||||||||||||||
expression = ch.Factory().UpdatePropertyAccessExpression(p, expression, nil, ch.Visitor().VisitNode(p.Name())) | ||||||||||||||||
} else { | ||||||||||||||||
p := node.AsElementAccessExpression() | ||||||||||||||||
expression = ch.Factory().UpdateElementAccessExpression(p, expression, nil, ch.Visitor().VisitNode(p.AsElementAccessExpression().ArgumentExpression)) | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
if thisArg != nil { | ||||||||||||||||
res := ch.Factory().NewSyntheticReferenceExpression(expression, thisArg) | ||||||||||||||||
ch.EmitContext().SetOriginal(res, node.AsNode()) | ||||||||||||||||
return res | ||||||||||||||||
} | ||||||||||||||||
return expression | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
func (ch *optionalChainTransformer) visitDeleteExpression(node *ast.DeleteExpression) *ast.Node { | ||||||||||||||||
unwrapped := ast.SkipParentheses(node.Expression) | ||||||||||||||||
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 commentThe reason will be displayed to describe this comment to others. Learn more. Any reason you do this instead of the original-ish
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
func (ch *optionalChainTransformer) visitNonOptionalExpression(node *ast.Expression, captureThisArg bool, isDelete bool) *ast.Expression { | ||||||||||||||||
switch node.Kind { | ||||||||||||||||
case ast.KindParenthesizedExpression: | ||||||||||||||||
return ch.visitParenthesizedExpression(node.AsParenthesizedExpression(), captureThisArg, isDelete) | ||||||||||||||||
case ast.KindElementAccessExpression, ast.KindPropertyAccessExpression: | ||||||||||||||||
return ch.visitPropertyOrElementAccessExpression(node, captureThisArg, isDelete) | ||||||||||||||||
case ast.KindCallExpression: | ||||||||||||||||
return ch.visitCallExpression(node.AsCallExpression(), captureThisArg) | ||||||||||||||||
default: | ||||||||||||||||
return ch.Visitor().VisitNode(node.AsNode()) | ||||||||||||||||
} | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
type flattenResult struct { | ||||||||||||||||
expression *ast.Expression | ||||||||||||||||
chain []*ast.Node | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
func flattenChain(chain *ast.Node) flattenResult { | ||||||||||||||||
// Debug.assertNotNode(chain, isNonNullChain); // !!! | ||||||||||||||||
links := []*ast.Node{chain} | ||||||||||||||||
for !ast.IsTaggedTemplateExpression(chain) && chain.QuestionDotToken() == nil { | ||||||||||||||||
chain = ast.SkipPartiallyEmittedExpressions(chain.Expression()) | ||||||||||||||||
// Debug.assertNotNode(chain, isNonNullChain); // !!! | ||||||||||||||||
links = append([]*ast.Node{chain}, links...) | ||||||||||||||||
} | ||||||||||||||||
Comment on lines
+136
to
+137
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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?
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||||||||||||||||
return flattenResult{chain.Expression(), links} | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
func isCallChain(node *ast.Node) bool { | ||||||||||||||||
return ast.IsCallExpression(node) && node.Flags&ast.NodeFlagsOptionalChain != 0 | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
func (ch *optionalChainTransformer) visitOptionalExpression(node *ast.Node, captureThisArg bool, isDelete bool) *ast.Node { | ||||||||||||||||
r := flattenChain(node) | ||||||||||||||||
expression := r.expression | ||||||||||||||||
chain := r.chain | ||||||||||||||||
left := ch.visitNonOptionalExpression(ast.SkipPartiallyEmittedExpressions(expression), isCallChain(chain[0]), false) | ||||||||||||||||
var leftThisArg *ast.Expression | ||||||||||||||||
capturedLeft := left | ||||||||||||||||
if ast.IsSyntheticReferenceExpression(left) { | ||||||||||||||||
leftThisArg = left.AsSyntheticReferenceExpression().ThisArg | ||||||||||||||||
capturedLeft = left.AsSyntheticReferenceExpression().Expression | ||||||||||||||||
Comment on lines
+152
to
+154
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think that kind of shadowing is a great idea ( There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 commentThe 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 commentThe 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 |
||||||||||||||||
} | ||||||||||||||||
leftExpression := ch.Factory().RestoreOuterExpressions(expression, capturedLeft, ast.OEKPartiallyEmittedExpressions) | ||||||||||||||||
if !transformers.IsSimpleCopiableExpression(capturedLeft) { | ||||||||||||||||
capturedLeft = ch.Factory().NewTempVariable() | ||||||||||||||||
ch.EmitContext().AddVariableDeclaration(capturedLeft) | ||||||||||||||||
leftExpression = ch.Factory().NewAssignmentExpression(capturedLeft, leftExpression) | ||||||||||||||||
} | ||||||||||||||||
rightExpression := capturedLeft | ||||||||||||||||
var thisArg *ast.Expression | ||||||||||||||||
|
||||||||||||||||
for i, segment := range chain { | ||||||||||||||||
switch segment.Kind { | ||||||||||||||||
case ast.KindElementAccessExpression, ast.KindPropertyAccessExpression: | ||||||||||||||||
if i == len(chain)-1 && captureThisArg { | ||||||||||||||||
if !transformers.IsSimpleCopiableExpression(rightExpression) { | ||||||||||||||||
thisArg = ch.Factory().NewTempVariable() | ||||||||||||||||
ch.EmitContext().AddVariableDeclaration(thisArg) | ||||||||||||||||
rightExpression = ch.Factory().NewAssignmentExpression(thisArg, rightExpression) | ||||||||||||||||
} else { | ||||||||||||||||
thisArg = rightExpression | ||||||||||||||||
} | ||||||||||||||||
} | ||||||||||||||||
if segment.Kind == ast.KindElementAccessExpression { | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. As are the bodies in the branches of the check. |
||||||||||||||||
rightExpression = ch.Factory().NewElementAccessExpression(rightExpression, nil, ch.Visitor().VisitNode(segment.AsElementAccessExpression().ArgumentExpression), ast.NodeFlagsNone) | ||||||||||||||||
} else { | ||||||||||||||||
rightExpression = ch.Factory().NewPropertyAccessExpression(rightExpression, nil, ch.Visitor().VisitNode(segment.AsPropertyAccessExpression().Name()), ast.NodeFlagsNone) | ||||||||||||||||
} | ||||||||||||||||
case ast.KindCallExpression: | ||||||||||||||||
if i == 0 && leftThisArg != nil { | ||||||||||||||||
if !ch.EmitContext().HasAutoGenerateInfo(leftThisArg) { | ||||||||||||||||
leftThisArg = leftThisArg.Clone(ch.Factory()) | ||||||||||||||||
ch.EmitContext().AddEmitFlags(leftThisArg, printer.EFNoComments) | ||||||||||||||||
} | ||||||||||||||||
callThisArg := leftThisArg | ||||||||||||||||
if leftThisArg.Kind == ast.KindSuperKeyword { | ||||||||||||||||
callThisArg = ch.Factory().NewThisExpression() | ||||||||||||||||
} | ||||||||||||||||
rightExpression = ch.Factory().NewFunctionCallCall(rightExpression, callThisArg, ch.Visitor().VisitNodes(segment.ArgumentList()).Nodes) | ||||||||||||||||
} else { | ||||||||||||||||
rightExpression = ch.Factory().NewCallExpression( | ||||||||||||||||
rightExpression, | ||||||||||||||||
nil, | ||||||||||||||||
nil, | ||||||||||||||||
ch.Visitor().VisitNodes(segment.ArgumentList()), | ||||||||||||||||
ast.NodeFlagsNone, | ||||||||||||||||
) | ||||||||||||||||
} | ||||||||||||||||
} | ||||||||||||||||
ch.EmitContext().SetOriginal(rightExpression, segment) | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
var target *ast.Node | ||||||||||||||||
if isDelete { | ||||||||||||||||
target = ch.Factory().NewConditionalExpression( | ||||||||||||||||
createNotNullCondition(ch.EmitContext(), leftExpression, capturedLeft, true), | ||||||||||||||||
ch.Factory().NewToken(ast.KindQuestionToken), | ||||||||||||||||
ch.Factory().NewTrueExpression(), | ||||||||||||||||
ch.Factory().NewToken(ast.KindColonToken), | ||||||||||||||||
ch.Factory().NewDeleteExpression(rightExpression), | ||||||||||||||||
) | ||||||||||||||||
} else { | ||||||||||||||||
target = ch.Factory().NewConditionalExpression( | ||||||||||||||||
createNotNullCondition(ch.EmitContext(), leftExpression, capturedLeft, true), | ||||||||||||||||
ch.Factory().NewToken(ast.KindQuestionToken), | ||||||||||||||||
ch.Factory().NewVoidZeroExpression(), | ||||||||||||||||
ch.Factory().NewToken(ast.KindColonToken), | ||||||||||||||||
rightExpression, | ||||||||||||||||
) | ||||||||||||||||
} | ||||||||||||||||
target.Loc = node.Loc | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any reason you have to do this? I see you run There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||||||||
if thisArg != nil { | ||||||||||||||||
target = ch.Factory().NewSyntheticReferenceExpression(target, thisArg) | ||||||||||||||||
} | ||||||||||||||||
ch.EmitContext().SetOriginal(target, node.AsNode()) | ||||||||||||||||
return target | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
func newOptionalChainTransformer(emitContext *printer.EmitContext) *transformers.Transformer { | ||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,8 +7,4 @@ | |
-"use strict"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 commentThe 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 |
||
const animal = { type: 'cat', canMeow: true }; | ||
assertEqual(animal.type, 'cat'); | ||
animal.canMeow; // since is cat, should not be an error | ||
const animalOrUndef = { type: 'cat', canMeow: true }; | ||
-assertEqual(animalOrUndef === null || animalOrUndef === void 0 ? void 0 : animalOrUndef.type, 'cat'); | ||
+assertEqual(animalOrUndef?.type, 'cat'); | ||
animalOrUndef.canMeow; // since is cat, should not be an error | ||
animal.canMeow; // since is cat, should not be an error |
This file was deleted.
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.