mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-12 20:01:02 -05:00
Merge pull request #22313 from Microsoft/fixDistributiveConditionalTypes
Fix distributive conditional types
This commit is contained in:
@@ -7900,6 +7900,7 @@ namespace ts {
|
||||
}
|
||||
else if (flags & TypeFlags.Any) {
|
||||
includes |= TypeIncludes.Any;
|
||||
if (type === wildcardType) includes |= TypeIncludes.Wildcard;
|
||||
}
|
||||
else if (flags & TypeFlags.Never) {
|
||||
includes |= TypeIncludes.Never;
|
||||
@@ -7951,7 +7952,7 @@ namespace ts {
|
||||
return neverType;
|
||||
}
|
||||
if (includes & TypeIncludes.Any) {
|
||||
return anyType;
|
||||
return includes & TypeIncludes.Wildcard ? wildcardType : anyType;
|
||||
}
|
||||
if (includes & TypeIncludes.EmptyObject && !(includes & TypeIncludes.ObjectType)) {
|
||||
typeSet.push(emptyObjectType);
|
||||
@@ -8189,6 +8190,9 @@ namespace ts {
|
||||
}
|
||||
|
||||
function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode): Type {
|
||||
if (objectType === wildcardType || indexType === wildcardType) {
|
||||
return wildcardType;
|
||||
}
|
||||
// If the index type is generic, or if the object type is generic and doesn't originate in an expression,
|
||||
// we are performing a higher-order index access where we cannot meaningfully access the properties of the
|
||||
// object type. Note that for a generic T and a non-generic K, we eagerly resolve T[K] if it originates in
|
||||
@@ -8254,37 +8258,45 @@ namespace ts {
|
||||
function getConditionalType(root: ConditionalRoot, mapper: TypeMapper): Type {
|
||||
const checkType = instantiateType(root.checkType, mapper);
|
||||
const extendsType = instantiateType(root.extendsType, mapper);
|
||||
// Return falseType for a definitely false extends check. We check an instantations of the two
|
||||
// types with type parameters mapped to the wildcard type, the most permissive instantiations
|
||||
// possible (the wildcard type is assignable to and from all types). If those are not related,
|
||||
// then no instatiations will be and we can just return the false branch type.
|
||||
if (!typeMaybeAssignableTo(getWildcardInstantiation(checkType), getWildcardInstantiation(extendsType))) {
|
||||
return instantiateType(root.falseType, mapper);
|
||||
if (checkType === wildcardType || extendsType === wildcardType) {
|
||||
return wildcardType;
|
||||
}
|
||||
// The check could be true for some instantiation
|
||||
let combinedMapper: TypeMapper;
|
||||
if (root.inferTypeParameters) {
|
||||
const inferences = map(root.inferTypeParameters, createInferenceInfo);
|
||||
// We don't want inferences from constraints as they may cause us to eagerly resolve the
|
||||
// conditional type instead of deferring resolution. Also, we always want strict function
|
||||
// types rules (i.e. proper contravariance) for inferences.
|
||||
inferTypes(inferences, checkType, extendsType, InferencePriority.NoConstraints | InferencePriority.AlwaysStrict);
|
||||
// We infer {} when there are no candidates for a type parameter
|
||||
const inferredTypes = map(inferences, inference => getTypeFromInference(inference) || emptyObjectType);
|
||||
combinedMapper = combineTypeMappers(mapper, createTypeMapper(root.inferTypeParameters, inferredTypes));
|
||||
}
|
||||
// Return union of trueType and falseType for 'any' since it matches anything
|
||||
if (checkType.flags & TypeFlags.Any) {
|
||||
return getUnionType([instantiateType(root.trueType, combinedMapper || mapper), instantiateType(root.falseType, mapper)]);
|
||||
}
|
||||
// Instantiate the extends type including inferences for 'infer T' type parameters
|
||||
const inferredExtendsType = combinedMapper ? instantiateType(root.extendsType, combinedMapper) : extendsType;
|
||||
// Return trueType for a definitely true extends check. The definitely assignable relation excludes
|
||||
// type variable constraints from consideration. Without the definitely assignable relation, the type
|
||||
// type Foo<T extends { x: any }> = T extends { x: string } ? string : number
|
||||
// would immediately resolve to 'string' instead of being deferred.
|
||||
if (checkTypeRelatedTo(checkType, inferredExtendsType, definitelyAssignableRelation, /*errorNode*/ undefined)) {
|
||||
return instantiateType(root.trueType, combinedMapper || mapper);
|
||||
// If this is a distributive conditional type and the check type is generic, we need to defer
|
||||
// resolution of the conditional type such that a later instantiation will properly distribute
|
||||
// over union types.
|
||||
if (!root.isDistributive || !maybeTypeOfKind(checkType, TypeFlags.Instantiable)) {
|
||||
// Return falseType for a definitely false extends check. We check an instantations of the two
|
||||
// types with type parameters mapped to the wildcard type, the most permissive instantiations
|
||||
// possible (the wildcard type is assignable to and from all types). If those are not related,
|
||||
// then no instatiations will be and we can just return the false branch type.
|
||||
if (!isTypeAssignableTo(getWildcardInstantiation(checkType), getWildcardInstantiation(extendsType))) {
|
||||
return instantiateType(root.falseType, mapper);
|
||||
}
|
||||
// The check could be true for some instantiation
|
||||
let combinedMapper: TypeMapper;
|
||||
if (root.inferTypeParameters) {
|
||||
const inferences = map(root.inferTypeParameters, createInferenceInfo);
|
||||
// We don't want inferences from constraints as they may cause us to eagerly resolve the
|
||||
// conditional type instead of deferring resolution. Also, we always want strict function
|
||||
// types rules (i.e. proper contravariance) for inferences.
|
||||
inferTypes(inferences, checkType, extendsType, InferencePriority.NoConstraints | InferencePriority.AlwaysStrict);
|
||||
// We infer {} when there are no candidates for a type parameter
|
||||
const inferredTypes = map(inferences, inference => getTypeFromInference(inference) || emptyObjectType);
|
||||
combinedMapper = combineTypeMappers(mapper, createTypeMapper(root.inferTypeParameters, inferredTypes));
|
||||
}
|
||||
// Return union of trueType and falseType for 'any' since it matches anything
|
||||
if (checkType.flags & TypeFlags.Any) {
|
||||
return getUnionType([instantiateType(root.trueType, combinedMapper || mapper), instantiateType(root.falseType, mapper)]);
|
||||
}
|
||||
// Instantiate the extends type including inferences for 'infer T' type parameters
|
||||
const inferredExtendsType = combinedMapper ? instantiateType(root.extendsType, combinedMapper) : extendsType;
|
||||
// Return trueType for a definitely true extends check. The definitely assignable relation excludes
|
||||
// type variable constraints from consideration. Without the definitely assignable relation, the type
|
||||
// type Foo<T extends { x: any }> = T extends { x: string } ? string : number
|
||||
// would immediately resolve to 'string' instead of being deferred.
|
||||
if (checkTypeRelatedTo(checkType, inferredExtendsType, definitelyAssignableRelation, /*errorNode*/ undefined)) {
|
||||
return instantiateType(root.trueType, combinedMapper || mapper);
|
||||
}
|
||||
}
|
||||
// Return a deferred type for a check that is neither definitely true nor definitely false
|
||||
const erasedCheckType = getActualTypeParameter(checkType);
|
||||
|
||||
@@ -191,4 +191,19 @@ tests/cases/conformance/types/conditional/inferTypes1.ts(134,40): error TS2322:
|
||||
type B<T> = string extends T ? { [P in T]: void; } : T; // Error
|
||||
~
|
||||
!!! error TS2322: Type 'T' is not assignable to type 'string'.
|
||||
|
||||
// Repro from #22302
|
||||
|
||||
type MatchingKeys<T, U, K extends keyof T = keyof T> =
|
||||
K extends keyof T ? T[K] extends U ? K : never : never;
|
||||
|
||||
type VoidKeys<T> = MatchingKeys<T, void>;
|
||||
|
||||
interface test {
|
||||
a: 1,
|
||||
b: void
|
||||
}
|
||||
|
||||
type T80 = MatchingKeys<test, void>;
|
||||
type T81 = VoidKeys<test>;
|
||||
|
||||
@@ -133,6 +133,21 @@ type C2<S, U extends void> = S extends A2<infer T, U> ? [T, U] : never;
|
||||
|
||||
type A<T> = T extends string ? { [P in T]: void; } : T;
|
||||
type B<T> = string extends T ? { [P in T]: void; } : T; // Error
|
||||
|
||||
// Repro from #22302
|
||||
|
||||
type MatchingKeys<T, U, K extends keyof T = keyof T> =
|
||||
K extends keyof T ? T[K] extends U ? K : never : never;
|
||||
|
||||
type VoidKeys<T> = MatchingKeys<T, void>;
|
||||
|
||||
interface test {
|
||||
a: 1,
|
||||
b: void
|
||||
}
|
||||
|
||||
type T80 = MatchingKeys<test, void>;
|
||||
type T81 = VoidKeys<test>;
|
||||
|
||||
|
||||
//// [inferTypes1.js]
|
||||
|
||||
@@ -575,3 +575,47 @@ type B<T> = string extends T ? { [P in T]: void; } : T; // Error
|
||||
>T : Symbol(T, Decl(inferTypes1.ts, 133, 7))
|
||||
>T : Symbol(T, Decl(inferTypes1.ts, 133, 7))
|
||||
|
||||
// Repro from #22302
|
||||
|
||||
type MatchingKeys<T, U, K extends keyof T = keyof T> =
|
||||
>MatchingKeys : Symbol(MatchingKeys, Decl(inferTypes1.ts, 133, 55))
|
||||
>T : Symbol(T, Decl(inferTypes1.ts, 137, 18))
|
||||
>U : Symbol(U, Decl(inferTypes1.ts, 137, 20))
|
||||
>K : Symbol(K, Decl(inferTypes1.ts, 137, 23))
|
||||
>T : Symbol(T, Decl(inferTypes1.ts, 137, 18))
|
||||
>T : Symbol(T, Decl(inferTypes1.ts, 137, 18))
|
||||
|
||||
K extends keyof T ? T[K] extends U ? K : never : never;
|
||||
>K : Symbol(K, Decl(inferTypes1.ts, 137, 23))
|
||||
>T : Symbol(T, Decl(inferTypes1.ts, 137, 18))
|
||||
>T : Symbol(T, Decl(inferTypes1.ts, 137, 18))
|
||||
>K : Symbol(K, Decl(inferTypes1.ts, 137, 23))
|
||||
>U : Symbol(U, Decl(inferTypes1.ts, 137, 20))
|
||||
>K : Symbol(K, Decl(inferTypes1.ts, 137, 23))
|
||||
|
||||
type VoidKeys<T> = MatchingKeys<T, void>;
|
||||
>VoidKeys : Symbol(VoidKeys, Decl(inferTypes1.ts, 138, 59))
|
||||
>T : Symbol(T, Decl(inferTypes1.ts, 140, 14))
|
||||
>MatchingKeys : Symbol(MatchingKeys, Decl(inferTypes1.ts, 133, 55))
|
||||
>T : Symbol(T, Decl(inferTypes1.ts, 140, 14))
|
||||
|
||||
interface test {
|
||||
>test : Symbol(test, Decl(inferTypes1.ts, 140, 41))
|
||||
|
||||
a: 1,
|
||||
>a : Symbol(test.a, Decl(inferTypes1.ts, 142, 16))
|
||||
|
||||
b: void
|
||||
>b : Symbol(test.b, Decl(inferTypes1.ts, 143, 9))
|
||||
}
|
||||
|
||||
type T80 = MatchingKeys<test, void>;
|
||||
>T80 : Symbol(T80, Decl(inferTypes1.ts, 145, 1))
|
||||
>MatchingKeys : Symbol(MatchingKeys, Decl(inferTypes1.ts, 133, 55))
|
||||
>test : Symbol(test, Decl(inferTypes1.ts, 140, 41))
|
||||
|
||||
type T81 = VoidKeys<test>;
|
||||
>T81 : Symbol(T81, Decl(inferTypes1.ts, 147, 36))
|
||||
>VoidKeys : Symbol(VoidKeys, Decl(inferTypes1.ts, 138, 59))
|
||||
>test : Symbol(test, Decl(inferTypes1.ts, 140, 41))
|
||||
|
||||
|
||||
@@ -312,7 +312,7 @@ type T60 = infer U; // Error
|
||||
>U : U
|
||||
|
||||
type T61<T> = infer A extends infer B ? infer C : infer D; // Error
|
||||
>T61 : {}
|
||||
>T61 : T61<T>
|
||||
>T : T
|
||||
>A : A
|
||||
>B : B
|
||||
@@ -582,3 +582,47 @@ type B<T> = string extends T ? { [P in T]: void; } : T; // Error
|
||||
>T : T
|
||||
>T : T
|
||||
|
||||
// Repro from #22302
|
||||
|
||||
type MatchingKeys<T, U, K extends keyof T = keyof T> =
|
||||
>MatchingKeys : MatchingKeys<T, U, K>
|
||||
>T : T
|
||||
>U : U
|
||||
>K : K
|
||||
>T : T
|
||||
>T : T
|
||||
|
||||
K extends keyof T ? T[K] extends U ? K : never : never;
|
||||
>K : K
|
||||
>T : T
|
||||
>T : T
|
||||
>K : K
|
||||
>U : U
|
||||
>K : K
|
||||
|
||||
type VoidKeys<T> = MatchingKeys<T, void>;
|
||||
>VoidKeys : MatchingKeys<T, void, keyof T>
|
||||
>T : T
|
||||
>MatchingKeys : MatchingKeys<T, U, K>
|
||||
>T : T
|
||||
|
||||
interface test {
|
||||
>test : test
|
||||
|
||||
a: 1,
|
||||
>a : 1
|
||||
|
||||
b: void
|
||||
>b : void
|
||||
}
|
||||
|
||||
type T80 = MatchingKeys<test, void>;
|
||||
>T80 : "b"
|
||||
>MatchingKeys : MatchingKeys<T, U, K>
|
||||
>test : test
|
||||
|
||||
type T81 = VoidKeys<test>;
|
||||
>T81 : "b"
|
||||
>VoidKeys : MatchingKeys<T, void, keyof T>
|
||||
>test : test
|
||||
|
||||
|
||||
@@ -135,3 +135,18 @@ type C2<S, U extends void> = S extends A2<infer T, U> ? [T, U] : never;
|
||||
|
||||
type A<T> = T extends string ? { [P in T]: void; } : T;
|
||||
type B<T> = string extends T ? { [P in T]: void; } : T; // Error
|
||||
|
||||
// Repro from #22302
|
||||
|
||||
type MatchingKeys<T, U, K extends keyof T = keyof T> =
|
||||
K extends keyof T ? T[K] extends U ? K : never : never;
|
||||
|
||||
type VoidKeys<T> = MatchingKeys<T, void>;
|
||||
|
||||
interface test {
|
||||
a: 1,
|
||||
b: void
|
||||
}
|
||||
|
||||
type T80 = MatchingKeys<test, void>;
|
||||
type T81 = VoidKeys<test>;
|
||||
|
||||
Reference in New Issue
Block a user