Skip to content

Commit b94b299

Browse files
authored
fix CFA for BindingElement. microsoft#49759 (microsoft#49836)
* fix CFA for BindingElement. microsoft#49759 * fix Parameter * fix controlFlowBindingPatternOrder * fix bindParameterFlow * add tests * refactor * refactor * refactor
1 parent dd98c17 commit b94b299

13 files changed

+1046
-24
lines changed

src/compiler/binder.ts

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -850,6 +850,9 @@ namespace ts {
850850
case SyntaxKind.BindingElement:
851851
bindBindingElementFlow(node as BindingElement);
852852
break;
853+
case SyntaxKind.Parameter:
854+
bindParameterFlow(node as ParameterDeclaration);
855+
break;
853856
case SyntaxKind.ObjectLiteralExpression:
854857
case SyntaxKind.ArrayLiteralExpression:
855858
case SyntaxKind.PropertyAssignment:
@@ -1655,20 +1658,40 @@ namespace ts {
16551658
}
16561659

16571660
function bindBindingElementFlow(node: BindingElement) {
1658-
if (isBindingPattern(node.name)) {
1659-
// When evaluating a binding pattern, the initializer is evaluated before the binding pattern, per:
1660-
// - https://tc39.es/ecma262/#sec-destructuring-binding-patterns-runtime-semantics-iteratorbindinginitialization
1661-
// - `BindingElement: BindingPattern Initializer?`
1662-
// - https://tc39.es/ecma262/#sec-runtime-semantics-keyedbindinginitialization
1663-
// - `BindingElement: BindingPattern Initializer?`
1664-
bind(node.dotDotDotToken);
1665-
bind(node.propertyName);
1666-
bind(node.initializer);
1667-
bind(node.name);
1661+
// When evaluating a binding pattern, the initializer is evaluated before the binding pattern, per:
1662+
// - https://tc39.es/ecma262/#sec-destructuring-binding-patterns-runtime-semantics-iteratorbindinginitialization
1663+
// - `BindingElement: BindingPattern Initializer?`
1664+
// - https://tc39.es/ecma262/#sec-runtime-semantics-keyedbindinginitialization
1665+
// - `BindingElement: BindingPattern Initializer?`
1666+
bind(node.dotDotDotToken);
1667+
bind(node.propertyName);
1668+
bindInitializer(node.initializer);
1669+
bind(node.name);
1670+
}
1671+
1672+
function bindParameterFlow(node: ParameterDeclaration) {
1673+
bindEach(node.modifiers);
1674+
bind(node.dotDotDotToken);
1675+
bind(node.questionToken);
1676+
bind(node.type);
1677+
bindInitializer(node.initializer);
1678+
bind(node.name);
1679+
}
1680+
1681+
// a BindingElement/Parameter does not have side effects if initializers are not evaluated and used. (see GH#49759)
1682+
function bindInitializer(node: Expression | undefined) {
1683+
if (!node) {
1684+
return;
16681685
}
1669-
else {
1670-
bindEachChild(node);
1686+
const entryFlow = currentFlow;
1687+
bind(node);
1688+
if (entryFlow === unreachableFlow || entryFlow === currentFlow) {
1689+
return;
16711690
}
1691+
const exitFlow = createBranchLabel();
1692+
addAntecedent(exitFlow, entryFlow);
1693+
addAntecedent(exitFlow, currentFlow);
1694+
currentFlow = finishFlowLabel(exitFlow);
16721695
}
16731696

16741697
function bindJSDocTypeAlias(node: JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag) {
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
//// [controlFlowBindingElement.ts]
2+
{
3+
const data = { param: 'value' };
4+
5+
const {
6+
param = (() => { throw new Error('param is not defined') })(),
7+
} = data;
8+
9+
console.log(param); // should not trigger 'Unreachable code detected.'
10+
}
11+
12+
13+
{
14+
const data = { param: 'value' };
15+
16+
let foo: string | undefined = "";
17+
const {
18+
param = (() => { throw new Error('param is not defined') })(),
19+
} = data;
20+
21+
foo; // should be string
22+
}
23+
24+
{
25+
const data = { param: 'value' };
26+
27+
let foo: string | undefined = "";
28+
const {
29+
param = (() => { foo = undefined })(),
30+
} = data;
31+
32+
foo; // should be string | undefined
33+
}
34+
35+
{
36+
const data = { param: 'value' };
37+
38+
let foo: string | undefined = "";
39+
const {
40+
param = (() => { return "" + 1 })(),
41+
} = data;
42+
43+
foo; // should be string
44+
}
45+
46+
{
47+
interface Window {
48+
window: Window;
49+
}
50+
51+
let foo: string | undefined;
52+
let window = {} as Window;
53+
window.window = window;
54+
55+
const { [(() => { foo = ""; return 'window' as const })()]:
56+
{ [(() => { return 'window' as const })()]: bar } } = window;
57+
58+
foo; // should be string
59+
}
60+
61+
{
62+
interface Window {
63+
window: Window;
64+
}
65+
66+
let foo: string | undefined;
67+
let window = {} as Window;
68+
window.window = window;
69+
70+
const { [(() => { return 'window' as const })()]:
71+
{ [(() => { foo = ""; return 'window' as const })()]: bar } } = window;
72+
73+
foo; // should be string
74+
}
75+
76+
{
77+
interface Window {
78+
window: Window;
79+
}
80+
81+
let foo: string | undefined;
82+
let window = {} as Window;
83+
window.window = window;
84+
85+
const { [(() => { return 'window' as const })()]:
86+
{ [(() => { return 'window' as const })()]: bar = (() => { foo = ""; return window; })() } } = window;
87+
88+
foo; // should be string | undefined
89+
}
90+
91+
92+
//// [controlFlowBindingElement.js]
93+
{
94+
var data = { param: 'value' };
95+
var _a = data.param, param = _a === void 0 ? (function () { throw new Error('param is not defined'); })() : _a;
96+
console.log(param); // should not trigger 'Unreachable code detected.'
97+
}
98+
{
99+
var data = { param: 'value' };
100+
var foo = "";
101+
var _b = data.param, param = _b === void 0 ? (function () { throw new Error('param is not defined'); })() : _b;
102+
foo; // should be string
103+
}
104+
{
105+
var data = { param: 'value' };
106+
var foo_1 = "";
107+
var _c = data.param, param = _c === void 0 ? (function () { foo_1 = undefined; })() : _c;
108+
foo_1; // should be string | undefined
109+
}
110+
{
111+
var data = { param: 'value' };
112+
var foo = "";
113+
var _d = data.param, param = _d === void 0 ? (function () { return "" + 1; })() : _d;
114+
foo; // should be string
115+
}
116+
{
117+
var foo_2;
118+
var window_1 = {};
119+
window_1.window = window_1;
120+
var _e = window_1, _f = (function () { foo_2 = ""; return 'window'; })(), _g = (function () { return 'window'; })(), bar = _e[_f][_g];
121+
foo_2; // should be string
122+
}
123+
{
124+
var foo_3;
125+
var window_2 = {};
126+
window_2.window = window_2;
127+
var _h = window_2, _j = (function () { return 'window'; })(), _k = (function () { foo_3 = ""; return 'window'; })(), bar = _h[_j][_k];
128+
foo_3; // should be string
129+
}
130+
{
131+
var foo_4;
132+
var window_3 = {};
133+
window_3.window = window_3;
134+
var _l = window_3, _m = (function () { return 'window'; })(), _o = (function () { return 'window'; })(), _p = _l[_m][_o], bar = _p === void 0 ? (function () { foo_4 = ""; return window_3; })() : _p;
135+
foo_4; // should be string | undefined
136+
}
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
=== tests/cases/conformance/controlFlow/controlFlowBindingElement.ts ===
2+
{
3+
const data = { param: 'value' };
4+
>data : Symbol(data, Decl(controlFlowBindingElement.ts, 1, 9))
5+
>param : Symbol(param, Decl(controlFlowBindingElement.ts, 1, 19))
6+
7+
const {
8+
param = (() => { throw new Error('param is not defined') })(),
9+
>param : Symbol(param, Decl(controlFlowBindingElement.ts, 3, 11))
10+
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
11+
12+
} = data;
13+
>data : Symbol(data, Decl(controlFlowBindingElement.ts, 1, 9))
14+
15+
console.log(param); // should not trigger 'Unreachable code detected.'
16+
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
17+
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
18+
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
19+
>param : Symbol(param, Decl(controlFlowBindingElement.ts, 3, 11))
20+
}
21+
22+
23+
{
24+
const data = { param: 'value' };
25+
>data : Symbol(data, Decl(controlFlowBindingElement.ts, 12, 9))
26+
>param : Symbol(param, Decl(controlFlowBindingElement.ts, 12, 19))
27+
28+
let foo: string | undefined = "";
29+
>foo : Symbol(foo, Decl(controlFlowBindingElement.ts, 14, 7))
30+
31+
const {
32+
param = (() => { throw new Error('param is not defined') })(),
33+
>param : Symbol(param, Decl(controlFlowBindingElement.ts, 15, 11))
34+
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
35+
36+
} = data;
37+
>data : Symbol(data, Decl(controlFlowBindingElement.ts, 12, 9))
38+
39+
foo; // should be string
40+
>foo : Symbol(foo, Decl(controlFlowBindingElement.ts, 14, 7))
41+
}
42+
43+
{
44+
const data = { param: 'value' };
45+
>data : Symbol(data, Decl(controlFlowBindingElement.ts, 23, 9))
46+
>param : Symbol(param, Decl(controlFlowBindingElement.ts, 23, 19))
47+
48+
let foo: string | undefined = "";
49+
>foo : Symbol(foo, Decl(controlFlowBindingElement.ts, 25, 7))
50+
51+
const {
52+
param = (() => { foo = undefined })(),
53+
>param : Symbol(param, Decl(controlFlowBindingElement.ts, 26, 11))
54+
>foo : Symbol(foo, Decl(controlFlowBindingElement.ts, 25, 7))
55+
>undefined : Symbol(undefined)
56+
57+
} = data;
58+
>data : Symbol(data, Decl(controlFlowBindingElement.ts, 23, 9))
59+
60+
foo; // should be string | undefined
61+
>foo : Symbol(foo, Decl(controlFlowBindingElement.ts, 25, 7))
62+
}
63+
64+
{
65+
const data = { param: 'value' };
66+
>data : Symbol(data, Decl(controlFlowBindingElement.ts, 34, 9))
67+
>param : Symbol(param, Decl(controlFlowBindingElement.ts, 34, 19))
68+
69+
let foo: string | undefined = "";
70+
>foo : Symbol(foo, Decl(controlFlowBindingElement.ts, 36, 7))
71+
72+
const {
73+
param = (() => { return "" + 1 })(),
74+
>param : Symbol(param, Decl(controlFlowBindingElement.ts, 37, 11))
75+
76+
} = data;
77+
>data : Symbol(data, Decl(controlFlowBindingElement.ts, 34, 9))
78+
79+
foo; // should be string
80+
>foo : Symbol(foo, Decl(controlFlowBindingElement.ts, 36, 7))
81+
}
82+
83+
{
84+
interface Window {
85+
>Window : Symbol(Window, Decl(controlFlowBindingElement.ts, 44, 1))
86+
87+
window: Window;
88+
>window : Symbol(Window.window, Decl(controlFlowBindingElement.ts, 45, 22))
89+
>Window : Symbol(Window, Decl(controlFlowBindingElement.ts, 44, 1))
90+
}
91+
92+
let foo: string | undefined;
93+
>foo : Symbol(foo, Decl(controlFlowBindingElement.ts, 49, 7))
94+
95+
let window = {} as Window;
96+
>window : Symbol(window, Decl(controlFlowBindingElement.ts, 50, 7))
97+
>Window : Symbol(Window, Decl(controlFlowBindingElement.ts, 44, 1))
98+
99+
window.window = window;
100+
>window.window : Symbol(Window.window, Decl(controlFlowBindingElement.ts, 45, 22))
101+
>window : Symbol(window, Decl(controlFlowBindingElement.ts, 50, 7))
102+
>window : Symbol(Window.window, Decl(controlFlowBindingElement.ts, 45, 22))
103+
>window : Symbol(window, Decl(controlFlowBindingElement.ts, 50, 7))
104+
105+
const { [(() => { foo = ""; return 'window' as const })()]:
106+
>foo : Symbol(foo, Decl(controlFlowBindingElement.ts, 49, 7))
107+
>const : Symbol(const)
108+
109+
{ [(() => { return 'window' as const })()]: bar } } = window;
110+
>const : Symbol(const)
111+
>bar : Symbol(bar, Decl(controlFlowBindingElement.ts, 54, 9))
112+
>window : Symbol(window, Decl(controlFlowBindingElement.ts, 50, 7))
113+
114+
foo; // should be string
115+
>foo : Symbol(foo, Decl(controlFlowBindingElement.ts, 49, 7))
116+
}
117+
118+
{
119+
interface Window {
120+
>Window : Symbol(Window, Decl(controlFlowBindingElement.ts, 59, 1))
121+
122+
window: Window;
123+
>window : Symbol(Window.window, Decl(controlFlowBindingElement.ts, 60, 22))
124+
>Window : Symbol(Window, Decl(controlFlowBindingElement.ts, 59, 1))
125+
}
126+
127+
let foo: string | undefined;
128+
>foo : Symbol(foo, Decl(controlFlowBindingElement.ts, 64, 7))
129+
130+
let window = {} as Window;
131+
>window : Symbol(window, Decl(controlFlowBindingElement.ts, 65, 7))
132+
>Window : Symbol(Window, Decl(controlFlowBindingElement.ts, 59, 1))
133+
134+
window.window = window;
135+
>window.window : Symbol(Window.window, Decl(controlFlowBindingElement.ts, 60, 22))
136+
>window : Symbol(window, Decl(controlFlowBindingElement.ts, 65, 7))
137+
>window : Symbol(Window.window, Decl(controlFlowBindingElement.ts, 60, 22))
138+
>window : Symbol(window, Decl(controlFlowBindingElement.ts, 65, 7))
139+
140+
const { [(() => { return 'window' as const })()]:
141+
>const : Symbol(const)
142+
143+
{ [(() => { foo = ""; return 'window' as const })()]: bar } } = window;
144+
>foo : Symbol(foo, Decl(controlFlowBindingElement.ts, 64, 7))
145+
>const : Symbol(const)
146+
>bar : Symbol(bar, Decl(controlFlowBindingElement.ts, 69, 9))
147+
>window : Symbol(window, Decl(controlFlowBindingElement.ts, 65, 7))
148+
149+
foo; // should be string
150+
>foo : Symbol(foo, Decl(controlFlowBindingElement.ts, 64, 7))
151+
}
152+
153+
{
154+
interface Window {
155+
>Window : Symbol(Window, Decl(controlFlowBindingElement.ts, 74, 1))
156+
157+
window: Window;
158+
>window : Symbol(Window.window, Decl(controlFlowBindingElement.ts, 75, 22))
159+
>Window : Symbol(Window, Decl(controlFlowBindingElement.ts, 74, 1))
160+
}
161+
162+
let foo: string | undefined;
163+
>foo : Symbol(foo, Decl(controlFlowBindingElement.ts, 79, 7))
164+
165+
let window = {} as Window;
166+
>window : Symbol(window, Decl(controlFlowBindingElement.ts, 80, 7))
167+
>Window : Symbol(Window, Decl(controlFlowBindingElement.ts, 74, 1))
168+
169+
window.window = window;
170+
>window.window : Symbol(Window.window, Decl(controlFlowBindingElement.ts, 75, 22))
171+
>window : Symbol(window, Decl(controlFlowBindingElement.ts, 80, 7))
172+
>window : Symbol(Window.window, Decl(controlFlowBindingElement.ts, 75, 22))
173+
>window : Symbol(window, Decl(controlFlowBindingElement.ts, 80, 7))
174+
175+
const { [(() => { return 'window' as const })()]:
176+
>const : Symbol(const)
177+
178+
{ [(() => { return 'window' as const })()]: bar = (() => { foo = ""; return window; })() } } = window;
179+
>const : Symbol(const)
180+
>bar : Symbol(bar, Decl(controlFlowBindingElement.ts, 84, 9))
181+
>foo : Symbol(foo, Decl(controlFlowBindingElement.ts, 79, 7))
182+
>window : Symbol(window, Decl(controlFlowBindingElement.ts, 80, 7))
183+
>window : Symbol(window, Decl(controlFlowBindingElement.ts, 80, 7))
184+
185+
foo; // should be string | undefined
186+
>foo : Symbol(foo, Decl(controlFlowBindingElement.ts, 79, 7))
187+
}
188+

0 commit comments

Comments
 (0)