Skip to content

Commit 367b820

Browse files
authored
Hoist and distribute type parameter constraints over type parameters … (#33453)
* Hoist and distribute type parameter constraints over type parameters when comparing against union targets when fetching union constraints * Fix PR nits
1 parent 26caa37 commit 367b820

7 files changed

+93
-46
lines changed

src/compiler/checker.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7965,10 +7965,10 @@ namespace ts {
79657965
return hasNonCircularBaseConstraint(type) ? getConstraintFromConditionalType(type) : undefined;
79667966
}
79677967

7968-
function getUnionConstraintOfIntersection(type: IntersectionType, targetIsUnion: boolean) {
7968+
function getEffectiveConstraintOfIntersection(types: readonly Type[], targetIsUnion: boolean) {
79697969
let constraints: Type[] | undefined;
79707970
let hasDisjointDomainType = false;
7971-
for (const t of type.types) {
7971+
for (const t of types) {
79727972
if (t.flags & TypeFlags.Instantiable) {
79737973
// We keep following constraints as long as we have an instantiable type that is known
79747974
// not to be circular or infinite (hence we stop on index access types).
@@ -7978,6 +7978,9 @@ namespace ts {
79787978
}
79797979
if (constraint) {
79807980
constraints = append(constraints, constraint);
7981+
if (targetIsUnion) {
7982+
constraints = append(constraints, t);
7983+
}
79817984
}
79827985
}
79837986
else if (t.flags & TypeFlags.DisjointDomains) {
@@ -7990,7 +7993,7 @@ namespace ts {
79907993
if (hasDisjointDomainType) {
79917994
// We add any types belong to one of the disjoint domains because they might cause the final
79927995
// intersection operation to reduce the union constraints.
7993-
for (const t of type.types) {
7996+
for (const t of types) {
79947997
if (t.flags & TypeFlags.DisjointDomains) {
79957998
constraints = append(constraints, t);
79967999
}
@@ -13089,7 +13092,7 @@ namespace ts {
1308913092
}
1309013093
}
1309113094
}
13092-
if (!result && source.flags & TypeFlags.Intersection) {
13095+
if (!result && source.flags & (TypeFlags.Intersection | TypeFlags.TypeParameter)) {
1309313096
// The combined constraint of an intersection type is the intersection of the constraints of
1309413097
// the constituents. When an intersection type contains instantiable types with union type
1309513098
// constraints, there are situations where we need to examine the combined constraint. One is
@@ -13099,10 +13102,17 @@ namespace ts {
1309913102
// we need to check this constraint against a union on the target side. Also, given a type
1310013103
// variable V constrained to 'string | number', 'V & number' has a combined constraint of
1310113104
// 'string & number | number & number' which reduces to just 'number'.
13102-
const constraint = getUnionConstraintOfIntersection(<IntersectionType>source, !!(target.flags & TypeFlags.Union));
13103-
if (constraint) {
13104-
if (result = isRelatedTo(constraint, target, reportErrors, /*headMessage*/ undefined, isIntersectionConstituent)) {
13105-
resetErrorInfo(saveErrorInfo);
13105+
// This also handles type parameters, as a type parameter with a union constraint compared against a union
13106+
// needs to have its constraint hoisted into an intersection with said type parameter, this way
13107+
// the type param can be compared with itself in the target (with the influence of its constraint to match other parts)
13108+
// For example, if `T extends 1 | 2` and `U extends 2 | 3` and we compare `T & U` to `T & U & (1 | 2 | 3)`
13109+
const constraint = getEffectiveConstraintOfIntersection(source.flags & TypeFlags.Intersection ? (<IntersectionType>source).types: [source], !!(target.flags & TypeFlags.Union));
13110+
if (constraint && (source.flags & TypeFlags.Intersection || target.flags & TypeFlags.Union)) {
13111+
if (everyType(constraint, c => c !== source)) { // Skip comparison if expansion contains the source itself
13112+
// TODO: Stack errors so we get a pyramid for the "normal" comparison above, _and_ a second for this
13113+
if (result = isRelatedTo(constraint, target, /*reportErrors*/ false, /*headMessage*/ undefined, isIntersectionConstituent)) {
13114+
resetErrorInfo(saveErrorInfo);
13115+
}
1310613116
}
1310713117
}
1310813118
}

tests/baselines/reference/intersectionWithUnionConstraint.errors.txt

Lines changed: 10 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,13 @@
11
tests/cases/conformance/types/intersection/intersectionWithUnionConstraint.ts(7,9): error TS2322: Type 'T & U' is not assignable to type 'string | number'.
2-
Type 'string | undefined' is not assignable to type 'string | number'.
3-
Type 'undefined' is not assignable to type 'string | number'.
4-
Type 'T & U' is not assignable to type 'number'.
2+
Type 'T & U' is not assignable to type 'number'.
53
tests/cases/conformance/types/intersection/intersectionWithUnionConstraint.ts(8,9): error TS2322: Type 'T & U' is not assignable to type 'string | null'.
6-
Type 'string | undefined' is not assignable to type 'string | null'.
7-
Type 'undefined' is not assignable to type 'string | null'.
8-
Type 'T & U' is not assignable to type 'string'.
4+
Type 'T & U' is not assignable to type 'string'.
95
tests/cases/conformance/types/intersection/intersectionWithUnionConstraint.ts(10,9): error TS2322: Type 'T & U' is not assignable to type 'number | null'.
10-
Type 'string | undefined' is not assignable to type 'number | null'.
11-
Type 'undefined' is not assignable to type 'number | null'.
12-
Type 'T & U' is not assignable to type 'number'.
6+
Type 'T & U' is not assignable to type 'number'.
137
tests/cases/conformance/types/intersection/intersectionWithUnionConstraint.ts(11,9): error TS2322: Type 'T & U' is not assignable to type 'number | undefined'.
14-
Type 'string | undefined' is not assignable to type 'number | undefined'.
15-
Type 'string' is not assignable to type 'number | undefined'.
16-
Type 'T & U' is not assignable to type 'number'.
8+
Type 'T & U' is not assignable to type 'number'.
179
tests/cases/conformance/types/intersection/intersectionWithUnionConstraint.ts(12,9): error TS2322: Type 'T & U' is not assignable to type 'null | undefined'.
18-
Type 'string | undefined' is not assignable to type 'null | undefined'.
19-
Type 'string' is not assignable to type 'null | undefined'.
20-
Type 'T & U' is not assignable to type 'null'.
10+
Type 'T & U' is not assignable to type 'null'.
2111

2212

2313
==== tests/cases/conformance/types/intersection/intersectionWithUnionConstraint.ts (5 errors) ====
@@ -30,34 +20,24 @@ tests/cases/conformance/types/intersection/intersectionWithUnionConstraint.ts(12
3020
let y1: string | number = x; // Error
3121
~~
3222
!!! error TS2322: Type 'T & U' is not assignable to type 'string | number'.
33-
!!! error TS2322: Type 'string | undefined' is not assignable to type 'string | number'.
34-
!!! error TS2322: Type 'undefined' is not assignable to type 'string | number'.
35-
!!! error TS2322: Type 'T & U' is not assignable to type 'number'.
23+
!!! error TS2322: Type 'T & U' is not assignable to type 'number'.
3624
let y2: string | null = x; // Error
3725
~~
3826
!!! error TS2322: Type 'T & U' is not assignable to type 'string | null'.
39-
!!! error TS2322: Type 'string | undefined' is not assignable to type 'string | null'.
40-
!!! error TS2322: Type 'undefined' is not assignable to type 'string | null'.
41-
!!! error TS2322: Type 'T & U' is not assignable to type 'string'.
27+
!!! error TS2322: Type 'T & U' is not assignable to type 'string'.
4228
let y3: string | undefined = x;
4329
let y4: number | null = x; // Error
4430
~~
4531
!!! error TS2322: Type 'T & U' is not assignable to type 'number | null'.
46-
!!! error TS2322: Type 'string | undefined' is not assignable to type 'number | null'.
47-
!!! error TS2322: Type 'undefined' is not assignable to type 'number | null'.
48-
!!! error TS2322: Type 'T & U' is not assignable to type 'number'.
32+
!!! error TS2322: Type 'T & U' is not assignable to type 'number'.
4933
let y5: number | undefined = x; // Error
5034
~~
5135
!!! error TS2322: Type 'T & U' is not assignable to type 'number | undefined'.
52-
!!! error TS2322: Type 'string | undefined' is not assignable to type 'number | undefined'.
53-
!!! error TS2322: Type 'string' is not assignable to type 'number | undefined'.
54-
!!! error TS2322: Type 'T & U' is not assignable to type 'number'.
36+
!!! error TS2322: Type 'T & U' is not assignable to type 'number'.
5537
let y6: null | undefined = x; // Error
5638
~~
5739
!!! error TS2322: Type 'T & U' is not assignable to type 'null | undefined'.
58-
!!! error TS2322: Type 'string | undefined' is not assignable to type 'null | undefined'.
59-
!!! error TS2322: Type 'string' is not assignable to type 'null | undefined'.
60-
!!! error TS2322: Type 'T & U' is not assignable to type 'null'.
40+
!!! error TS2322: Type 'T & U' is not assignable to type 'null'.
6141
}
6242

6343
type T1 = (string | number | undefined) & (string | null | undefined); // string | undefined

tests/baselines/reference/keyofAndIndexedAccessErrors.errors.txt

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,6 @@ tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(103,9): error
4848
'string & keyof T' is assignable to the constraint of type 'K', but 'K' could be instantiated with a different subtype of constraint 'string'.
4949
Type 'string' is not assignable to type 'K'.
5050
'string' is assignable to the constraint of type 'K', but 'K' could be instantiated with a different subtype of constraint 'string'.
51-
Type 'string' is not assignable to type 'K'.
52-
'string' is assignable to the constraint of type 'K', but 'K' could be instantiated with a different subtype of constraint 'string'.
5351
tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(105,9): error TS2322: Type 'T[Extract<keyof T, string>]' is not assignable to type 'T[K]'.
5452
Type 'Extract<keyof T, string>' is not assignable to type 'K'.
5553
'Extract<keyof T, string>' is assignable to the constraint of type 'K', but 'K' could be instantiated with a different subtype of constraint 'string'.
@@ -68,8 +66,6 @@ tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(114,5): error
6866
'string & keyof T' is assignable to the constraint of type 'J', but 'J' could be instantiated with a different subtype of constraint 'string'.
6967
Type 'string' is not assignable to type 'J'.
7068
'string' is assignable to the constraint of type 'J', but 'J' could be instantiated with a different subtype of constraint 'string'.
71-
Type 'string' is not assignable to type 'J'.
72-
'string' is assignable to the constraint of type 'J', but 'J' could be instantiated with a different subtype of constraint 'string'.
7369
tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(117,5): error TS2322: Type 'T[K]' is not assignable to type 'U[J]'.
7470
Type 'T' is not assignable to type 'U'.
7571
'T' is assignable to the constraint of type 'U', but 'U' could be instantiated with a different subtype of constraint '{}'.
@@ -264,8 +260,6 @@ tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(142,5): error
264260
!!! error TS2322: 'string & keyof T' is assignable to the constraint of type 'K', but 'K' could be instantiated with a different subtype of constraint 'string'.
265261
!!! error TS2322: Type 'string' is not assignable to type 'K'.
266262
!!! error TS2322: 'string' is assignable to the constraint of type 'K', but 'K' could be instantiated with a different subtype of constraint 'string'.
267-
!!! error TS2322: Type 'string' is not assignable to type 'K'.
268-
!!! error TS2322: 'string' is assignable to the constraint of type 'K', but 'K' could be instantiated with a different subtype of constraint 'string'.
269263
t[key] = tk; // ok, T[K] ==> T[keyof T]
270264
tk = t[key]; // error, T[keyof T] =/=> T[K]
271265
~~
@@ -299,8 +293,6 @@ tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(142,5): error
299293
!!! error TS2322: 'string & keyof T' is assignable to the constraint of type 'J', but 'J' could be instantiated with a different subtype of constraint 'string'.
300294
!!! error TS2322: Type 'string' is not assignable to type 'J'.
301295
!!! error TS2322: 'string' is assignable to the constraint of type 'J', but 'J' could be instantiated with a different subtype of constraint 'string'.
302-
!!! error TS2322: Type 'string' is not assignable to type 'J'.
303-
!!! error TS2322: 'string' is assignable to the constraint of type 'J', but 'J' could be instantiated with a different subtype of constraint 'string'.
304296

305297
tk = uj;
306298
uj = tk; // error
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
//// [typeParameterExtendsUnionConstraintDistributed.ts]
2+
type A = 1 | 2;
3+
function f<T extends A>(a: T): A & T { return a; } // Shouldn't error
4+
5+
type B = 2 | 3;
6+
function f2<T extends A, U extends B>(ab: T & U): (A | B) & T & U { return ab; } // Also shouldn't error
7+
8+
9+
//// [typeParameterExtendsUnionConstraintDistributed.js]
10+
function f(a) { return a; } // Shouldn't error
11+
function f2(ab) { return ab; } // Also shouldn't error
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
=== tests/cases/conformance/jsdoc/typeParameterExtendsUnionConstraintDistributed.ts ===
2+
type A = 1 | 2;
3+
>A : Symbol(A, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 0, 0))
4+
5+
function f<T extends A>(a: T): A & T { return a; } // Shouldn't error
6+
>f : Symbol(f, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 0, 15))
7+
>T : Symbol(T, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 1, 11))
8+
>A : Symbol(A, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 0, 0))
9+
>a : Symbol(a, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 1, 24))
10+
>T : Symbol(T, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 1, 11))
11+
>A : Symbol(A, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 0, 0))
12+
>T : Symbol(T, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 1, 11))
13+
>a : Symbol(a, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 1, 24))
14+
15+
type B = 2 | 3;
16+
>B : Symbol(B, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 1, 50))
17+
18+
function f2<T extends A, U extends B>(ab: T & U): (A | B) & T & U { return ab; } // Also shouldn't error
19+
>f2 : Symbol(f2, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 3, 15))
20+
>T : Symbol(T, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 4, 12))
21+
>A : Symbol(A, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 0, 0))
22+
>U : Symbol(U, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 4, 24))
23+
>B : Symbol(B, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 1, 50))
24+
>ab : Symbol(ab, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 4, 38))
25+
>T : Symbol(T, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 4, 12))
26+
>U : Symbol(U, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 4, 24))
27+
>A : Symbol(A, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 0, 0))
28+
>B : Symbol(B, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 1, 50))
29+
>T : Symbol(T, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 4, 12))
30+
>U : Symbol(U, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 4, 24))
31+
>ab : Symbol(ab, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 4, 38))
32+
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
=== tests/cases/conformance/jsdoc/typeParameterExtendsUnionConstraintDistributed.ts ===
2+
type A = 1 | 2;
3+
>A : A
4+
5+
function f<T extends A>(a: T): A & T { return a; } // Shouldn't error
6+
>f : <T extends A>(a: T) => (1 & T) | (2 & T)
7+
>a : T
8+
>a : T
9+
10+
type B = 2 | 3;
11+
>B : B
12+
13+
function f2<T extends A, U extends B>(ab: T & U): (A | B) & T & U { return ab; } // Also shouldn't error
14+
>f2 : <T extends A, U extends B>(ab: T & U) => (1 & T & U) | (2 & T & U) | (3 & T & U)
15+
>ab : T & U
16+
>ab : T & U
17+
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
type A = 1 | 2;
2+
function f<T extends A>(a: T): A & T { return a; } // Shouldn't error
3+
4+
type B = 2 | 3;
5+
function f2<T extends A, U extends B>(ab: T & U): (A | B) & T & U { return ab; } // Also shouldn't error

0 commit comments

Comments
 (0)