Skip to content

Commit c968eed

Browse files
authored
Merge pull request microsoft#14472 from Microsoft/typeParameterTypeGuards
Fix type parameter type guards
2 parents 41226d0 + 478c0d8 commit c968eed

File tree

5 files changed

+322
-7
lines changed

5 files changed

+322
-7
lines changed

src/compiler/checker.ts

+13-7
Original file line numberDiff line numberDiff line change
@@ -9863,9 +9863,8 @@ namespace ts {
98639863
if (flags & TypeFlags.NonPrimitive) {
98649864
return strictNullChecks ? TypeFacts.ObjectStrictFacts : TypeFacts.ObjectFacts;
98659865
}
9866-
if (flags & TypeFlags.TypeParameter) {
9867-
const constraint = getConstraintOfTypeParameter(<TypeParameter>type);
9868-
return getTypeFacts(constraint || emptyObjectType);
9866+
if (flags & TypeFlags.TypeVariable) {
9867+
return getTypeFacts(getBaseConstraintOfType(<TypeVariable>type) || emptyObjectType);
98699868
}
98709869
if (flags & TypeFlags.UnionOrIntersection) {
98719870
return getTypeFactsOfTypes((<UnionOrIntersectionType>type).types);
@@ -10675,8 +10674,16 @@ namespace ts {
1067510674
// is a supertype of that primitive type. For example, type 'any' can be narrowed
1067610675
// to one of the primitive types.
1067710676
const targetType = typeofTypesByName.get(literal.text);
10678-
if (targetType && isTypeSubtypeOf(targetType, type)) {
10679-
return targetType;
10677+
if (targetType) {
10678+
if (isTypeSubtypeOf(targetType, type)) {
10679+
return targetType;
10680+
}
10681+
if (type.flags & TypeFlags.TypeVariable) {
10682+
const constraint = getBaseConstraintOfType(<TypeVariable>type) || anyType;
10683+
if (isTypeSubtypeOf(targetType, constraint)) {
10684+
return getIntersectionType([type, targetType]);
10685+
}
10686+
}
1068010687
}
1068110688
}
1068210689
const facts = assumeTrue ?
@@ -10774,10 +10781,9 @@ namespace ts {
1077410781
// Otherwise, if the candidate type is assignable to the target type, narrow to the candidate
1077510782
// type. Otherwise, the types are completely unrelated, so narrow to an intersection of the
1077610783
// two types.
10777-
const targetType = type.flags & TypeFlags.TypeParameter ? getApparentType(type) : type;
1077810784
return isTypeSubtypeOf(candidate, type) ? candidate :
1077910785
isTypeAssignableTo(type, candidate) ? type :
10780-
isTypeAssignableTo(candidate, targetType) ? candidate :
10786+
isTypeAssignableTo(candidate, type) ? candidate :
1078110787
getIntersectionType([type, candidate]);
1078210788
}
1078310789

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
//// [typeGuardsTypeParameters.ts]
2+
3+
// Type guards involving type parameters produce intersection types
4+
5+
class C {
6+
prop: string;
7+
}
8+
9+
function f1<T>(x: T) {
10+
if (x instanceof C) {
11+
let v1: T = x;
12+
let v2: C = x;
13+
x.prop;
14+
}
15+
}
16+
17+
function f2<T>(x: T) {
18+
if (typeof x === "string") {
19+
let v1: T = x;
20+
let v2: string = x;
21+
x.length;
22+
}
23+
}
24+
25+
// Repro from #13872
26+
27+
function fun<T>(item: { [P in keyof T]: T[P] }) {
28+
const strings: string[] = [];
29+
for (const key in item) {
30+
const value = item[key];
31+
if (typeof value === "string") {
32+
strings.push(value);
33+
}
34+
}
35+
}
36+
37+
38+
//// [typeGuardsTypeParameters.js]
39+
// Type guards involving type parameters produce intersection types
40+
var C = (function () {
41+
function C() {
42+
}
43+
return C;
44+
}());
45+
function f1(x) {
46+
if (x instanceof C) {
47+
var v1 = x;
48+
var v2 = x;
49+
x.prop;
50+
}
51+
}
52+
function f2(x) {
53+
if (typeof x === "string") {
54+
var v1 = x;
55+
var v2 = x;
56+
x.length;
57+
}
58+
}
59+
// Repro from #13872
60+
function fun(item) {
61+
var strings = [];
62+
for (var key in item) {
63+
var value = item[key];
64+
if (typeof value === "string") {
65+
strings.push(value);
66+
}
67+
}
68+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
=== tests/cases/conformance/controlFlow/typeGuardsTypeParameters.ts ===
2+
3+
// Type guards involving type parameters produce intersection types
4+
5+
class C {
6+
>C : Symbol(C, Decl(typeGuardsTypeParameters.ts, 0, 0))
7+
8+
prop: string;
9+
>prop : Symbol(C.prop, Decl(typeGuardsTypeParameters.ts, 3, 9))
10+
}
11+
12+
function f1<T>(x: T) {
13+
>f1 : Symbol(f1, Decl(typeGuardsTypeParameters.ts, 5, 1))
14+
>T : Symbol(T, Decl(typeGuardsTypeParameters.ts, 7, 12))
15+
>x : Symbol(x, Decl(typeGuardsTypeParameters.ts, 7, 15))
16+
>T : Symbol(T, Decl(typeGuardsTypeParameters.ts, 7, 12))
17+
18+
if (x instanceof C) {
19+
>x : Symbol(x, Decl(typeGuardsTypeParameters.ts, 7, 15))
20+
>C : Symbol(C, Decl(typeGuardsTypeParameters.ts, 0, 0))
21+
22+
let v1: T = x;
23+
>v1 : Symbol(v1, Decl(typeGuardsTypeParameters.ts, 9, 11))
24+
>T : Symbol(T, Decl(typeGuardsTypeParameters.ts, 7, 12))
25+
>x : Symbol(x, Decl(typeGuardsTypeParameters.ts, 7, 15))
26+
27+
let v2: C = x;
28+
>v2 : Symbol(v2, Decl(typeGuardsTypeParameters.ts, 10, 11))
29+
>C : Symbol(C, Decl(typeGuardsTypeParameters.ts, 0, 0))
30+
>x : Symbol(x, Decl(typeGuardsTypeParameters.ts, 7, 15))
31+
32+
x.prop;
33+
>x.prop : Symbol(C.prop, Decl(typeGuardsTypeParameters.ts, 3, 9))
34+
>x : Symbol(x, Decl(typeGuardsTypeParameters.ts, 7, 15))
35+
>prop : Symbol(C.prop, Decl(typeGuardsTypeParameters.ts, 3, 9))
36+
}
37+
}
38+
39+
function f2<T>(x: T) {
40+
>f2 : Symbol(f2, Decl(typeGuardsTypeParameters.ts, 13, 1))
41+
>T : Symbol(T, Decl(typeGuardsTypeParameters.ts, 15, 12))
42+
>x : Symbol(x, Decl(typeGuardsTypeParameters.ts, 15, 15))
43+
>T : Symbol(T, Decl(typeGuardsTypeParameters.ts, 15, 12))
44+
45+
if (typeof x === "string") {
46+
>x : Symbol(x, Decl(typeGuardsTypeParameters.ts, 15, 15))
47+
48+
let v1: T = x;
49+
>v1 : Symbol(v1, Decl(typeGuardsTypeParameters.ts, 17, 11))
50+
>T : Symbol(T, Decl(typeGuardsTypeParameters.ts, 15, 12))
51+
>x : Symbol(x, Decl(typeGuardsTypeParameters.ts, 15, 15))
52+
53+
let v2: string = x;
54+
>v2 : Symbol(v2, Decl(typeGuardsTypeParameters.ts, 18, 11))
55+
>x : Symbol(x, Decl(typeGuardsTypeParameters.ts, 15, 15))
56+
57+
x.length;
58+
>x.length : Symbol(String.length, Decl(lib.d.ts, --, --))
59+
>x : Symbol(x, Decl(typeGuardsTypeParameters.ts, 15, 15))
60+
>length : Symbol(String.length, Decl(lib.d.ts, --, --))
61+
}
62+
}
63+
64+
// Repro from #13872
65+
66+
function fun<T>(item: { [P in keyof T]: T[P] }) {
67+
>fun : Symbol(fun, Decl(typeGuardsTypeParameters.ts, 21, 1))
68+
>T : Symbol(T, Decl(typeGuardsTypeParameters.ts, 25, 13))
69+
>item : Symbol(item, Decl(typeGuardsTypeParameters.ts, 25, 16))
70+
>P : Symbol(P, Decl(typeGuardsTypeParameters.ts, 25, 25))
71+
>T : Symbol(T, Decl(typeGuardsTypeParameters.ts, 25, 13))
72+
>T : Symbol(T, Decl(typeGuardsTypeParameters.ts, 25, 13))
73+
>P : Symbol(P, Decl(typeGuardsTypeParameters.ts, 25, 25))
74+
75+
const strings: string[] = [];
76+
>strings : Symbol(strings, Decl(typeGuardsTypeParameters.ts, 26, 9))
77+
78+
for (const key in item) {
79+
>key : Symbol(key, Decl(typeGuardsTypeParameters.ts, 27, 14))
80+
>item : Symbol(item, Decl(typeGuardsTypeParameters.ts, 25, 16))
81+
82+
const value = item[key];
83+
>value : Symbol(value, Decl(typeGuardsTypeParameters.ts, 28, 13))
84+
>item : Symbol(item, Decl(typeGuardsTypeParameters.ts, 25, 16))
85+
>key : Symbol(key, Decl(typeGuardsTypeParameters.ts, 27, 14))
86+
87+
if (typeof value === "string") {
88+
>value : Symbol(value, Decl(typeGuardsTypeParameters.ts, 28, 13))
89+
90+
strings.push(value);
91+
>strings.push : Symbol(Array.push, Decl(lib.d.ts, --, --))
92+
>strings : Symbol(strings, Decl(typeGuardsTypeParameters.ts, 26, 9))
93+
>push : Symbol(Array.push, Decl(lib.d.ts, --, --))
94+
>value : Symbol(value, Decl(typeGuardsTypeParameters.ts, 28, 13))
95+
}
96+
}
97+
}
98+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
=== tests/cases/conformance/controlFlow/typeGuardsTypeParameters.ts ===
2+
3+
// Type guards involving type parameters produce intersection types
4+
5+
class C {
6+
>C : C
7+
8+
prop: string;
9+
>prop : string
10+
}
11+
12+
function f1<T>(x: T) {
13+
>f1 : <T>(x: T) => void
14+
>T : T
15+
>x : T
16+
>T : T
17+
18+
if (x instanceof C) {
19+
>x instanceof C : boolean
20+
>x : T
21+
>C : typeof C
22+
23+
let v1: T = x;
24+
>v1 : T
25+
>T : T
26+
>x : T & C
27+
28+
let v2: C = x;
29+
>v2 : C
30+
>C : C
31+
>x : T & C
32+
33+
x.prop;
34+
>x.prop : string
35+
>x : T & C
36+
>prop : string
37+
}
38+
}
39+
40+
function f2<T>(x: T) {
41+
>f2 : <T>(x: T) => void
42+
>T : T
43+
>x : T
44+
>T : T
45+
46+
if (typeof x === "string") {
47+
>typeof x === "string" : boolean
48+
>typeof x : "string" | "number" | "boolean" | "symbol" | "undefined" | "object" | "function"
49+
>x : T
50+
>"string" : "string"
51+
52+
let v1: T = x;
53+
>v1 : T
54+
>T : T
55+
>x : T & string
56+
57+
let v2: string = x;
58+
>v2 : string
59+
>x : T & string
60+
61+
x.length;
62+
>x.length : number
63+
>x : T & string
64+
>length : number
65+
}
66+
}
67+
68+
// Repro from #13872
69+
70+
function fun<T>(item: { [P in keyof T]: T[P] }) {
71+
>fun : <T>(item: { [P in keyof T]: T[P]; }) => void
72+
>T : T
73+
>item : { [P in keyof T]: T[P]; }
74+
>P : P
75+
>T : T
76+
>T : T
77+
>P : P
78+
79+
const strings: string[] = [];
80+
>strings : string[]
81+
>[] : never[]
82+
83+
for (const key in item) {
84+
>key : keyof T
85+
>item : { [P in keyof T]: T[P]; }
86+
87+
const value = item[key];
88+
>value : T[keyof T]
89+
>item[key] : T[keyof T]
90+
>item : { [P in keyof T]: T[P]; }
91+
>key : keyof T
92+
93+
if (typeof value === "string") {
94+
>typeof value === "string" : boolean
95+
>typeof value : "string" | "number" | "boolean" | "symbol" | "undefined" | "object" | "function"
96+
>value : T[keyof T]
97+
>"string" : "string"
98+
99+
strings.push(value);
100+
>strings.push(value) : number
101+
>strings.push : (...items: string[]) => number
102+
>strings : string[]
103+
>push : (...items: string[]) => number
104+
>value : T[keyof T] & string
105+
}
106+
}
107+
}
108+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// @strictNullChecks: true
2+
3+
// Type guards involving type parameters produce intersection types
4+
5+
class C {
6+
prop: string;
7+
}
8+
9+
function f1<T>(x: T) {
10+
if (x instanceof C) {
11+
let v1: T = x;
12+
let v2: C = x;
13+
x.prop;
14+
}
15+
}
16+
17+
function f2<T>(x: T) {
18+
if (typeof x === "string") {
19+
let v1: T = x;
20+
let v2: string = x;
21+
x.length;
22+
}
23+
}
24+
25+
// Repro from #13872
26+
27+
function fun<T>(item: { [P in keyof T]: T[P] }) {
28+
const strings: string[] = [];
29+
for (const key in item) {
30+
const value = item[key];
31+
if (typeof value === "string") {
32+
strings.push(value);
33+
}
34+
}
35+
}

0 commit comments

Comments
 (0)