Skip to content

Commit 15fae38

Browse files
authored
Improve narrowing of generic types in control flow analysis (#43183)
* Narrow type variables with union constraints when merited by contextual type * Narrow generics with union type constraints as indicated by contextual type * Accept new baselines * Add tests * Fix circularity for JSX elements * Remove unnecessary isConstraintPosition information from flow cache key * Update comment * Add additional tests * Rename to getNarrowableTypeForReference, remove getConstraintForLocation * Add comment * Fix removal of undefined in destructurings with initializers * Use getContextFreeTypeOfExpression in discriminateContextualTypeByObjectMembers * In obj[x], use constraint of obj's type only when x's type is non-generic * Add comment
1 parent 8a5939f commit 15fae38

30 files changed

+1400
-158
lines changed

src/compiler/checker.ts

Lines changed: 90 additions & 63 deletions
Large diffs are not rendered by default.

tests/baselines/reference/assignmentNonObjectTypeConstraints.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ function bar<T extends A | B>(x: T) {
4040

4141
var y: A | B = x; // Ok
4242
>y : A | B
43-
>x : T
43+
>x : A | B
4444
}
4545

4646
bar(new A);

tests/baselines/reference/conditionalTypes1.errors.txt

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,11 @@ tests/cases/conformance/types/conditional/conditionalTypes1.ts(12,5): error TS23
22
tests/cases/conformance/types/conditional/conditionalTypes1.ts(17,5): error TS2322: Type 'T' is not assignable to type 'NonNullable<T>'.
33
Type 'string | undefined' is not assignable to type 'NonNullable<T>'.
44
Type 'undefined' is not assignable to type 'NonNullable<T>'.
5-
tests/cases/conformance/types/conditional/conditionalTypes1.ts(18,9): error TS2322: Type 'T' is not assignable to type 'string'.
6-
Type 'string | undefined' is not assignable to type 'string'.
7-
Type 'undefined' is not assignable to type 'string'.
85
tests/cases/conformance/types/conditional/conditionalTypes1.ts(24,5): error TS2322: Type 'T[keyof T] | undefined' is not assignable to type 'NonNullable<Partial<T>[keyof T]>'.
96
Type 'undefined' is not assignable to type 'NonNullable<Partial<T>[keyof T]>'.
107
tests/cases/conformance/types/conditional/conditionalTypes1.ts(29,5): error TS2322: Type 'T["x"]' is not assignable to type 'NonNullable<T["x"]>'.
118
Type 'string | undefined' is not assignable to type 'NonNullable<T["x"]>'.
129
Type 'undefined' is not assignable to type 'NonNullable<T["x"]>'.
13-
tests/cases/conformance/types/conditional/conditionalTypes1.ts(30,9): error TS2322: Type 'T["x"]' is not assignable to type 'string'.
14-
Type 'string | undefined' is not assignable to type 'string'.
15-
Type 'undefined' is not assignable to type 'string'.
1610
tests/cases/conformance/types/conditional/conditionalTypes1.ts(103,5): error TS2322: Type 'FunctionProperties<T>' is not assignable to type 'T'.
1711
'T' could be instantiated with an arbitrary type which could be unrelated to 'FunctionProperties<T>'.
1812
tests/cases/conformance/types/conditional/conditionalTypes1.ts(104,5): error TS2322: Type 'NonFunctionProperties<T>' is not assignable to type 'T'.
@@ -63,7 +57,7 @@ tests/cases/conformance/types/conditional/conditionalTypes1.ts(288,43): error TS
6357
Type 'boolean' is not assignable to type 'true'.
6458

6559

66-
==== tests/cases/conformance/types/conditional/conditionalTypes1.ts (22 errors) ====
60+
==== tests/cases/conformance/types/conditional/conditionalTypes1.ts (20 errors) ====
6761
type T00 = Exclude<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "b" | "d"
6862
type T01 = Extract<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "a" | "c"
6963

@@ -88,10 +82,6 @@ tests/cases/conformance/types/conditional/conditionalTypes1.ts(288,43): error TS
8882
!!! error TS2322: Type 'string | undefined' is not assignable to type 'NonNullable<T>'.
8983
!!! error TS2322: Type 'undefined' is not assignable to type 'NonNullable<T>'.
9084
let s1: string = x; // Error
91-
~~
92-
!!! error TS2322: Type 'T' is not assignable to type 'string'.
93-
!!! error TS2322: Type 'string | undefined' is not assignable to type 'string'.
94-
!!! error TS2322: Type 'undefined' is not assignable to type 'string'.
9585
let s2: string = y;
9686
}
9787

@@ -111,10 +101,6 @@ tests/cases/conformance/types/conditional/conditionalTypes1.ts(288,43): error TS
111101
!!! error TS2322: Type 'string | undefined' is not assignable to type 'NonNullable<T["x"]>'.
112102
!!! error TS2322: Type 'undefined' is not assignable to type 'NonNullable<T["x"]>'.
113103
let s1: string = x; // Error
114-
~~
115-
!!! error TS2322: Type 'T["x"]' is not assignable to type 'string'.
116-
!!! error TS2322: Type 'string | undefined' is not assignable to type 'string'.
117-
!!! error TS2322: Type 'undefined' is not assignable to type 'string'.
118104
let s2: string = y;
119105
}
120106

tests/baselines/reference/conditionalTypes1.types

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ function f2<T extends string | undefined>(x: T, y: NonNullable<T>) {
5151

5252
let s1: string = x; // Error
5353
>s1 : string
54-
>x : T
54+
>x : string
5555

5656
let s2: string = y;
5757
>s2 : string
@@ -92,7 +92,7 @@ function f4<T extends { x: string | undefined }>(x: T["x"], y: NonNullable<T["x"
9292

9393
let s1: string = x; // Error
9494
>s1 : string
95-
>x : T["x"]
95+
>x : string
9696

9797
let s2: string = y;
9898
>s2 : string
@@ -476,11 +476,11 @@ function f21<T extends number | string>(x: T, y: ZeroOf<T>) {
476476

477477
let z1: number | string = y;
478478
>z1 : string | number
479-
>y : ZeroOf<T>
479+
>y : "" | 0
480480

481481
let z2: 0 | "" = y;
482482
>z2 : "" | 0
483-
>y : ZeroOf<T>
483+
>y : "" | 0
484484

485485
x = y; // Error
486486
>x = y : ZeroOf<T>
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(49,15): error TS2345: Argument of type 'undefined' is not assignable to parameter of type 'Box<T>'.
2+
tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(55,15): error TS2345: Argument of type 'undefined' is not assignable to parameter of type 'Box<unknown>'.
3+
tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(81,11): error TS2339: Property 'foo' does not exist on type 'MyUnion'.
4+
Property 'foo' does not exist on type 'AA'.
5+
tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(90,44): error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
6+
tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(91,11): error TS2339: Property 'foo' does not exist on type 'MyUnion'.
7+
Property 'foo' does not exist on type 'AA'.
8+
9+
10+
==== tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts (5 errors) ====
11+
function f1<T extends string | undefined>(x: T, y: { a: T }, z: [T]): string {
12+
if (x) {
13+
x;
14+
x.length;
15+
return x;
16+
}
17+
if (y.a) {
18+
y.a.length;
19+
return y.a;
20+
}
21+
if (z[0]) {
22+
z[0].length;
23+
return z[0];
24+
}
25+
return "hello";
26+
}
27+
28+
function f2<T>(x: Extract<T, string | undefined> | null): string {
29+
if (x) {
30+
x;
31+
x.length;
32+
return x;
33+
}
34+
return "hello";
35+
}
36+
37+
interface Box<T> {
38+
item: T;
39+
}
40+
41+
declare function isBox(x: any): x is Box<unknown>;
42+
declare function isUndefined(x: unknown): x is undefined;
43+
declare function unbox<T>(x: Box<T>): T;
44+
45+
function g1<T extends Box<T> | undefined>(x: T) {
46+
if (isBox(x)) {
47+
unbox(x);
48+
}
49+
}
50+
51+
function g2<T extends Box<T> | undefined>(x: T) {
52+
if (!isUndefined(x)) {
53+
unbox(x);
54+
}
55+
}
56+
57+
function g3<T extends Box<T> | undefined>(x: T) {
58+
if (!isBox(x)) {
59+
unbox(x); // Error
60+
~
61+
!!! error TS2345: Argument of type 'undefined' is not assignable to parameter of type 'Box<T>'.
62+
}
63+
}
64+
65+
function g4<T extends Box<T> | undefined>(x: T) {
66+
if (isUndefined(x)) {
67+
unbox(x); // Error
68+
~
69+
!!! error TS2345: Argument of type 'undefined' is not assignable to parameter of type 'Box<unknown>'.
70+
}
71+
}
72+
73+
// Repro from #13995
74+
75+
declare function takeA(val: 'A'): void;
76+
export function bounceAndTakeIfA<AB extends 'A' | 'B'>(value: AB): AB {
77+
if (value === 'A') {
78+
takeA(value);
79+
return value;
80+
}
81+
else {
82+
return value;
83+
}
84+
}
85+
86+
// Repro from #13995
87+
88+
type Common = { id: number };
89+
type AA = { tag: 'A', id: number };
90+
type BB = { tag: 'B', id: number, foo: number };
91+
92+
type MyUnion = AA | BB;
93+
94+
const fn = (value: MyUnion) => {
95+
value.foo; // Error
96+
~~~
97+
!!! error TS2339: Property 'foo' does not exist on type 'MyUnion'.
98+
!!! error TS2339: Property 'foo' does not exist on type 'AA'.
99+
if ('foo' in value) {
100+
value.foo;
101+
}
102+
if (value.tag === 'B') {
103+
value.foo;
104+
}
105+
};
106+
107+
const fn2 = <T extends MyUnion>(value: T): MyUnion => {
108+
~~~~~~~
109+
!!! error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
110+
value.foo; // Error
111+
~~~
112+
!!! error TS2339: Property 'foo' does not exist on type 'MyUnion'.
113+
!!! error TS2339: Property 'foo' does not exist on type 'AA'.
114+
if ('foo' in value) {
115+
value.foo;
116+
}
117+
if (value.tag === 'B') {
118+
value.foo;
119+
}
120+
};
121+
122+
// Repro from #13995
123+
124+
type A1 = {
125+
testable: true
126+
doTest: () => void
127+
}
128+
type B1 = {
129+
testable: false
130+
};
131+
132+
type Union = A1 | B1
133+
134+
function notWorking<T extends Union>(object: T) {
135+
if (!object.testable) return;
136+
object.doTest();
137+
}
138+
139+
// Repro from #42939
140+
141+
interface A {
142+
a: number | null;
143+
};
144+
145+
function get<K extends keyof A>(key: K, obj: A): number {
146+
const value = obj[key];
147+
if (value !== null) {
148+
return value;
149+
}
150+
return 0;
151+
};
152+

0 commit comments

Comments
 (0)