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
This commit is contained in:
Wesley Wigham 2019-09-23 16:52:03 -07:00 committed by GitHub
parent 26caa3793e
commit 367b82055c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 93 additions and 46 deletions

View File

@ -7965,10 +7965,10 @@ namespace ts {
return hasNonCircularBaseConstraint(type) ? getConstraintFromConditionalType(type) : undefined;
}
function getUnionConstraintOfIntersection(type: IntersectionType, targetIsUnion: boolean) {
function getEffectiveConstraintOfIntersection(types: readonly Type[], targetIsUnion: boolean) {
let constraints: Type[] | undefined;
let hasDisjointDomainType = false;
for (const t of type.types) {
for (const t of types) {
if (t.flags & TypeFlags.Instantiable) {
// We keep following constraints as long as we have an instantiable type that is known
// not to be circular or infinite (hence we stop on index access types).
@ -7978,6 +7978,9 @@ namespace ts {
}
if (constraint) {
constraints = append(constraints, constraint);
if (targetIsUnion) {
constraints = append(constraints, t);
}
}
}
else if (t.flags & TypeFlags.DisjointDomains) {
@ -7990,7 +7993,7 @@ namespace ts {
if (hasDisjointDomainType) {
// We add any types belong to one of the disjoint domains because they might cause the final
// intersection operation to reduce the union constraints.
for (const t of type.types) {
for (const t of types) {
if (t.flags & TypeFlags.DisjointDomains) {
constraints = append(constraints, t);
}
@ -13089,7 +13092,7 @@ namespace ts {
}
}
}
if (!result && source.flags & TypeFlags.Intersection) {
if (!result && source.flags & (TypeFlags.Intersection | TypeFlags.TypeParameter)) {
// The combined constraint of an intersection type is the intersection of the constraints of
// the constituents. When an intersection type contains instantiable types with union type
// constraints, there are situations where we need to examine the combined constraint. One is
@ -13099,10 +13102,17 @@ namespace ts {
// we need to check this constraint against a union on the target side. Also, given a type
// variable V constrained to 'string | number', 'V & number' has a combined constraint of
// 'string & number | number & number' which reduces to just 'number'.
const constraint = getUnionConstraintOfIntersection(<IntersectionType>source, !!(target.flags & TypeFlags.Union));
if (constraint) {
if (result = isRelatedTo(constraint, target, reportErrors, /*headMessage*/ undefined, isIntersectionConstituent)) {
resetErrorInfo(saveErrorInfo);
// This also handles type parameters, as a type parameter with a union constraint compared against a union
// needs to have its constraint hoisted into an intersection with said type parameter, this way
// the type param can be compared with itself in the target (with the influence of its constraint to match other parts)
// For example, if `T extends 1 | 2` and `U extends 2 | 3` and we compare `T & U` to `T & U & (1 | 2 | 3)`
const constraint = getEffectiveConstraintOfIntersection(source.flags & TypeFlags.Intersection ? (<IntersectionType>source).types: [source], !!(target.flags & TypeFlags.Union));
if (constraint && (source.flags & TypeFlags.Intersection || target.flags & TypeFlags.Union)) {
if (everyType(constraint, c => c !== source)) { // Skip comparison if expansion contains the source itself
// TODO: Stack errors so we get a pyramid for the "normal" comparison above, _and_ a second for this
if (result = isRelatedTo(constraint, target, /*reportErrors*/ false, /*headMessage*/ undefined, isIntersectionConstituent)) {
resetErrorInfo(saveErrorInfo);
}
}
}
}

View File

@ -1,23 +1,13 @@
tests/cases/conformance/types/intersection/intersectionWithUnionConstraint.ts(7,9): error TS2322: Type 'T & U' is not assignable to type 'string | number'.
Type 'string | undefined' is not assignable to type 'string | number'.
Type 'undefined' is not assignable to type 'string | number'.
Type 'T & U' is not assignable to type 'number'.
Type 'T & U' is not assignable to type 'number'.
tests/cases/conformance/types/intersection/intersectionWithUnionConstraint.ts(8,9): error TS2322: Type 'T & U' is not assignable to type 'string | null'.
Type 'string | undefined' is not assignable to type 'string | null'.
Type 'undefined' is not assignable to type 'string | null'.
Type 'T & U' is not assignable to type 'string'.
Type 'T & U' is not assignable to type 'string'.
tests/cases/conformance/types/intersection/intersectionWithUnionConstraint.ts(10,9): error TS2322: Type 'T & U' is not assignable to type 'number | null'.
Type 'string | undefined' is not assignable to type 'number | null'.
Type 'undefined' is not assignable to type 'number | null'.
Type 'T & U' is not assignable to type 'number'.
Type 'T & U' is not assignable to type 'number'.
tests/cases/conformance/types/intersection/intersectionWithUnionConstraint.ts(11,9): error TS2322: Type 'T & U' is not assignable to type 'number | undefined'.
Type 'string | undefined' is not assignable to type 'number | undefined'.
Type 'string' is not assignable to type 'number | undefined'.
Type 'T & U' is not assignable to type 'number'.
Type 'T & U' is not assignable to type 'number'.
tests/cases/conformance/types/intersection/intersectionWithUnionConstraint.ts(12,9): error TS2322: Type 'T & U' is not assignable to type 'null | undefined'.
Type 'string | undefined' is not assignable to type 'null | undefined'.
Type 'string' is not assignable to type 'null | undefined'.
Type 'T & U' is not assignable to type 'null'.
Type 'T & U' is not assignable to type 'null'.
==== tests/cases/conformance/types/intersection/intersectionWithUnionConstraint.ts (5 errors) ====
@ -30,34 +20,24 @@ tests/cases/conformance/types/intersection/intersectionWithUnionConstraint.ts(12
let y1: string | number = x; // Error
~~
!!! error TS2322: Type 'T & U' is not assignable to type 'string | number'.
!!! error TS2322: Type 'string | undefined' is not assignable to type 'string | number'.
!!! error TS2322: Type 'undefined' is not assignable to type 'string | number'.
!!! error TS2322: Type 'T & U' is not assignable to type 'number'.
!!! error TS2322: Type 'T & U' is not assignable to type 'number'.
let y2: string | null = x; // Error
~~
!!! error TS2322: Type 'T & U' is not assignable to type 'string | null'.
!!! error TS2322: Type 'string | undefined' is not assignable to type 'string | null'.
!!! error TS2322: Type 'undefined' is not assignable to type 'string | null'.
!!! error TS2322: Type 'T & U' is not assignable to type 'string'.
!!! error TS2322: Type 'T & U' is not assignable to type 'string'.
let y3: string | undefined = x;
let y4: number | null = x; // Error
~~
!!! error TS2322: Type 'T & U' is not assignable to type 'number | null'.
!!! error TS2322: Type 'string | undefined' is not assignable to type 'number | null'.
!!! error TS2322: Type 'undefined' is not assignable to type 'number | null'.
!!! error TS2322: Type 'T & U' is not assignable to type 'number'.
!!! error TS2322: Type 'T & U' is not assignable to type 'number'.
let y5: number | undefined = x; // Error
~~
!!! error TS2322: Type 'T & U' is not assignable to type 'number | undefined'.
!!! error TS2322: Type 'string | undefined' is not assignable to type 'number | undefined'.
!!! error TS2322: Type 'string' is not assignable to type 'number | undefined'.
!!! error TS2322: Type 'T & U' is not assignable to type 'number'.
!!! error TS2322: Type 'T & U' is not assignable to type 'number'.
let y6: null | undefined = x; // Error
~~
!!! error TS2322: Type 'T & U' is not assignable to type 'null | undefined'.
!!! error TS2322: Type 'string | undefined' is not assignable to type 'null | undefined'.
!!! error TS2322: Type 'string' is not assignable to type 'null | undefined'.
!!! error TS2322: Type 'T & U' is not assignable to type 'null'.
!!! error TS2322: Type 'T & U' is not assignable to type 'null'.
}
type T1 = (string | number | undefined) & (string | null | undefined); // string | undefined

View File

@ -48,8 +48,6 @@ tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(103,9): error
'string & keyof T' is assignable to the constraint of type 'K', but 'K' could be instantiated with a different subtype of constraint 'string'.
Type 'string' is not assignable to type 'K'.
'string' is assignable to the constraint of type 'K', but 'K' could be instantiated with a different subtype of constraint 'string'.
Type 'string' is not assignable to type 'K'.
'string' is assignable to the constraint of type 'K', but 'K' could be instantiated with a different subtype of constraint 'string'.
tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(105,9): error TS2322: Type 'T[Extract<keyof T, string>]' is not assignable to type 'T[K]'.
Type 'Extract<keyof T, string>' is not assignable to type 'K'.
'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
'string & keyof T' is assignable to the constraint of type 'J', but 'J' could be instantiated with a different subtype of constraint 'string'.
Type 'string' is not assignable to type 'J'.
'string' is assignable to the constraint of type 'J', but 'J' could be instantiated with a different subtype of constraint 'string'.
Type 'string' is not assignable to type 'J'.
'string' is assignable to the constraint of type 'J', but 'J' could be instantiated with a different subtype of constraint 'string'.
tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(117,5): error TS2322: Type 'T[K]' is not assignable to type 'U[J]'.
Type 'T' is not assignable to type 'U'.
'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
!!! 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'.
!!! error TS2322: Type 'string' is not assignable to type 'K'.
!!! error TS2322: 'string' is assignable to the constraint of type 'K', but 'K' could be instantiated with a different subtype of constraint 'string'.
!!! error TS2322: Type 'string' is not assignable to type 'K'.
!!! error TS2322: 'string' is assignable to the constraint of type 'K', but 'K' could be instantiated with a different subtype of constraint 'string'.
t[key] = tk; // ok, T[K] ==> T[keyof T]
tk = t[key]; // error, T[keyof T] =/=> T[K]
~~
@ -299,8 +293,6 @@ tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(142,5): error
!!! 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'.
!!! error TS2322: Type 'string' is not assignable to type 'J'.
!!! error TS2322: 'string' is assignable to the constraint of type 'J', but 'J' could be instantiated with a different subtype of constraint 'string'.
!!! error TS2322: Type 'string' is not assignable to type 'J'.
!!! error TS2322: 'string' is assignable to the constraint of type 'J', but 'J' could be instantiated with a different subtype of constraint 'string'.
tk = uj;
uj = tk; // error

View File

@ -0,0 +1,11 @@
//// [typeParameterExtendsUnionConstraintDistributed.ts]
type A = 1 | 2;
function f<T extends A>(a: T): A & T { return a; } // Shouldn't error
type B = 2 | 3;
function f2<T extends A, U extends B>(ab: T & U): (A | B) & T & U { return ab; } // Also shouldn't error
//// [typeParameterExtendsUnionConstraintDistributed.js]
function f(a) { return a; } // Shouldn't error
function f2(ab) { return ab; } // Also shouldn't error

View File

@ -0,0 +1,32 @@
=== tests/cases/conformance/jsdoc/typeParameterExtendsUnionConstraintDistributed.ts ===
type A = 1 | 2;
>A : Symbol(A, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 0, 0))
function f<T extends A>(a: T): A & T { return a; } // Shouldn't error
>f : Symbol(f, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 0, 15))
>T : Symbol(T, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 1, 11))
>A : Symbol(A, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 0, 0))
>a : Symbol(a, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 1, 24))
>T : Symbol(T, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 1, 11))
>A : Symbol(A, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 0, 0))
>T : Symbol(T, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 1, 11))
>a : Symbol(a, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 1, 24))
type B = 2 | 3;
>B : Symbol(B, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 1, 50))
function f2<T extends A, U extends B>(ab: T & U): (A | B) & T & U { return ab; } // Also shouldn't error
>f2 : Symbol(f2, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 3, 15))
>T : Symbol(T, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 4, 12))
>A : Symbol(A, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 0, 0))
>U : Symbol(U, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 4, 24))
>B : Symbol(B, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 1, 50))
>ab : Symbol(ab, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 4, 38))
>T : Symbol(T, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 4, 12))
>U : Symbol(U, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 4, 24))
>A : Symbol(A, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 0, 0))
>B : Symbol(B, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 1, 50))
>T : Symbol(T, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 4, 12))
>U : Symbol(U, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 4, 24))
>ab : Symbol(ab, Decl(typeParameterExtendsUnionConstraintDistributed.ts, 4, 38))

View File

@ -0,0 +1,17 @@
=== tests/cases/conformance/jsdoc/typeParameterExtendsUnionConstraintDistributed.ts ===
type A = 1 | 2;
>A : A
function f<T extends A>(a: T): A & T { return a; } // Shouldn't error
>f : <T extends A>(a: T) => (1 & T) | (2 & T)
>a : T
>a : T
type B = 2 | 3;
>B : B
function f2<T extends A, U extends B>(ab: T & U): (A | B) & T & U { return ab; } // Also shouldn't error
>f2 : <T extends A, U extends B>(ab: T & U) => (1 & T & U) | (2 & T & U) | (3 & T & U)
>ab : T & U
>ab : T & U

View File

@ -0,0 +1,5 @@
type A = 1 | 2;
function f<T extends A>(a: T): A & T { return a; } // Shouldn't error
type B = 2 | 3;
function f2<T extends A, U extends B>(ab: T & U): (A | B) & T & U { return ab; } // Also shouldn't error