Skip to content

Commit 6c17bea

Browse files
authored
Port ?. emit (#1385)
1 parent 2b82831 commit 6c17bea

File tree

130 files changed

+841
-1913
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

130 files changed

+841
-1913
lines changed

internal/ast/ast.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -879,6 +879,20 @@ func (n *Node) Elements() []*Node {
879879
return nil
880880
}
881881

882+
func (n *Node) QuestionDotToken() *Node {
883+
switch n.Kind {
884+
case KindElementAccessExpression:
885+
return n.AsElementAccessExpression().QuestionDotToken
886+
case KindPropertyAccessExpression:
887+
return n.AsPropertyAccessExpression().QuestionDotToken
888+
case KindCallExpression:
889+
return n.AsCallExpression().QuestionDotToken
890+
case KindTaggedTemplateExpression:
891+
return n.AsTaggedTemplateExpression().QuestionDotToken
892+
}
893+
panic("Unhandled case in Node.QuestionDotToken: " + n.Kind.String())
894+
}
895+
882896
// Determines if `n` contains `descendant` by walking up the `Parent` pointers from `descendant`. This method panics if
883897
// `descendant` or one of its ancestors is not parented except when that node is a `SourceFile`.
884898
func (n *Node) Contains(descendant *Node) bool {
@@ -1673,6 +1687,10 @@ func (n *Node) AsSyntaxList() *SyntaxList {
16731687
return n.data.(*SyntaxList)
16741688
}
16751689

1690+
func (n *Node) AsSyntheticReferenceExpression() *SyntheticReferenceExpression {
1691+
return n.data.(*SyntheticReferenceExpression)
1692+
}
1693+
16761694
// NodeData
16771695

16781696
type nodeData interface {
@@ -4127,6 +4145,53 @@ func IsNotEmittedTypeElement(node *Node) bool {
41274145
return node.Kind == KindNotEmittedTypeElement
41284146
}
41294147

4148+
// SyntheticReferenceExpression
4149+
// Used by optional chaining transform to shuffle a `this` arg expression between steps of a chain.
4150+
// While this does implement the full expected interface of a node, and is used in place of a node in transforms,
4151+
// it generally shouldn't be treated or visited like a normal node.
4152+
4153+
type SyntheticReferenceExpression struct {
4154+
ExpressionBase
4155+
Expression *Expression
4156+
ThisArg *Expression
4157+
}
4158+
4159+
func (f *NodeFactory) NewSyntheticReferenceExpression(expr *Expression, thisArg *Expression) *Node {
4160+
data := &SyntheticReferenceExpression{Expression: expr, ThisArg: thisArg}
4161+
return newNode(KindSyntheticReferenceExpression, data, f.hooks)
4162+
}
4163+
4164+
func (f *NodeFactory) UpdateSyntheticReferenceExpression(node *SyntheticReferenceExpression, expr *Expression, thisArg *Expression) *Node {
4165+
if expr != node.Expression || thisArg != node.ThisArg {
4166+
return updateNode(f.NewSyntheticReferenceExpression(expr, thisArg), node.AsNode(), f.hooks)
4167+
}
4168+
return node.AsNode()
4169+
}
4170+
4171+
func (node *SyntheticReferenceExpression) ForEachChild(v Visitor) bool {
4172+
return visit(v, node.Expression)
4173+
}
4174+
4175+
func (node *SyntheticReferenceExpression) VisitEachChild(v *NodeVisitor) *Node {
4176+
return v.Factory.UpdateSyntheticReferenceExpression(node, v.visitNode(node.Expression), node.ThisArg)
4177+
}
4178+
4179+
func (node *SyntheticReferenceExpression) Clone(f NodeFactoryCoercible) *Node {
4180+
return cloneNode(f.AsNodeFactory().NewSyntheticReferenceExpression(node.Expression, node.ThisArg), node.AsNode(), f.AsNodeFactory().hooks)
4181+
}
4182+
4183+
func (node *SyntheticReferenceExpression) computeSubtreeFacts() SubtreeFacts {
4184+
return propagateSubtreeFacts(node.Expression)
4185+
}
4186+
4187+
func (node *SyntheticReferenceExpression) propagateSubtreeFacts() SubtreeFacts {
4188+
return node.SubtreeFacts()
4189+
}
4190+
4191+
func IsSyntheticReferenceExpression(node *Node) bool {
4192+
return node.Kind == KindSyntheticReferenceExpression
4193+
}
4194+
41304195
// ImportEqualsDeclaration
41314196

41324197
type ImportEqualsDeclaration struct {

internal/printer/factory.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,14 @@ func (f *NodeFactory) NewGlobalMethodCall(globalObjectName string, methodName st
308308
return f.NewMethodCall(f.NewIdentifier(globalObjectName), f.NewIdentifier(methodName), argumentsList)
309309
}
310310

311+
func (f *NodeFactory) NewFunctionCallCall(target *ast.Expression, thisArg *ast.Expression, argumentsList []*ast.Node) *ast.Node {
312+
if thisArg == nil {
313+
panic("Attempted to construct function call call without this argument expression")
314+
}
315+
args := append([]*ast.Expression{thisArg}, argumentsList...)
316+
return f.NewMethodCall(target, f.NewIdentifier("call"), args)
317+
}
318+
311319
// Determines whether a node is a parenthesized expression that can be ignored when recreating outer expressions.
312320
//
313321
// A parenthesized expression can be ignored when all of the following are true:

internal/printer/printer.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3093,12 +3093,12 @@ func (p *Printer) emitExpression(node *ast.Expression, precedence ast.OperatorPr
30933093
return
30943094
case ast.KindPartiallyEmittedExpression:
30953095
p.emitPartiallyEmittedExpression(node.AsPartiallyEmittedExpression(), precedence)
3096+
case ast.KindSyntheticReferenceExpression:
3097+
panic("SyntheticReferenceExpression should not be printed")
30963098

30973099
// !!!
30983100
////case ast.KindCommaListExpression:
30993101
//// p.emitCommaList(node.AsCommaListExpression())
3100-
////case ast.KindSyntheticReferenceExpression:
3101-
//// return Debug.fail("SyntheticReferenceExpression should not be printed")
31023102

31033103
default:
31043104
panic(fmt.Sprintf("unexpected Expression: %v", node.Kind))

internal/transformers/estransforms/optionalchain.go

Lines changed: 216 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,222 @@ type optionalChainTransformer struct {
1111
}
1212

1313
func (ch *optionalChainTransformer) visit(node *ast.Node) *ast.Node {
14-
return node // !!!
14+
if node.SubtreeFacts()&ast.SubtreeContainsOptionalChaining == 0 {
15+
return node
16+
}
17+
switch node.Kind {
18+
case ast.KindCallExpression:
19+
return ch.visitCallExpression(node.AsCallExpression(), false)
20+
case ast.KindPropertyAccessExpression,
21+
ast.KindElementAccessExpression:
22+
if node.Flags&ast.NodeFlagsOptionalChain != 0 {
23+
return ch.visitOptionalExpression(node, false, false)
24+
}
25+
return ch.Visitor().VisitEachChild(node)
26+
case ast.KindDeleteExpression:
27+
return ch.visitDeleteExpression(node.AsDeleteExpression())
28+
default:
29+
return ch.Visitor().VisitEachChild(node)
30+
}
31+
}
32+
33+
func (ch *optionalChainTransformer) visitCallExpression(node *ast.CallExpression, captureThisArg bool) *ast.Node {
34+
if node.Flags&ast.NodeFlagsOptionalChain != 0 {
35+
// If `node` is an optional chain, then it is the outermost chain of an optional expression.
36+
return ch.visitOptionalExpression(node.AsNode(), captureThisArg, false)
37+
}
38+
if ast.IsParenthesizedExpression(node.Expression) {
39+
unwrapped := ast.SkipParentheses(node.Expression)
40+
if unwrapped.Flags&ast.NodeFlagsOptionalChain != 0 {
41+
// capture thisArg for calls of parenthesized optional chains like `(foo?.bar)()`
42+
expression := ch.visitParenthesizedExpression(node.Expression.AsParenthesizedExpression(), true, false)
43+
args := ch.Visitor().VisitNodes(node.Arguments)
44+
if ast.IsSyntheticReferenceExpression(expression) {
45+
res := ch.Factory().NewFunctionCallCall(expression.AsSyntheticReferenceExpression().Expression, expression.AsSyntheticReferenceExpression().ThisArg, args.Nodes)
46+
res.Loc = node.Loc
47+
ch.EmitContext().SetOriginal(res, node.AsNode())
48+
return res
49+
}
50+
return ch.Factory().UpdateCallExpression(node, expression, nil, nil, args)
51+
}
52+
}
53+
return ch.Visitor().VisitEachChild(node.AsNode())
54+
}
55+
56+
func (ch *optionalChainTransformer) visitParenthesizedExpression(node *ast.ParenthesizedExpression, captureThisArg bool, isDelete bool) *ast.Node {
57+
expr := ch.visitNonOptionalExpression(node.Expression, captureThisArg, isDelete)
58+
if ast.IsSyntheticReferenceExpression(expr) {
59+
// `(a.b)` -> { expression `((_a = a).b)`, thisArg: `_a` }
60+
// `(a[b])` -> { expression `((_a = a)[b])`, thisArg: `_a` }
61+
synth := expr.AsSyntheticReferenceExpression()
62+
res := ch.Factory().NewSyntheticReferenceExpression(ch.Factory().UpdateParenthesizedExpression(node, synth.Expression), synth.ThisArg)
63+
ch.EmitContext().SetOriginal(res, node.AsNode())
64+
return res
65+
}
66+
return ch.Factory().UpdateParenthesizedExpression(node, expr)
67+
}
68+
69+
func (ch *optionalChainTransformer) visitPropertyOrElementAccessExpression(node *ast.Expression, captureThisArg bool, isDelete bool) *ast.Expression {
70+
if node.Flags&ast.NodeFlagsOptionalChain != 0 {
71+
// If `node` is an optional chain, then it is the outermost chain of an optional expression.
72+
return ch.visitOptionalExpression(node.AsNode(), captureThisArg, isDelete)
73+
}
74+
expression := ch.Visitor().VisitNode(node.Expression())
75+
// Debug.assertNotNode(expression, isSyntheticReference); // !!!
76+
77+
var thisArg *ast.Expression
78+
if captureThisArg {
79+
if !transformers.IsSimpleCopiableExpression(expression) {
80+
thisArg = ch.Factory().NewTempVariable()
81+
ch.EmitContext().AddVariableDeclaration(thisArg)
82+
expression = ch.Factory().NewAssignmentExpression(thisArg, expression)
83+
} else {
84+
thisArg = expression
85+
}
86+
}
87+
88+
if node.Kind == ast.KindPropertyAccessExpression {
89+
p := node.AsPropertyAccessExpression()
90+
expression = ch.Factory().UpdatePropertyAccessExpression(p, expression, nil, ch.Visitor().VisitNode(p.Name()))
91+
} else {
92+
p := node.AsElementAccessExpression()
93+
expression = ch.Factory().UpdateElementAccessExpression(p, expression, nil, ch.Visitor().VisitNode(p.AsElementAccessExpression().ArgumentExpression))
94+
}
95+
96+
if thisArg != nil {
97+
res := ch.Factory().NewSyntheticReferenceExpression(expression, thisArg)
98+
ch.EmitContext().SetOriginal(res, node.AsNode())
99+
return res
100+
}
101+
return expression
102+
}
103+
104+
func (ch *optionalChainTransformer) visitDeleteExpression(node *ast.DeleteExpression) *ast.Node {
105+
unwrapped := ast.SkipParentheses(node.Expression)
106+
if unwrapped.Flags&ast.NodeFlagsOptionalChain != 0 {
107+
return ch.visitNonOptionalExpression(node.Expression, false, true)
108+
}
109+
return ch.Visitor().VisitEachChild(node.AsNode())
110+
}
111+
112+
func (ch *optionalChainTransformer) visitNonOptionalExpression(node *ast.Expression, captureThisArg bool, isDelete bool) *ast.Expression {
113+
switch node.Kind {
114+
case ast.KindParenthesizedExpression:
115+
return ch.visitParenthesizedExpression(node.AsParenthesizedExpression(), captureThisArg, isDelete)
116+
case ast.KindElementAccessExpression, ast.KindPropertyAccessExpression:
117+
return ch.visitPropertyOrElementAccessExpression(node, captureThisArg, isDelete)
118+
case ast.KindCallExpression:
119+
return ch.visitCallExpression(node.AsCallExpression(), captureThisArg)
120+
default:
121+
return ch.Visitor().VisitNode(node.AsNode())
122+
}
123+
}
124+
125+
type flattenResult struct {
126+
expression *ast.Expression
127+
chain []*ast.Node
128+
}
129+
130+
func flattenChain(chain *ast.Node) flattenResult {
131+
// Debug.assertNotNode(chain, isNonNullChain); // !!!
132+
links := []*ast.Node{chain}
133+
for !ast.IsTaggedTemplateExpression(chain) && chain.QuestionDotToken() == nil {
134+
chain = ast.SkipPartiallyEmittedExpressions(chain.Expression())
135+
// Debug.assertNotNode(chain, isNonNullChain); // !!!
136+
links = append([]*ast.Node{chain}, links...)
137+
}
138+
return flattenResult{chain.Expression(), links}
139+
}
140+
141+
func isCallChain(node *ast.Node) bool {
142+
return ast.IsCallExpression(node) && node.Flags&ast.NodeFlagsOptionalChain != 0
143+
}
144+
145+
func (ch *optionalChainTransformer) visitOptionalExpression(node *ast.Node, captureThisArg bool, isDelete bool) *ast.Node {
146+
r := flattenChain(node)
147+
expression := r.expression
148+
chain := r.chain
149+
left := ch.visitNonOptionalExpression(ast.SkipPartiallyEmittedExpressions(expression), isCallChain(chain[0]), false)
150+
var leftThisArg *ast.Expression
151+
capturedLeft := left
152+
if ast.IsSyntheticReferenceExpression(left) {
153+
leftThisArg = left.AsSyntheticReferenceExpression().ThisArg
154+
capturedLeft = left.AsSyntheticReferenceExpression().Expression
155+
}
156+
leftExpression := ch.Factory().RestoreOuterExpressions(expression, capturedLeft, ast.OEKPartiallyEmittedExpressions)
157+
if !transformers.IsSimpleCopiableExpression(capturedLeft) {
158+
capturedLeft = ch.Factory().NewTempVariable()
159+
ch.EmitContext().AddVariableDeclaration(capturedLeft)
160+
leftExpression = ch.Factory().NewAssignmentExpression(capturedLeft, leftExpression)
161+
}
162+
rightExpression := capturedLeft
163+
var thisArg *ast.Expression
164+
165+
for i, segment := range chain {
166+
switch segment.Kind {
167+
case ast.KindElementAccessExpression, ast.KindPropertyAccessExpression:
168+
if i == len(chain)-1 && captureThisArg {
169+
if !transformers.IsSimpleCopiableExpression(rightExpression) {
170+
thisArg = ch.Factory().NewTempVariable()
171+
ch.EmitContext().AddVariableDeclaration(thisArg)
172+
rightExpression = ch.Factory().NewAssignmentExpression(thisArg, rightExpression)
173+
} else {
174+
thisArg = rightExpression
175+
}
176+
}
177+
if segment.Kind == ast.KindElementAccessExpression {
178+
rightExpression = ch.Factory().NewElementAccessExpression(rightExpression, nil, ch.Visitor().VisitNode(segment.AsElementAccessExpression().ArgumentExpression), ast.NodeFlagsNone)
179+
} else {
180+
rightExpression = ch.Factory().NewPropertyAccessExpression(rightExpression, nil, ch.Visitor().VisitNode(segment.AsPropertyAccessExpression().Name()), ast.NodeFlagsNone)
181+
}
182+
case ast.KindCallExpression:
183+
if i == 0 && leftThisArg != nil {
184+
if !ch.EmitContext().HasAutoGenerateInfo(leftThisArg) {
185+
leftThisArg = leftThisArg.Clone(ch.Factory())
186+
ch.EmitContext().AddEmitFlags(leftThisArg, printer.EFNoComments)
187+
}
188+
callThisArg := leftThisArg
189+
if leftThisArg.Kind == ast.KindSuperKeyword {
190+
callThisArg = ch.Factory().NewThisExpression()
191+
}
192+
rightExpression = ch.Factory().NewFunctionCallCall(rightExpression, callThisArg, ch.Visitor().VisitNodes(segment.ArgumentList()).Nodes)
193+
} else {
194+
rightExpression = ch.Factory().NewCallExpression(
195+
rightExpression,
196+
nil,
197+
nil,
198+
ch.Visitor().VisitNodes(segment.ArgumentList()),
199+
ast.NodeFlagsNone,
200+
)
201+
}
202+
}
203+
ch.EmitContext().SetOriginal(rightExpression, segment)
204+
}
205+
206+
var target *ast.Node
207+
if isDelete {
208+
target = ch.Factory().NewConditionalExpression(
209+
createNotNullCondition(ch.EmitContext(), leftExpression, capturedLeft, true),
210+
ch.Factory().NewToken(ast.KindQuestionToken),
211+
ch.Factory().NewTrueExpression(),
212+
ch.Factory().NewToken(ast.KindColonToken),
213+
ch.Factory().NewDeleteExpression(rightExpression),
214+
)
215+
} else {
216+
target = ch.Factory().NewConditionalExpression(
217+
createNotNullCondition(ch.EmitContext(), leftExpression, capturedLeft, true),
218+
ch.Factory().NewToken(ast.KindQuestionToken),
219+
ch.Factory().NewVoidZeroExpression(),
220+
ch.Factory().NewToken(ast.KindColonToken),
221+
rightExpression,
222+
)
223+
}
224+
target.Loc = node.Loc
225+
if thisArg != nil {
226+
target = ch.Factory().NewSyntheticReferenceExpression(target, thisArg)
227+
}
228+
ch.EmitContext().SetOriginal(target, node.AsNode())
229+
return target
15230
}
16231

17232
func newOptionalChainTransformer(emitContext *printer.EmitContext) *transformers.Transformer {

testdata/baselines/reference/submodule/compiler/assertionFunctionsCanNarrowByDiscriminant.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,5 @@ const animal = { type: 'cat', canMeow: true };
3131
assertEqual(animal.type, 'cat');
3232
animal.canMeow; // since is cat, should not be an error
3333
const animalOrUndef = { type: 'cat', canMeow: true };
34-
assertEqual(animalOrUndef?.type, 'cat');
34+
assertEqual(animalOrUndef === null || animalOrUndef === void 0 ? void 0 : animalOrUndef.type, 'cat');
3535
animalOrUndef.canMeow; // since is cat, should not be an error

testdata/baselines/reference/submodule/compiler/assertionFunctionsCanNarrowByDiscriminant.js.diff

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,4 @@
77
-"use strict";
88
const animal = { type: 'cat', canMeow: true };
99
assertEqual(animal.type, 'cat');
10-
animal.canMeow; // since is cat, should not be an error
11-
const animalOrUndef = { type: 'cat', canMeow: true };
12-
-assertEqual(animalOrUndef === null || animalOrUndef === void 0 ? void 0 : animalOrUndef.type, 'cat');
13-
+assertEqual(animalOrUndef?.type, 'cat');
14-
animalOrUndef.canMeow; // since is cat, should not be an error
10+
animal.canMeow; // since is cat, should not be an error

testdata/baselines/reference/submodule/compiler/invalidOptionalChainFromNewExpression.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ new A()?.b() // ok
1010

1111

1212
//// [invalidOptionalChainFromNewExpression.js]
13+
var _a, _b;
1314
class A {
1415
b() { }
1516
}
16-
(new A)?.b(); // error
17-
new A()?.b(); // ok
17+
(_a = new A) === null || _a === void 0 ? void 0 : _a.b(); // error
18+
(_b = new A()) === null || _b === void 0 ? void 0 : _b.b(); // ok

testdata/baselines/reference/submodule/compiler/invalidOptionalChainFromNewExpression.js.diff

Lines changed: 0 additions & 14 deletions
This file was deleted.

0 commit comments

Comments
 (0)