Skip to content

Commit 3bb5dc6

Browse files
committed
Merge pull request #5895 from Microsoft/unionTypeParameterInference
Fix union type parameter inference
2 parents 4c9575a + 86d4a4c commit 3bb5dc6

9 files changed

+254
-28
lines changed

src/compiler/checker.ts

+7-28
Original file line numberDiff line numberDiff line change
@@ -5083,9 +5083,6 @@ namespace ts {
50835083
}
50845084
return objectTypeRelatedTo(source, source, target, /*reportErrors*/ false);
50855085
}
5086-
if (source.flags & TypeFlags.TypeParameter && target.flags & TypeFlags.TypeParameter) {
5087-
return typeParameterIdenticalTo(<TypeParameter>source, <TypeParameter>target);
5088-
}
50895086
if (source.flags & TypeFlags.Union && target.flags & TypeFlags.Union ||
50905087
source.flags & TypeFlags.Intersection && target.flags & TypeFlags.Intersection) {
50915088
if (result = eachTypeRelatedToSomeType(<UnionOrIntersectionType>source, <UnionOrIntersectionType>target)) {
@@ -5216,17 +5213,6 @@ namespace ts {
52165213
return result;
52175214
}
52185215

5219-
function typeParameterIdenticalTo(source: TypeParameter, target: TypeParameter): Ternary {
5220-
// covers case when both type parameters does not have constraint (both equal to noConstraintType)
5221-
if (source.constraint === target.constraint) {
5222-
return Ternary.True;
5223-
}
5224-
if (source.constraint === noConstraintType || target.constraint === noConstraintType) {
5225-
return Ternary.False;
5226-
}
5227-
return isIdenticalTo(source.constraint, target.constraint);
5228-
}
5229-
52305216
// Determine if two object types are related by structure. First, check if the result is already available in the global cache.
52315217
// Second, check if we have already started a comparison of the given two types in which case we assume the result to be true.
52325218
// Third, check if both types are part of deeply nested chains of generic type instantiations and if so assume the types are
@@ -5781,26 +5767,19 @@ namespace ts {
57815767
if (!(isMatchingSignature(source, target, partialMatch))) {
57825768
return Ternary.False;
57835769
}
5784-
let result = Ternary.True;
5785-
if (source.typeParameters && target.typeParameters) {
5786-
if (source.typeParameters.length !== target.typeParameters.length) {
5787-
return Ternary.False;
5788-
}
5789-
for (let i = 0, len = source.typeParameters.length; i < len; ++i) {
5790-
const related = compareTypes(source.typeParameters[i], target.typeParameters[i]);
5791-
if (!related) {
5792-
return Ternary.False;
5793-
}
5794-
result &= related;
5795-
}
5796-
}
5797-
else if (source.typeParameters || target.typeParameters) {
5770+
// Check that the two signatures have the same number of type parameters. We might consider
5771+
// also checking that any type parameter constraints match, but that would require instantiating
5772+
// the constraints with a common set of type arguments to get relatable entities in places where
5773+
// type parameters occur in the constraints. The complexity of doing that doesn't seem worthwhile,
5774+
// particularly as we're comparing erased versions of the signatures below.
5775+
if ((source.typeParameters ? source.typeParameters.length : 0) !== (target.typeParameters ? target.typeParameters.length : 0)) {
57985776
return Ternary.False;
57995777
}
58005778
// Spec 1.0 Section 3.8.3 & 3.8.4:
58015779
// M and N (the signatures) are instantiated using type Any as the type argument for all type parameters declared by M and N
58025780
source = getErasedSignature(source);
58035781
target = getErasedSignature(target);
5782+
let result = Ternary.True;
58045783
const targetLen = target.parameters.length;
58055784
for (let i = 0; i < targetLen; i++) {
58065785
const s = isRestParameterIndex(source, i) ? getRestTypeOfSignature(source) : getTypeOfSymbol(source.parameters[i]);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//// [genericSignatureIdentity.ts]
2+
// This test is here to remind us of our current limits of type identity checking.
3+
// Ideally all of the below declarations would be considered different (and thus errors)
4+
// but they aren't because we erase type parameters to type any and don't check that
5+
// constraints are identical.
6+
7+
var x: {
8+
<T extends Date>(x: T): T;
9+
};
10+
11+
var x: {
12+
<T extends number>(x: T): T;
13+
};
14+
15+
var x: {
16+
<T>(x: T): T;
17+
};
18+
19+
var x: {
20+
<T>(x: any): any;
21+
};
22+
23+
24+
//// [genericSignatureIdentity.js]
25+
// This test is here to remind us of our current limits of type identity checking.
26+
// Ideally all of the below declarations would be considered different (and thus errors)
27+
// but they aren't because we erase type parameters to type any and don't check that
28+
// constraints are identical.
29+
var x;
30+
var x;
31+
var x;
32+
var x;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
=== tests/cases/compiler/genericSignatureIdentity.ts ===
2+
// This test is here to remind us of our current limits of type identity checking.
3+
// Ideally all of the below declarations would be considered different (and thus errors)
4+
// but they aren't because we erase type parameters to type any and don't check that
5+
// constraints are identical.
6+
7+
var x: {
8+
>x : Symbol(x, Decl(genericSignatureIdentity.ts, 5, 3), Decl(genericSignatureIdentity.ts, 9, 3), Decl(genericSignatureIdentity.ts, 13, 3), Decl(genericSignatureIdentity.ts, 17, 3))
9+
10+
<T extends Date>(x: T): T;
11+
>T : Symbol(T, Decl(genericSignatureIdentity.ts, 6, 5))
12+
>Date : Symbol(Date, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
13+
>x : Symbol(x, Decl(genericSignatureIdentity.ts, 6, 21))
14+
>T : Symbol(T, Decl(genericSignatureIdentity.ts, 6, 5))
15+
>T : Symbol(T, Decl(genericSignatureIdentity.ts, 6, 5))
16+
17+
};
18+
19+
var x: {
20+
>x : Symbol(x, Decl(genericSignatureIdentity.ts, 5, 3), Decl(genericSignatureIdentity.ts, 9, 3), Decl(genericSignatureIdentity.ts, 13, 3), Decl(genericSignatureIdentity.ts, 17, 3))
21+
22+
<T extends number>(x: T): T;
23+
>T : Symbol(T, Decl(genericSignatureIdentity.ts, 10, 5))
24+
>x : Symbol(x, Decl(genericSignatureIdentity.ts, 10, 23))
25+
>T : Symbol(T, Decl(genericSignatureIdentity.ts, 10, 5))
26+
>T : Symbol(T, Decl(genericSignatureIdentity.ts, 10, 5))
27+
28+
};
29+
30+
var x: {
31+
>x : Symbol(x, Decl(genericSignatureIdentity.ts, 5, 3), Decl(genericSignatureIdentity.ts, 9, 3), Decl(genericSignatureIdentity.ts, 13, 3), Decl(genericSignatureIdentity.ts, 17, 3))
32+
33+
<T>(x: T): T;
34+
>T : Symbol(T, Decl(genericSignatureIdentity.ts, 14, 5))
35+
>x : Symbol(x, Decl(genericSignatureIdentity.ts, 14, 8))
36+
>T : Symbol(T, Decl(genericSignatureIdentity.ts, 14, 5))
37+
>T : Symbol(T, Decl(genericSignatureIdentity.ts, 14, 5))
38+
39+
};
40+
41+
var x: {
42+
>x : Symbol(x, Decl(genericSignatureIdentity.ts, 5, 3), Decl(genericSignatureIdentity.ts, 9, 3), Decl(genericSignatureIdentity.ts, 13, 3), Decl(genericSignatureIdentity.ts, 17, 3))
43+
44+
<T>(x: any): any;
45+
>T : Symbol(T, Decl(genericSignatureIdentity.ts, 18, 5))
46+
>x : Symbol(x, Decl(genericSignatureIdentity.ts, 18, 8))
47+
48+
};
49+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
=== tests/cases/compiler/genericSignatureIdentity.ts ===
2+
// This test is here to remind us of our current limits of type identity checking.
3+
// Ideally all of the below declarations would be considered different (and thus errors)
4+
// but they aren't because we erase type parameters to type any and don't check that
5+
// constraints are identical.
6+
7+
var x: {
8+
>x : <T extends Date>(x: T) => T
9+
10+
<T extends Date>(x: T): T;
11+
>T : T
12+
>Date : Date
13+
>x : T
14+
>T : T
15+
>T : T
16+
17+
};
18+
19+
var x: {
20+
>x : <T extends Date>(x: T) => T
21+
22+
<T extends number>(x: T): T;
23+
>T : T
24+
>x : T
25+
>T : T
26+
>T : T
27+
28+
};
29+
30+
var x: {
31+
>x : <T extends Date>(x: T) => T
32+
33+
<T>(x: T): T;
34+
>T : T
35+
>x : T
36+
>T : T
37+
>T : T
38+
39+
};
40+
41+
var x: {
42+
>x : <T extends Date>(x: T) => T
43+
44+
<T>(x: any): any;
45+
>T : T
46+
>x : any
47+
48+
};
49+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//// [unionTypeParameterInference.ts]
2+
// Regression test for #5861
3+
4+
interface Foo<T> { prop: T; }
5+
6+
declare function lift<U>(value: U | Foo<U>): Foo<U>;
7+
8+
function unlift<U>(value: U | Foo<U>): U {
9+
return lift(value).prop;
10+
}
11+
12+
13+
//// [unionTypeParameterInference.js]
14+
// Regression test for #5861
15+
function unlift(value) {
16+
return lift(value).prop;
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
=== tests/cases/compiler/unionTypeParameterInference.ts ===
2+
// Regression test for #5861
3+
4+
interface Foo<T> { prop: T; }
5+
>Foo : Symbol(Foo, Decl(unionTypeParameterInference.ts, 0, 0))
6+
>T : Symbol(T, Decl(unionTypeParameterInference.ts, 2, 14))
7+
>prop : Symbol(prop, Decl(unionTypeParameterInference.ts, 2, 18))
8+
>T : Symbol(T, Decl(unionTypeParameterInference.ts, 2, 14))
9+
10+
declare function lift<U>(value: U | Foo<U>): Foo<U>;
11+
>lift : Symbol(lift, Decl(unionTypeParameterInference.ts, 2, 29))
12+
>U : Symbol(U, Decl(unionTypeParameterInference.ts, 4, 22))
13+
>value : Symbol(value, Decl(unionTypeParameterInference.ts, 4, 25))
14+
>U : Symbol(U, Decl(unionTypeParameterInference.ts, 4, 22))
15+
>Foo : Symbol(Foo, Decl(unionTypeParameterInference.ts, 0, 0))
16+
>U : Symbol(U, Decl(unionTypeParameterInference.ts, 4, 22))
17+
>Foo : Symbol(Foo, Decl(unionTypeParameterInference.ts, 0, 0))
18+
>U : Symbol(U, Decl(unionTypeParameterInference.ts, 4, 22))
19+
20+
function unlift<U>(value: U | Foo<U>): U {
21+
>unlift : Symbol(unlift, Decl(unionTypeParameterInference.ts, 4, 52))
22+
>U : Symbol(U, Decl(unionTypeParameterInference.ts, 6, 16))
23+
>value : Symbol(value, Decl(unionTypeParameterInference.ts, 6, 19))
24+
>U : Symbol(U, Decl(unionTypeParameterInference.ts, 6, 16))
25+
>Foo : Symbol(Foo, Decl(unionTypeParameterInference.ts, 0, 0))
26+
>U : Symbol(U, Decl(unionTypeParameterInference.ts, 6, 16))
27+
>U : Symbol(U, Decl(unionTypeParameterInference.ts, 6, 16))
28+
29+
return lift(value).prop;
30+
>lift(value).prop : Symbol(Foo.prop, Decl(unionTypeParameterInference.ts, 2, 18))
31+
>lift : Symbol(lift, Decl(unionTypeParameterInference.ts, 2, 29))
32+
>value : Symbol(value, Decl(unionTypeParameterInference.ts, 6, 19))
33+
>prop : Symbol(Foo.prop, Decl(unionTypeParameterInference.ts, 2, 18))
34+
}
35+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
=== tests/cases/compiler/unionTypeParameterInference.ts ===
2+
// Regression test for #5861
3+
4+
interface Foo<T> { prop: T; }
5+
>Foo : Foo<T>
6+
>T : T
7+
>prop : T
8+
>T : T
9+
10+
declare function lift<U>(value: U | Foo<U>): Foo<U>;
11+
>lift : <U>(value: U | Foo<U>) => Foo<U>
12+
>U : U
13+
>value : U | Foo<U>
14+
>U : U
15+
>Foo : Foo<T>
16+
>U : U
17+
>Foo : Foo<T>
18+
>U : U
19+
20+
function unlift<U>(value: U | Foo<U>): U {
21+
>unlift : <U>(value: U | Foo<U>) => U
22+
>U : U
23+
>value : U | Foo<U>
24+
>U : U
25+
>Foo : Foo<T>
26+
>U : U
27+
>U : U
28+
29+
return lift(value).prop;
30+
>lift(value).prop : U
31+
>lift(value) : Foo<U>
32+
>lift : <U>(value: U | Foo<U>) => Foo<U>
33+
>value : U | Foo<U>
34+
>prop : U
35+
}
36+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// This test is here to remind us of our current limits of type identity checking.
2+
// Ideally all of the below declarations would be considered different (and thus errors)
3+
// but they aren't because we erase type parameters to type any and don't check that
4+
// constraints are identical.
5+
6+
var x: {
7+
<T extends Date>(x: T): T;
8+
};
9+
10+
var x: {
11+
<T extends number>(x: T): T;
12+
};
13+
14+
var x: {
15+
<T>(x: T): T;
16+
};
17+
18+
var x: {
19+
<T>(x: any): any;
20+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Regression test for #5861
2+
3+
interface Foo<T> { prop: T; }
4+
5+
declare function lift<U>(value: U | Foo<U>): Foo<U>;
6+
7+
function unlift<U>(value: U | Foo<U>): U {
8+
return lift(value).prop;
9+
}

0 commit comments

Comments
 (0)