Skip to content

Commit 8a15291

Browse files
committed
Update control flow for correct destructuring evaluation order
1 parent 0b303ff commit 8a15291

File tree

7 files changed

+457
-0
lines changed

7 files changed

+457
-0
lines changed

src/compiler/binder.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,9 @@ namespace ts {
218218
// or if compiler options contain alwaysStrict.
219219
let inStrictMode: boolean;
220220

221+
// If we are binding an assignment pattern, we will bind certain expressions differently.
222+
let inAssignmentPattern = false;
223+
221224
let symbolCount = 0;
222225

223226
let Symbol: new (flags: SymbolFlags, name: __String) => Symbol;
@@ -275,6 +278,7 @@ namespace ts {
275278
currentExceptionTarget = undefined;
276279
activeLabelList = undefined;
277280
hasExplicitReturn = false;
281+
inAssignmentPattern = false;
278282
emitFlags = NodeFlags.None;
279283
}
280284

@@ -733,9 +737,14 @@ namespace ts {
733737
}
734738

735739
function bindChildren(node: Node): void {
740+
const saveInAssignmentPattern = inAssignmentPattern;
741+
// Most nodes aren't valid in an assignment pattern, so we clear the value here
742+
// and set it before we descend into nodes that could actually be part of an assignment pattern.
743+
inAssignmentPattern = false;
736744
if (checkUnreachable(node)) {
737745
bindEachChild(node);
738746
bindJSDoc(node);
747+
inAssignmentPattern = saveInAssignmentPattern;
739748
return;
740749
}
741750
if (node.kind >= SyntaxKind.FirstStatement && node.kind <= SyntaxKind.LastStatement && !options.allowUnreachableCode) {
@@ -791,6 +800,13 @@ namespace ts {
791800
bindPostfixUnaryExpressionFlow(<PostfixUnaryExpression>node);
792801
break;
793802
case SyntaxKind.BinaryExpression:
803+
if (isDestructuringAssignment(node)) {
804+
// Carry over whether we are in an assignment pattern to
805+
// binary expressions that could actually be an initializer
806+
inAssignmentPattern = saveInAssignmentPattern;
807+
bindDestructuringAssignmentFlow(node);
808+
return;
809+
}
794810
bindBinaryExpressionFlow(<BinaryExpression>node);
795811
break;
796812
case SyntaxKind.DeleteExpression:
@@ -827,11 +843,23 @@ namespace ts {
827843
case SyntaxKind.ModuleBlock:
828844
bindEachFunctionsFirst((node as Block).statements);
829845
break;
846+
case SyntaxKind.BindingElement:
847+
bindBindingElementFlow(<BindingElement>node);
848+
break;
849+
case SyntaxKind.ObjectLiteralExpression:
850+
case SyntaxKind.ArrayLiteralExpression:
851+
case SyntaxKind.PropertyAssignment:
852+
case SyntaxKind.SpreadElement:
853+
// Carry over whether we are in an assignment pattern of Object and Array literals
854+
// as well as their children that are valid assignment targets.
855+
inAssignmentPattern = saveInAssignmentPattern;
856+
// falls through
830857
default:
831858
bindEachChild(node);
832859
break;
833860
}
834861
bindJSDoc(node);
862+
inAssignmentPattern = saveInAssignmentPattern;
835863
}
836864

837865
function isNarrowingExpression(expr: Expression): boolean {
@@ -1449,6 +1477,24 @@ namespace ts {
14491477
}
14501478
}
14511479

1480+
function bindDestructuringAssignmentFlow(node: DestructuringAssignment) {
1481+
if (inAssignmentPattern) {
1482+
inAssignmentPattern = false;
1483+
bind(node.operatorToken);
1484+
bind(node.right);
1485+
inAssignmentPattern = true;
1486+
bind(node.left);
1487+
}
1488+
else {
1489+
inAssignmentPattern = true;
1490+
bind(node.left);
1491+
inAssignmentPattern = false;
1492+
bind(node.operatorToken);
1493+
bind(node.right);
1494+
}
1495+
bindAssignmentTargetFlow(node.left);
1496+
}
1497+
14521498
const enum BindBinaryExpressionFlowState {
14531499
BindThenBindChildren,
14541500
MaybeBindLeft,
@@ -1617,6 +1663,25 @@ namespace ts {
16171663
}
16181664
}
16191665

1666+
function bindBindingElementFlow(node: BindingElement) {
1667+
if (isBindingPattern(node.name)) {
1668+
// When evaluating a binding pattern, the initializer is evaluated before the binding pattern, per:
1669+
// - https://tc39.es/ecma262/#sec-destructuring-binding-patterns-runtime-semantics-iteratorbindinginitialization
1670+
// - `BindingElement: BindingPattern Initializer?`
1671+
// - https://tc39.es/ecma262/#sec-runtime-semantics-keyedbindinginitialization
1672+
// - `BindingElement: BindingPattern Initializer?`
1673+
bindEach(node.decorators);
1674+
bindEach(node.modifiers);
1675+
bind(node.dotDotDotToken);
1676+
bind(node.propertyName);
1677+
bind(node.initializer);
1678+
bind(node.name);
1679+
}
1680+
else {
1681+
bindEachChild(node);
1682+
}
1683+
}
1684+
16201685
function bindJSDocTypeAlias(node: JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag) {
16211686
setParent(node.tagName, node);
16221687
if (node.kind !== SyntaxKind.JSDocEnumTag && node.fullName) {
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
=== tests/cases/conformance/controlFlow/controlFlowAssignmentPatternOrder.ts ===
2+
// https://github.com/microsoft/TypeScript/pull/41094#issuecomment-716044363
3+
{
4+
let a: 0 | 1 = 0;
5+
>a : Symbol(a, Decl(controlFlowAssignmentPatternOrder.ts, 2, 7))
6+
7+
let b: 0 | 1 | 9;
8+
>b : Symbol(b, Decl(controlFlowAssignmentPatternOrder.ts, 3, 7))
9+
10+
[{ [(a = 1)]: b } = [9, a] as const] = [];
11+
>[(a = 1)] : Symbol([(a = 1)], Decl(controlFlowAssignmentPatternOrder.ts, 4, 6))
12+
>a : Symbol(a, Decl(controlFlowAssignmentPatternOrder.ts, 2, 7))
13+
>b : Symbol(b, Decl(controlFlowAssignmentPatternOrder.ts, 3, 7))
14+
>a : Symbol(a, Decl(controlFlowAssignmentPatternOrder.ts, 2, 7))
15+
16+
const bb: 0 = b;
17+
>bb : Symbol(bb, Decl(controlFlowAssignmentPatternOrder.ts, 5, 9))
18+
>b : Symbol(b, Decl(controlFlowAssignmentPatternOrder.ts, 3, 7))
19+
}
20+
{
21+
let a: 0 | 1 = 1;
22+
>a : Symbol(a, Decl(controlFlowAssignmentPatternOrder.ts, 8, 7))
23+
24+
let b: 0 | 1 | 9;
25+
>b : Symbol(b, Decl(controlFlowAssignmentPatternOrder.ts, 9, 7))
26+
27+
[{ [a]: b } = [9, a = 0] as const] = [];
28+
>[a] : Symbol([a], Decl(controlFlowAssignmentPatternOrder.ts, 10, 6))
29+
>a : Symbol(a, Decl(controlFlowAssignmentPatternOrder.ts, 8, 7))
30+
>b : Symbol(b, Decl(controlFlowAssignmentPatternOrder.ts, 9, 7))
31+
>a : Symbol(a, Decl(controlFlowAssignmentPatternOrder.ts, 8, 7))
32+
33+
const bb: 9 = b;
34+
>bb : Symbol(bb, Decl(controlFlowAssignmentPatternOrder.ts, 11, 9))
35+
>b : Symbol(b, Decl(controlFlowAssignmentPatternOrder.ts, 9, 7))
36+
}
37+
{
38+
let a: 0 | 1 = 0;
39+
>a : Symbol(a, Decl(controlFlowAssignmentPatternOrder.ts, 14, 7))
40+
41+
let b: 0 | 1 | 8 | 9;
42+
>b : Symbol(b, Decl(controlFlowAssignmentPatternOrder.ts, 15, 7))
43+
44+
[{ [(a = 1)]: b } = [9, a] as const] = [[9, 8] as const];
45+
>[(a = 1)] : Symbol([(a = 1)], Decl(controlFlowAssignmentPatternOrder.ts, 16, 6))
46+
>a : Symbol(a, Decl(controlFlowAssignmentPatternOrder.ts, 14, 7))
47+
>b : Symbol(b, Decl(controlFlowAssignmentPatternOrder.ts, 15, 7))
48+
>a : Symbol(a, Decl(controlFlowAssignmentPatternOrder.ts, 14, 7))
49+
50+
const bb: 0 | 8 = b;
51+
>bb : Symbol(bb, Decl(controlFlowAssignmentPatternOrder.ts, 17, 9))
52+
>b : Symbol(b, Decl(controlFlowAssignmentPatternOrder.ts, 15, 7))
53+
}
54+
{
55+
let a: 0 | 1 = 1;
56+
>a : Symbol(a, Decl(controlFlowAssignmentPatternOrder.ts, 20, 7))
57+
58+
let b: 0 | 1 | 8 | 9;
59+
>b : Symbol(b, Decl(controlFlowAssignmentPatternOrder.ts, 21, 7))
60+
61+
[{ [a]: b } = [a = 0, 9] as const] = [[8, 9] as const];
62+
>[a] : Symbol([a], Decl(controlFlowAssignmentPatternOrder.ts, 22, 6))
63+
>a : Symbol(a, Decl(controlFlowAssignmentPatternOrder.ts, 20, 7))
64+
>b : Symbol(b, Decl(controlFlowAssignmentPatternOrder.ts, 21, 7))
65+
>a : Symbol(a, Decl(controlFlowAssignmentPatternOrder.ts, 20, 7))
66+
67+
const bb: 0 | 8 = b;
68+
>bb : Symbol(bb, Decl(controlFlowAssignmentPatternOrder.ts, 23, 9))
69+
>b : Symbol(b, Decl(controlFlowAssignmentPatternOrder.ts, 21, 7))
70+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
=== tests/cases/conformance/controlFlow/controlFlowAssignmentPatternOrder.ts ===
2+
// https://github.com/microsoft/TypeScript/pull/41094#issuecomment-716044363
3+
{
4+
let a: 0 | 1 = 0;
5+
>a : 0 | 1
6+
>0 : 0
7+
8+
let b: 0 | 1 | 9;
9+
>b : 0 | 1 | 9
10+
11+
[{ [(a = 1)]: b } = [9, a] as const] = [];
12+
>[{ [(a = 1)]: b } = [9, a] as const] = [] : []
13+
>[{ [(a = 1)]: b } = [9, a] as const] : [readonly [9, 0]]
14+
>{ [(a = 1)]: b } = [9, a] as const : readonly [9, 0]
15+
>{ [(a = 1)]: b } : { 1: 0 | 1 | 9; }
16+
>[(a = 1)] : 0 | 1 | 9
17+
>(a = 1) : 1
18+
>a = 1 : 1
19+
>a : 0 | 1
20+
>1 : 1
21+
>b : 0 | 1 | 9
22+
>[9, a] as const : readonly [9, 0]
23+
>[9, a] : readonly [9, 0]
24+
>9 : 9
25+
>a : 0
26+
>[] : []
27+
28+
const bb: 0 = b;
29+
>bb : 0
30+
>b : 0
31+
}
32+
{
33+
let a: 0 | 1 = 1;
34+
>a : 0 | 1
35+
>1 : 1
36+
37+
let b: 0 | 1 | 9;
38+
>b : 0 | 1 | 9
39+
40+
[{ [a]: b } = [9, a = 0] as const] = [];
41+
>[{ [a]: b } = [9, a = 0] as const] = [] : []
42+
>[{ [a]: b } = [9, a = 0] as const] : [readonly [9, 0]]
43+
>{ [a]: b } = [9, a = 0] as const : readonly [9, 0]
44+
>{ [a]: b } : { 0: 0 | 1 | 9; }
45+
>[a] : 0 | 1 | 9
46+
>a : 0
47+
>b : 0 | 1 | 9
48+
>[9, a = 0] as const : readonly [9, 0]
49+
>[9, a = 0] : readonly [9, 0]
50+
>9 : 9
51+
>a = 0 : 0
52+
>a : 0 | 1
53+
>0 : 0
54+
>[] : []
55+
56+
const bb: 9 = b;
57+
>bb : 9
58+
>b : 9
59+
}
60+
{
61+
let a: 0 | 1 = 0;
62+
>a : 0 | 1
63+
>0 : 0
64+
65+
let b: 0 | 1 | 8 | 9;
66+
>b : 0 | 1 | 9 | 8
67+
68+
[{ [(a = 1)]: b } = [9, a] as const] = [[9, 8] as const];
69+
>[{ [(a = 1)]: b } = [9, a] as const] = [[9, 8] as const] : [readonly [9, 8]]
70+
>[{ [(a = 1)]: b } = [9, a] as const] : [readonly [9, 0]]
71+
>{ [(a = 1)]: b } = [9, a] as const : readonly [9, 0]
72+
>{ [(a = 1)]: b } : { 1: 0 | 1 | 9 | 8; }
73+
>[(a = 1)] : 0 | 1 | 9 | 8
74+
>(a = 1) : 1
75+
>a = 1 : 1
76+
>a : 0 | 1
77+
>1 : 1
78+
>b : 0 | 1 | 9 | 8
79+
>[9, a] as const : readonly [9, 0]
80+
>[9, a] : readonly [9, 0]
81+
>9 : 9
82+
>a : 0
83+
>[[9, 8] as const] : [readonly [9, 8]]
84+
>[9, 8] as const : readonly [9, 8]
85+
>[9, 8] : readonly [9, 8]
86+
>9 : 9
87+
>8 : 8
88+
89+
const bb: 0 | 8 = b;
90+
>bb : 0 | 8
91+
>b : 0 | 8
92+
}
93+
{
94+
let a: 0 | 1 = 1;
95+
>a : 0 | 1
96+
>1 : 1
97+
98+
let b: 0 | 1 | 8 | 9;
99+
>b : 0 | 1 | 9 | 8
100+
101+
[{ [a]: b } = [a = 0, 9] as const] = [[8, 9] as const];
102+
>[{ [a]: b } = [a = 0, 9] as const] = [[8, 9] as const] : [readonly [8, 9]]
103+
>[{ [a]: b } = [a = 0, 9] as const] : [readonly [0, 9]]
104+
>{ [a]: b } = [a = 0, 9] as const : readonly [0, 9]
105+
>{ [a]: b } : { 0: 0 | 1 | 9 | 8; }
106+
>[a] : 0 | 1 | 9 | 8
107+
>a : 0
108+
>b : 0 | 1 | 9 | 8
109+
>[a = 0, 9] as const : readonly [0, 9]
110+
>[a = 0, 9] : readonly [0, 9]
111+
>a = 0 : 0
112+
>a : 0 | 1
113+
>0 : 0
114+
>9 : 9
115+
>[[8, 9] as const] : [readonly [8, 9]]
116+
>[8, 9] as const : readonly [8, 9]
117+
>[8, 9] : readonly [8, 9]
118+
>8 : 8
119+
>9 : 9
120+
121+
const bb: 0 | 8 = b;
122+
>bb : 0 | 8
123+
>b : 0 | 8
124+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
=== tests/cases/conformance/controlFlow/controlFlowBindingPatternOrder.ts ===
2+
// https://github.com/microsoft/TypeScript/pull/41094#issuecomment-716044363
3+
{
4+
let a: 0 | 1 = 0;
5+
>a : Symbol(a, Decl(controlFlowBindingPatternOrder.ts, 2, 7))
6+
7+
const [{ [(a = 1)]: b } = [9, a] as const] = [];
8+
>a : Symbol(a, Decl(controlFlowBindingPatternOrder.ts, 2, 7))
9+
>b : Symbol(b, Decl(controlFlowBindingPatternOrder.ts, 3, 12))
10+
>a : Symbol(a, Decl(controlFlowBindingPatternOrder.ts, 2, 7))
11+
12+
const bb: 0 = b;
13+
>bb : Symbol(bb, Decl(controlFlowBindingPatternOrder.ts, 4, 9))
14+
>b : Symbol(b, Decl(controlFlowBindingPatternOrder.ts, 3, 12))
15+
}
16+
{
17+
let a: 0 | 1 = 1;
18+
>a : Symbol(a, Decl(controlFlowBindingPatternOrder.ts, 7, 7))
19+
20+
const [{ [a]: b } = [9, a = 0] as const] = [];
21+
>a : Symbol(a, Decl(controlFlowBindingPatternOrder.ts, 7, 7))
22+
>b : Symbol(b, Decl(controlFlowBindingPatternOrder.ts, 8, 12))
23+
>a : Symbol(a, Decl(controlFlowBindingPatternOrder.ts, 7, 7))
24+
25+
const bb: 9 = b;
26+
>bb : Symbol(bb, Decl(controlFlowBindingPatternOrder.ts, 9, 9))
27+
>b : Symbol(b, Decl(controlFlowBindingPatternOrder.ts, 8, 12))
28+
}
29+
{
30+
let a: 0 | 1 = 0;
31+
>a : Symbol(a, Decl(controlFlowBindingPatternOrder.ts, 12, 7))
32+
33+
const [{ [(a = 1)]: b } = [9, a] as const] = [[9, 8] as const];
34+
>a : Symbol(a, Decl(controlFlowBindingPatternOrder.ts, 12, 7))
35+
>b : Symbol(b, Decl(controlFlowBindingPatternOrder.ts, 13, 12))
36+
>a : Symbol(a, Decl(controlFlowBindingPatternOrder.ts, 12, 7))
37+
38+
const bb: 0 | 8 = b;
39+
>bb : Symbol(bb, Decl(controlFlowBindingPatternOrder.ts, 14, 9))
40+
>b : Symbol(b, Decl(controlFlowBindingPatternOrder.ts, 13, 12))
41+
}
42+
{
43+
let a: 0 | 1 = 1;
44+
>a : Symbol(a, Decl(controlFlowBindingPatternOrder.ts, 17, 7))
45+
46+
const [{ [a]: b } = [a = 0, 9] as const] = [[8, 9] as const];
47+
>a : Symbol(a, Decl(controlFlowBindingPatternOrder.ts, 17, 7))
48+
>b : Symbol(b, Decl(controlFlowBindingPatternOrder.ts, 18, 12))
49+
>a : Symbol(a, Decl(controlFlowBindingPatternOrder.ts, 17, 7))
50+
51+
const bb: 0 | 8 = b;
52+
>bb : Symbol(bb, Decl(controlFlowBindingPatternOrder.ts, 19, 9))
53+
>b : Symbol(b, Decl(controlFlowBindingPatternOrder.ts, 18, 12))
54+
}

0 commit comments

Comments
 (0)