Skip to content

Commit 8058619

Browse files
authored
Control flow analysis of aliased conditional expressions and discriminants (#44730)
* CFA inlining of conditional expressions referenced by const variables * Accept new baselines * Add tests * Accept new baselines * Increase inlining limit to 5 levels per design meeting discussion
1 parent 906cbd2 commit 8058619

10 files changed

+2398
-40
lines changed

src/compiler/checker.ts

+91-35
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
tests/cases/conformance/controlFlow/controlFlowAliasing.ts(61,13): error TS2339: Property 'foo' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'.
2+
Property 'foo' does not exist on type '{ kind: "bar"; bar: number; }'.
3+
tests/cases/conformance/controlFlow/controlFlowAliasing.ts(64,13): error TS2339: Property 'bar' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'.
4+
Property 'bar' does not exist on type '{ kind: "foo"; foo: string; }'.
5+
tests/cases/conformance/controlFlow/controlFlowAliasing.ts(71,13): error TS2339: Property 'foo' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'.
6+
Property 'foo' does not exist on type '{ kind: "bar"; bar: number; }'.
7+
tests/cases/conformance/controlFlow/controlFlowAliasing.ts(74,13): error TS2339: Property 'bar' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'.
8+
Property 'bar' does not exist on type '{ kind: "foo"; foo: string; }'.
9+
tests/cases/conformance/controlFlow/controlFlowAliasing.ts(82,13): error TS2339: Property 'foo' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'.
10+
Property 'foo' does not exist on type '{ kind: "bar"; bar: number; }'.
11+
tests/cases/conformance/controlFlow/controlFlowAliasing.ts(85,13): error TS2339: Property 'bar' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'.
12+
Property 'bar' does not exist on type '{ kind: "foo"; foo: string; }'.
13+
tests/cases/conformance/controlFlow/controlFlowAliasing.ts(104,13): error TS2339: Property 'foo' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'.
14+
Property 'foo' does not exist on type '{ kind: "bar"; bar: number; }'.
15+
tests/cases/conformance/controlFlow/controlFlowAliasing.ts(107,13): error TS2339: Property 'bar' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'.
16+
Property 'bar' does not exist on type '{ kind: "foo"; foo: string; }'.
17+
tests/cases/conformance/controlFlow/controlFlowAliasing.ts(124,19): error TS2339: Property 'foo' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'.
18+
Property 'foo' does not exist on type '{ kind: "bar"; bar: number; }'.
19+
tests/cases/conformance/controlFlow/controlFlowAliasing.ts(127,19): error TS2339: Property 'bar' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'.
20+
Property 'bar' does not exist on type '{ kind: "foo"; foo: string; }'.
21+
tests/cases/conformance/controlFlow/controlFlowAliasing.ts(207,13): error TS2322: Type 'string | number' is not assignable to type 'string'.
22+
Type 'number' is not assignable to type 'string'.
23+
tests/cases/conformance/controlFlow/controlFlowAliasing.ts(210,13): error TS2322: Type 'string | number' is not assignable to type 'number'.
24+
Type 'string' is not assignable to type 'number'.
25+
26+
27+
==== tests/cases/conformance/controlFlow/controlFlowAliasing.ts (12 errors) ====
28+
// Narrowing by aliased conditional expressions
29+
30+
function f10(x: string | number) {
31+
const isString = typeof x === "string";
32+
if (isString) {
33+
let t: string = x;
34+
}
35+
else {
36+
let t: number = x;
37+
}
38+
}
39+
40+
function f11(x: unknown) {
41+
const isString = typeof x === "string";
42+
if (isString) {
43+
let t: string = x;
44+
}
45+
}
46+
47+
function f12(x: string | number | boolean) {
48+
const isString = typeof x === "string";
49+
const isNumber = typeof x === "number";
50+
if (isString || isNumber) {
51+
let t: string | number = x;
52+
}
53+
else {
54+
let t: boolean = x;
55+
}
56+
}
57+
58+
function f13(x: string | number | boolean) {
59+
const isString = typeof x === "string";
60+
const isNumber = typeof x === "number";
61+
const isStringOrNumber = isString || isNumber;
62+
if (isStringOrNumber) {
63+
let t: string | number = x;
64+
}
65+
else {
66+
let t: boolean = x;
67+
}
68+
}
69+
70+
function f14(x: number | null | undefined): number | null {
71+
const notUndefined = x !== undefined;
72+
return notUndefined ? x : 0;
73+
}
74+
75+
function f20(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) {
76+
const isFoo = obj.kind === 'foo';
77+
if (isFoo) {
78+
obj.foo;
79+
}
80+
else {
81+
obj.bar;
82+
}
83+
}
84+
85+
function f21(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) {
86+
const isFoo: boolean = obj.kind === 'foo';
87+
if (isFoo) {
88+
obj.foo; // Not narrowed because isFoo has type annotation
89+
~~~
90+
!!! error TS2339: Property 'foo' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'.
91+
!!! error TS2339: Property 'foo' does not exist on type '{ kind: "bar"; bar: number; }'.
92+
}
93+
else {
94+
obj.bar; // Not narrowed because isFoo has type annotation
95+
~~~
96+
!!! error TS2339: Property 'bar' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'.
97+
!!! error TS2339: Property 'bar' does not exist on type '{ kind: "foo"; foo: string; }'.
98+
}
99+
}
100+
101+
function f22(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) {
102+
let isFoo = obj.kind === 'foo';
103+
if (isFoo) {
104+
obj.foo; // Not narrowed because isFoo is mutable
105+
~~~
106+
!!! error TS2339: Property 'foo' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'.
107+
!!! error TS2339: Property 'foo' does not exist on type '{ kind: "bar"; bar: number; }'.
108+
}
109+
else {
110+
obj.bar; // Not narrowed because isFoo is mutable
111+
~~~
112+
!!! error TS2339: Property 'bar' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'.
113+
!!! error TS2339: Property 'bar' does not exist on type '{ kind: "foo"; foo: string; }'.
114+
}
115+
}
116+
117+
function f23(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) {
118+
const isFoo = obj.kind === 'foo';
119+
obj = obj;
120+
if (isFoo) {
121+
obj.foo; // Not narrowed because obj is assigned in function body
122+
~~~
123+
!!! error TS2339: Property 'foo' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'.
124+
!!! error TS2339: Property 'foo' does not exist on type '{ kind: "bar"; bar: number; }'.
125+
}
126+
else {
127+
obj.bar; // Not narrowed because obj is assigned in function body
128+
~~~
129+
!!! error TS2339: Property 'bar' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'.
130+
!!! error TS2339: Property 'bar' does not exist on type '{ kind: "foo"; foo: string; }'.
131+
}
132+
}
133+
134+
function f24(arg: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) {
135+
const obj = arg;
136+
const isFoo = obj.kind === 'foo';
137+
if (isFoo) {
138+
obj.foo;
139+
}
140+
else {
141+
obj.bar;
142+
}
143+
}
144+
145+
function f25(arg: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) {
146+
let obj = arg;
147+
const isFoo = obj.kind === 'foo';
148+
if (isFoo) {
149+
obj.foo; // Not narrowed because obj is mutable
150+
~~~
151+
!!! error TS2339: Property 'foo' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'.
152+
!!! error TS2339: Property 'foo' does not exist on type '{ kind: "bar"; bar: number; }'.
153+
}
154+
else {
155+
obj.bar; // Not narrowed because obj is mutable
156+
~~~
157+
!!! error TS2339: Property 'bar' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'.
158+
!!! error TS2339: Property 'bar' does not exist on type '{ kind: "foo"; foo: string; }'.
159+
}
160+
}
161+
162+
function f26(outer: { readonly obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number } }) {
163+
const isFoo = outer.obj.kind === 'foo';
164+
if (isFoo) {
165+
outer.obj.foo;
166+
}
167+
else {
168+
outer.obj.bar;
169+
}
170+
}
171+
172+
function f27(outer: { obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number } }) {
173+
const isFoo = outer.obj.kind === 'foo';
174+
if (isFoo) {
175+
outer.obj.foo; // Not narrowed because obj is mutable
176+
~~~
177+
!!! error TS2339: Property 'foo' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'.
178+
!!! error TS2339: Property 'foo' does not exist on type '{ kind: "bar"; bar: number; }'.
179+
}
180+
else {
181+
outer.obj.bar; // Not narrowed because obj is mutable
182+
~~~
183+
!!! error TS2339: Property 'bar' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'.
184+
!!! error TS2339: Property 'bar' does not exist on type '{ kind: "foo"; foo: string; }'.
185+
}
186+
}
187+
188+
function f28(obj?: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) {
189+
const isFoo = obj && obj.kind === 'foo';
190+
const isBar = obj && obj.kind === 'bar';
191+
if (isFoo) {
192+
obj.foo;
193+
}
194+
if (isBar) {
195+
obj.bar;
196+
}
197+
}
198+
199+
// Narrowing by aliased discriminant property access
200+
201+
function f30(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) {
202+
const kind = obj.kind;
203+
if (kind === 'foo') {
204+
obj.foo;
205+
}
206+
else {
207+
obj.bar;
208+
}
209+
}
210+
211+
function f31(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) {
212+
const { kind } = obj;
213+
if (kind === 'foo') {
214+
obj.foo;
215+
}
216+
else {
217+
obj.bar;
218+
}
219+
}
220+
221+
function f32(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) {
222+
const { kind: k } = obj;
223+
if (k === 'foo') {
224+
obj.foo;
225+
}
226+
else {
227+
obj.bar;
228+
}
229+
}
230+
231+
function f33(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) {
232+
const { kind } = obj;
233+
switch (kind) {
234+
case 'foo': obj.foo; break;
235+
case 'bar': obj.bar; break;
236+
}
237+
}
238+
239+
// Mixing of aliased discriminants and conditionals
240+
241+
function f40(obj: { kind: 'foo', foo?: string } | { kind: 'bar', bar?: number }) {
242+
const { kind } = obj;
243+
const isFoo = kind == 'foo';
244+
if (isFoo && obj.foo) {
245+
let t: string = obj.foo;
246+
}
247+
}
248+
249+
// Unsupported narrowing of destructured payload by destructured discriminant
250+
251+
type Data = { kind: 'str', payload: string } | { kind: 'num', payload: number };
252+
253+
function gg2(obj: Data) {
254+
if (obj.kind === 'str') {
255+
let t: string = obj.payload;
256+
}
257+
else {
258+
let t: number = obj.payload;
259+
}
260+
}
261+
262+
function foo({ kind, payload }: Data) {
263+
if (kind === 'str') {
264+
let t: string = payload;
265+
~
266+
!!! error TS2322: Type 'string | number' is not assignable to type 'string'.
267+
!!! error TS2322: Type 'number' is not assignable to type 'string'.
268+
}
269+
else {
270+
let t: number = payload;
271+
~
272+
!!! error TS2322: Type 'string | number' is not assignable to type 'number'.
273+
!!! error TS2322: Type 'string' is not assignable to type 'number'.
274+
}
275+
}
276+

0 commit comments

Comments
 (0)