Skip to content

Commit 8f40ac0

Browse files
ajafffrbuckton
authored andcommitted
optimize transform of optional chaining and nullish coalescing (microsoft#34951)
* optimize transform of optional chaining and nullish coalescing * remove unnecessary condition * typo * fix lint * prevent capturing of super * swap branches again * accept new baselines * avoid temporary objects
1 parent aa0cb88 commit 8f40ac0

36 files changed

+533
-390
lines changed

src/compiler/transformers/esnext.ts

Lines changed: 57 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -58,43 +58,29 @@ namespace ts {
5858
return updateParen(node, expression);
5959
}
6060

61-
function visitNonOptionalPropertyAccessExpression(node: PropertyAccessExpression, captureThisArg: boolean): Expression {
61+
function visitNonOptionalPropertyOrElementAccessExpression(node: AccessExpression, captureThisArg: boolean): Expression {
6262
if (isOptionalChain(node)) {
6363
// If `node` is an optional chain, then it is the outermost chain of an optional expression.
6464
return visitOptionalExpression(node, captureThisArg);
6565
}
6666

67-
let expression = visitNode(node.expression, visitor, isExpression);
67+
let expression: Expression = visitNode(node.expression, visitor, isExpression);
6868
Debug.assertNotNode(expression, isSyntheticReference);
6969

7070
let thisArg: Expression | undefined;
7171
if (captureThisArg) {
72-
// `a.b` -> { expression: `(_a = a).b`, thisArg: `_a` }
73-
thisArg = createTempVariable(hoistVariableDeclaration);
74-
expression = createParen(createAssignment(thisArg, expression));
75-
}
76-
77-
expression = updatePropertyAccess(node, expression, visitNode(node.name, visitor, isIdentifier));
78-
return thisArg ? createSyntheticReferenceExpression(expression, thisArg) : expression;
79-
}
80-
81-
function visitNonOptionalElementAccessExpression(node: ElementAccessExpression, captureThisArg: boolean): Expression {
82-
if (isOptionalChain(node)) {
83-
// If `node` is an optional chain, then it is the outermost chain of an optional expression.
84-
return visitOptionalExpression(node, captureThisArg);
85-
}
86-
87-
let expression = visitNode(node.expression, visitor, isExpression);
88-
Debug.assertNotNode(expression, isSyntheticReference);
89-
90-
let thisArg: Expression | undefined;
91-
if (captureThisArg) {
92-
// `a[b]` -> { expression: `(_a = a)[b]`, thisArg: `_a` }
93-
thisArg = createTempVariable(hoistVariableDeclaration);
94-
expression = createParen(createAssignment(thisArg, expression));
72+
if (shouldCaptureInTempVariable(expression)) {
73+
thisArg = createTempVariable(hoistVariableDeclaration);
74+
expression = createAssignment(thisArg, expression);
75+
}
76+
else {
77+
thisArg = expression;
78+
}
9579
}
9680

97-
expression = updateElementAccess(node, expression, visitNode(node.argumentExpression, visitor, isExpression));
81+
expression = node.kind === SyntaxKind.PropertyAccessExpression
82+
? updatePropertyAccess(node, expression, visitNode(node.name, visitor, isIdentifier))
83+
: updateElementAccess(node, expression, visitNode(node.argumentExpression, visitor, isExpression));
9884
return thisArg ? createSyntheticReferenceExpression(expression, thisArg) : expression;
9985
}
10086

@@ -109,8 +95,8 @@ namespace ts {
10995
function visitNonOptionalExpression(node: Expression, captureThisArg: boolean): Expression {
11096
switch (node.kind) {
11197
case SyntaxKind.ParenthesizedExpression: return visitNonOptionalParenthesizedExpression(node as ParenthesizedExpression, captureThisArg);
112-
case SyntaxKind.PropertyAccessExpression: return visitNonOptionalPropertyAccessExpression(node as PropertyAccessExpression, captureThisArg);
113-
case SyntaxKind.ElementAccessExpression: return visitNonOptionalElementAccessExpression(node as ElementAccessExpression, captureThisArg);
98+
case SyntaxKind.PropertyAccessExpression:
99+
case SyntaxKind.ElementAccessExpression: return visitNonOptionalPropertyOrElementAccessExpression(node as AccessExpression, captureThisArg);
114100
case SyntaxKind.CallExpression: return visitNonOptionalCallExpression(node as CallExpression, captureThisArg);
115101
default: return visitNode(node, visitor, isExpression);
116102
}
@@ -119,39 +105,38 @@ namespace ts {
119105
function visitOptionalExpression(node: OptionalChain, captureThisArg: boolean): Expression {
120106
const { expression, chain } = flattenChain(node);
121107
const left = visitNonOptionalExpression(expression, isCallChain(chain[0]));
122-
const temp = createTempVariable(hoistVariableDeclaration);
123108
const leftThisArg = isSyntheticReference(left) ? left.thisArg : undefined;
124-
const leftExpression = isSyntheticReference(left) ? left.expression : left;
125-
let rightExpression: Expression = temp;
109+
let leftExpression = isSyntheticReference(left) ? left.expression : left;
110+
let capturedLeft: Expression = leftExpression;
111+
if (shouldCaptureInTempVariable(leftExpression)) {
112+
capturedLeft = createTempVariable(hoistVariableDeclaration);
113+
leftExpression = createAssignment(capturedLeft, leftExpression);
114+
}
115+
let rightExpression = capturedLeft;
126116
let thisArg: Expression | undefined;
127117
for (let i = 0; i < chain.length; i++) {
128118
const segment = chain[i];
129119
switch (segment.kind) {
130120
case SyntaxKind.PropertyAccessExpression:
131-
if (i === chain.length - 1 && captureThisArg) {
132-
thisArg = createTempVariable(hoistVariableDeclaration);
133-
rightExpression = createParen(createAssignment(thisArg, rightExpression));
134-
}
135-
rightExpression = createPropertyAccess(
136-
rightExpression,
137-
visitNode(segment.name, visitor, isIdentifier)
138-
);
139-
break;
140121
case SyntaxKind.ElementAccessExpression:
141122
if (i === chain.length - 1 && captureThisArg) {
142-
thisArg = createTempVariable(hoistVariableDeclaration);
143-
rightExpression = createParen(createAssignment(thisArg, rightExpression));
123+
if (shouldCaptureInTempVariable(rightExpression)) {
124+
thisArg = createTempVariable(hoistVariableDeclaration);
125+
rightExpression = createAssignment(thisArg, rightExpression);
126+
}
127+
else {
128+
thisArg = rightExpression;
129+
}
144130
}
145-
rightExpression = createElementAccess(
146-
rightExpression,
147-
visitNode(segment.argumentExpression, visitor, isExpression)
148-
);
131+
rightExpression = segment.kind === SyntaxKind.PropertyAccessExpression
132+
? createPropertyAccess(rightExpression, visitNode(segment.name, visitor, isIdentifier))
133+
: createElementAccess(rightExpression, visitNode(segment.argumentExpression, visitor, isExpression));
149134
break;
150135
case SyntaxKind.CallExpression:
151136
if (i === 0 && leftThisArg) {
152137
rightExpression = createFunctionCall(
153138
rightExpression,
154-
leftThisArg,
139+
leftThisArg.kind === SyntaxKind.SuperKeyword ? createThis() : leftThisArg,
155140
visitNodes(segment.arguments, visitor, isExpression)
156141
);
157142
}
@@ -168,48 +153,49 @@ namespace ts {
168153
}
169154

170155
const target = createConditional(
171-
createLogicalOr(
172-
createStrictEquality(createAssignment(temp, leftExpression), createNull()),
173-
createStrictEquality(temp, createVoidZero())
174-
),
156+
createNotNullCondition(leftExpression, capturedLeft, /*invert*/ true),
175157
createVoidZero(),
176-
rightExpression
158+
rightExpression,
177159
);
178160
return thisArg ? createSyntheticReferenceExpression(target, thisArg) : target;
179161
}
180162

181-
function createNotNullCondition(node: Expression) {
163+
function createNotNullCondition(left: Expression, right: Expression, invert?: boolean) {
182164
return createBinary(
183165
createBinary(
184-
node,
185-
createToken(SyntaxKind.ExclamationEqualsEqualsToken),
166+
left,
167+
createToken(invert ? SyntaxKind.EqualsEqualsEqualsToken : SyntaxKind.ExclamationEqualsEqualsToken),
186168
createNull()
187169
),
188-
createToken(SyntaxKind.AmpersandAmpersandToken),
170+
createToken(invert ? SyntaxKind.BarBarToken : SyntaxKind.AmpersandAmpersandToken),
189171
createBinary(
190-
node,
191-
createToken(SyntaxKind.ExclamationEqualsEqualsToken),
172+
right,
173+
createToken(invert ? SyntaxKind.EqualsEqualsEqualsToken : SyntaxKind.ExclamationEqualsEqualsToken),
192174
createVoidZero()
193175
)
194176
);
195177
}
196178

197179
function transformNullishCoalescingExpression(node: BinaryExpression) {
198-
const expressions: Expression[] = [];
199180
let left = visitNode(node.left, visitor, isExpression);
200-
if (!isIdentifier(left)) {
201-
const temp = createTempVariable(hoistVariableDeclaration);
202-
expressions.push(createAssignment(temp, left));
203-
left = temp;
181+
let right = left;
182+
if (shouldCaptureInTempVariable(left)) {
183+
right = createTempVariable(hoistVariableDeclaration);
184+
left = createAssignment(right, left);
204185
}
205-
expressions.push(
206-
createParen(
207-
createConditional(
208-
createNotNullCondition(left),
209-
left,
210-
visitNode(node.right, visitor, isExpression)))
211-
);
212-
return inlineExpressions(expressions);
186+
return createConditional(
187+
createNotNullCondition(left, right),
188+
right,
189+
visitNode(node.right, visitor, isExpression),
190+
);
191+
}
192+
193+
function shouldCaptureInTempVariable(expression: Expression): boolean {
194+
// don't capture identifiers and `this` in a temporary variable
195+
// `super` cannot be captured as it's no real variable
196+
return !isIdentifier(expression) &&
197+
expression.kind !== SyntaxKind.ThisKeyword &&
198+
expression.kind !== SyntaxKind.SuperKeyword;
213199
}
214200
}
215201
}

tests/baselines/reference/callChain.2.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ o3.b?.().c;
1010

1111

1212
//// [callChain.2.js]
13-
var _a, _b, _c, _d;
14-
(_a = o1) === null || _a === void 0 ? void 0 : _a();
15-
(_b = o2) === null || _b === void 0 ? void 0 : _b.b();
16-
(_d = (_c = o3).b) === null || _d === void 0 ? void 0 : _d.call(_c).c;
13+
var _a;
14+
o1 === null || o1 === void 0 ? void 0 : o1();
15+
o2 === null || o2 === void 0 ? void 0 : o2.b();
16+
(_a = o3.b) === null || _a === void 0 ? void 0 : _a.call(o3).c;

tests/baselines/reference/callChain.3.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ t1 = a!.m!({x: 12});
1212

1313
//// [callChain.3.js]
1414
"use strict";
15-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
16-
var n1 = (_c = (_a = a) === null || _a === void 0 ? void 0 : (_b = _a).m) === null || _c === void 0 ? void 0 : _c.call(_b, { x: 12 }); // should be an error (`undefined` is not assignable to `number`)
17-
var n2 = (_f = (_d = a) === null || _d === void 0 ? void 0 : (_e = _d).m) === null || _f === void 0 ? void 0 : _f.call(_e, { x: absorb() }); // likewise
18-
var n3 = (_j = (_g = a) === null || _g === void 0 ? void 0 : (_h = _g).m) === null || _j === void 0 ? void 0 : _j.call(_h, { x: 12 }); // should be ok
19-
var n4 = (_m = (_k = a) === null || _k === void 0 ? void 0 : (_l = _k).m) === null || _m === void 0 ? void 0 : _m.call(_l, { x: absorb() }); // likewise
15+
var _a, _b, _c, _d, _e;
16+
var n1 = (_a = a === null || a === void 0 ? void 0 : a.m) === null || _a === void 0 ? void 0 : _a.call(a, { x: 12 }); // should be an error (`undefined` is not assignable to `number`)
17+
var n2 = (_b = a === null || a === void 0 ? void 0 : a.m) === null || _b === void 0 ? void 0 : _b.call(a, { x: absorb() }); // likewise
18+
var n3 = (_c = a === null || a === void 0 ? void 0 : a.m) === null || _c === void 0 ? void 0 : _c.call(a, { x: 12 }); // should be ok
19+
var n4 = (_d = a === null || a === void 0 ? void 0 : a.m) === null || _d === void 0 ? void 0 : _d.call(a, { x: absorb() }); // likewise
2020
// Also a test showing `!` vs `?` for good measure
21-
var t1 = (_q = (_o = a) === null || _o === void 0 ? void 0 : (_p = _o).m) === null || _q === void 0 ? void 0 : _q.call(_p, { x: 12 });
21+
var t1 = (_e = a === null || a === void 0 ? void 0 : a.m) === null || _e === void 0 ? void 0 : _e.call(a, { x: 12 });
2222
t1 = a.m({ x: 12 });

tests/baselines/reference/callChain.js

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -46,30 +46,30 @@ var __spreadArrays = (this && this.__spreadArrays) || function () {
4646
r[k] = a[j];
4747
return r;
4848
};
49-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13;
50-
(_a = o1) === null || _a === void 0 ? void 0 : _a();
51-
(_b = o1) === null || _b === void 0 ? void 0 : _b(1);
52-
(_c = o1) === null || _c === void 0 ? void 0 : _c.apply(void 0, [1, 2]);
53-
(_d = o1) === null || _d === void 0 ? void 0 : _d.apply(void 0, __spreadArrays([1], [2, 3], [4]));
54-
(_e = o2) === null || _e === void 0 ? void 0 : _e.b();
55-
(_f = o2) === null || _f === void 0 ? void 0 : _f.b(1);
56-
(_g = o2) === null || _g === void 0 ? void 0 : _g.b.apply(_g, [1, 2]);
57-
(_h = o2) === null || _h === void 0 ? void 0 : _h.b.apply(_h, __spreadArrays([1], [2, 3], [4]));
58-
(_j = o2) === null || _j === void 0 ? void 0 : _j["b"]();
59-
(_k = o2) === null || _k === void 0 ? void 0 : _k["b"](1);
60-
(_l = o2) === null || _l === void 0 ? void 0 : _l["b"].apply(_l, [1, 2]);
61-
(_m = o2) === null || _m === void 0 ? void 0 : _m["b"].apply(_m, __spreadArrays([1], [2, 3], [4]));
62-
(_p = (_o = o3).b) === null || _p === void 0 ? void 0 : _p.call(_o).c;
63-
(_r = (_q = o3).b) === null || _r === void 0 ? void 0 : _r.call(_q, 1).c;
64-
(_t = (_s = o3).b) === null || _t === void 0 ? void 0 : _t.call.apply(_t, __spreadArrays([_s], [1, 2])).c;
65-
(_v = (_u = o3).b) === null || _v === void 0 ? void 0 : _v.call.apply(_v, __spreadArrays([_u, 1], [2, 3], [4])).c;
66-
(_x = (_w = o3).b) === null || _x === void 0 ? void 0 : _x.call(_w)["c"];
67-
(_z = (_y = o3).b) === null || _z === void 0 ? void 0 : _z.call(_y, 1)["c"];
68-
(_1 = (_0 = o3).b) === null || _1 === void 0 ? void 0 : _1.call.apply(_1, __spreadArrays([_0], [1, 2]))["c"];
69-
(_3 = (_2 = o3).b) === null || _3 === void 0 ? void 0 : _3.call.apply(_3, __spreadArrays([_2, 1], [2, 3], [4]))["c"];
70-
(_5 = (_4 = o3)["b"]) === null || _5 === void 0 ? void 0 : _5.call(_4).c;
71-
(_7 = (_6 = o3)["b"]) === null || _7 === void 0 ? void 0 : _7.call(_6, 1).c;
72-
(_9 = (_8 = o3)["b"]) === null || _9 === void 0 ? void 0 : _9.call.apply(_9, __spreadArrays([_8], [1, 2])).c;
73-
(_11 = (_10 = o3)["b"]) === null || _11 === void 0 ? void 0 : _11.call.apply(_11, __spreadArrays([_10, 1], [2, 3], [4])).c;
74-
var v = (_12 = o4) === null || _12 === void 0 ? void 0 : _12(incr);
75-
(_13 = o5()) === null || _13 === void 0 ? void 0 : _13();
49+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
50+
o1 === null || o1 === void 0 ? void 0 : o1();
51+
o1 === null || o1 === void 0 ? void 0 : o1(1);
52+
o1 === null || o1 === void 0 ? void 0 : o1.apply(void 0, [1, 2]);
53+
o1 === null || o1 === void 0 ? void 0 : o1.apply(void 0, __spreadArrays([1], [2, 3], [4]));
54+
o2 === null || o2 === void 0 ? void 0 : o2.b();
55+
o2 === null || o2 === void 0 ? void 0 : o2.b(1);
56+
o2 === null || o2 === void 0 ? void 0 : o2.b.apply(o2, [1, 2]);
57+
o2 === null || o2 === void 0 ? void 0 : o2.b.apply(o2, __spreadArrays([1], [2, 3], [4]));
58+
o2 === null || o2 === void 0 ? void 0 : o2["b"]();
59+
o2 === null || o2 === void 0 ? void 0 : o2["b"](1);
60+
o2 === null || o2 === void 0 ? void 0 : o2["b"].apply(o2, [1, 2]);
61+
o2 === null || o2 === void 0 ? void 0 : o2["b"].apply(o2, __spreadArrays([1], [2, 3], [4]));
62+
(_a = o3.b) === null || _a === void 0 ? void 0 : _a.call(o3).c;
63+
(_b = o3.b) === null || _b === void 0 ? void 0 : _b.call(o3, 1).c;
64+
(_c = o3.b) === null || _c === void 0 ? void 0 : _c.call.apply(_c, __spreadArrays([o3], [1, 2])).c;
65+
(_d = o3.b) === null || _d === void 0 ? void 0 : _d.call.apply(_d, __spreadArrays([o3, 1], [2, 3], [4])).c;
66+
(_e = o3.b) === null || _e === void 0 ? void 0 : _e.call(o3)["c"];
67+
(_f = o3.b) === null || _f === void 0 ? void 0 : _f.call(o3, 1)["c"];
68+
(_g = o3.b) === null || _g === void 0 ? void 0 : _g.call.apply(_g, __spreadArrays([o3], [1, 2]))["c"];
69+
(_h = o3.b) === null || _h === void 0 ? void 0 : _h.call.apply(_h, __spreadArrays([o3, 1], [2, 3], [4]))["c"];
70+
(_j = o3["b"]) === null || _j === void 0 ? void 0 : _j.call(o3).c;
71+
(_k = o3["b"]) === null || _k === void 0 ? void 0 : _k.call(o3, 1).c;
72+
(_l = o3["b"]) === null || _l === void 0 ? void 0 : _l.call.apply(_l, __spreadArrays([o3], [1, 2])).c;
73+
(_m = o3["b"]) === null || _m === void 0 ? void 0 : _m.call.apply(_m, __spreadArrays([o3, 1], [2, 3], [4])).c;
74+
var v = o4 === null || o4 === void 0 ? void 0 : o4(incr);
75+
(_o = o5()) === null || _o === void 0 ? void 0 : _o();

tests/baselines/reference/controlFlowNullishCoalesce.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ if (x = o ?? true) {
1717
"use strict";
1818
// assignments in shortcutting rhs
1919
var a;
20-
(o !== null && o !== void 0 ? o : (a = 1));
20+
o !== null && o !== void 0 ? o : (a = 1);
2121
a.toString();
2222
var x;
23-
if (x = (o !== null && o !== void 0 ? o : true)) {
23+
if (x = o !== null && o !== void 0 ? o : true) {
2424
x;
2525
}

0 commit comments

Comments
 (0)