Skip to content

Commit 70818ae

Browse files
authored
Merge pull request microsoft#22313 from Microsoft/fixDistributiveConditionalTypes
Fix distributive conditional types
2 parents cbef5c2 + 6569f45 commit 70818ae

File tree

6 files changed

+178
-33
lines changed

6 files changed

+178
-33
lines changed

src/compiler/checker.ts

Lines changed: 44 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -7900,6 +7900,7 @@ namespace ts {
79007900
}
79017901
else if (flags & TypeFlags.Any) {
79027902
includes |= TypeIncludes.Any;
7903+
if (type === wildcardType) includes |= TypeIncludes.Wildcard;
79037904
}
79047905
else if (flags & TypeFlags.Never) {
79057906
includes |= TypeIncludes.Never;
@@ -7951,7 +7952,7 @@ namespace ts {
79517952
return neverType;
79527953
}
79537954
if (includes & TypeIncludes.Any) {
7954-
return anyType;
7955+
return includes & TypeIncludes.Wildcard ? wildcardType : anyType;
79557956
}
79567957
if (includes & TypeIncludes.EmptyObject && !(includes & TypeIncludes.ObjectType)) {
79577958
typeSet.push(emptyObjectType);
@@ -8189,6 +8190,9 @@ namespace ts {
81898190
}
81908191

81918192
function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode): Type {
8193+
if (objectType === wildcardType || indexType === wildcardType) {
8194+
return wildcardType;
8195+
}
81928196
// If the index type is generic, or if the object type is generic and doesn't originate in an expression,
81938197
// we are performing a higher-order index access where we cannot meaningfully access the properties of the
81948198
// 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 {
82548258
function getConditionalType(root: ConditionalRoot, mapper: TypeMapper): Type {
82558259
const checkType = instantiateType(root.checkType, mapper);
82568260
const extendsType = instantiateType(root.extendsType, mapper);
8257-
// Return falseType for a definitely false extends check. We check an instantations of the two
8258-
// types with type parameters mapped to the wildcard type, the most permissive instantiations
8259-
// possible (the wildcard type is assignable to and from all types). If those are not related,
8260-
// then no instatiations will be and we can just return the false branch type.
8261-
if (!typeMaybeAssignableTo(getWildcardInstantiation(checkType), getWildcardInstantiation(extendsType))) {
8262-
return instantiateType(root.falseType, mapper);
8263-
}
8264-
// The check could be true for some instantiation
8265-
let combinedMapper: TypeMapper;
8266-
if (root.inferTypeParameters) {
8267-
const inferences = map(root.inferTypeParameters, createInferenceInfo);
8268-
// We don't want inferences from constraints as they may cause us to eagerly resolve the
8269-
// conditional type instead of deferring resolution. Also, we always want strict function
8270-
// types rules (i.e. proper contravariance) for inferences.
8271-
inferTypes(inferences, checkType, extendsType, InferencePriority.NoConstraints | InferencePriority.AlwaysStrict);
8272-
// We infer {} when there are no candidates for a type parameter
8273-
const inferredTypes = map(inferences, inference => getTypeFromInference(inference) || emptyObjectType);
8274-
combinedMapper = combineTypeMappers(mapper, createTypeMapper(root.inferTypeParameters, inferredTypes));
8275-
}
8276-
// Return union of trueType and falseType for 'any' since it matches anything
8277-
if (checkType.flags & TypeFlags.Any) {
8278-
return getUnionType([instantiateType(root.trueType, combinedMapper || mapper), instantiateType(root.falseType, mapper)]);
8279-
}
8280-
// Instantiate the extends type including inferences for 'infer T' type parameters
8281-
const inferredExtendsType = combinedMapper ? instantiateType(root.extendsType, combinedMapper) : extendsType;
8282-
// Return trueType for a definitely true extends check. The definitely assignable relation excludes
8283-
// type variable constraints from consideration. Without the definitely assignable relation, the type
8284-
// type Foo<T extends { x: any }> = T extends { x: string } ? string : number
8285-
// would immediately resolve to 'string' instead of being deferred.
8286-
if (checkTypeRelatedTo(checkType, inferredExtendsType, definitelyAssignableRelation, /*errorNode*/ undefined)) {
8287-
return instantiateType(root.trueType, combinedMapper || mapper);
8261+
if (checkType === wildcardType || extendsType === wildcardType) {
8262+
return wildcardType;
8263+
}
8264+
// If this is a distributive conditional type and the check type is generic, we need to defer
8265+
// resolution of the conditional type such that a later instantiation will properly distribute
8266+
// over union types.
8267+
if (!root.isDistributive || !maybeTypeOfKind(checkType, TypeFlags.Instantiable)) {
8268+
// Return falseType for a definitely false extends check. We check an instantations of the two
8269+
// types with type parameters mapped to the wildcard type, the most permissive instantiations
8270+
// possible (the wildcard type is assignable to and from all types). If those are not related,
8271+
// then no instatiations will be and we can just return the false branch type.
8272+
if (!isTypeAssignableTo(getWildcardInstantiation(checkType), getWildcardInstantiation(extendsType))) {
8273+
return instantiateType(root.falseType, mapper);
8274+
}
8275+
// The check could be true for some instantiation
8276+
let combinedMapper: TypeMapper;
8277+
if (root.inferTypeParameters) {
8278+
const inferences = map(root.inferTypeParameters, createInferenceInfo);
8279+
// We don't want inferences from constraints as they may cause us to eagerly resolve the
8280+
// conditional type instead of deferring resolution. Also, we always want strict function
8281+
// types rules (i.e. proper contravariance) for inferences.
8282+
inferTypes(inferences, checkType, extendsType, InferencePriority.NoConstraints | InferencePriority.AlwaysStrict);
8283+
// We infer {} when there are no candidates for a type parameter
8284+
const inferredTypes = map(inferences, inference => getTypeFromInference(inference) || emptyObjectType);
8285+
combinedMapper = combineTypeMappers(mapper, createTypeMapper(root.inferTypeParameters, inferredTypes));
8286+
}
8287+
// Return union of trueType and falseType for 'any' since it matches anything
8288+
if (checkType.flags & TypeFlags.Any) {
8289+
return getUnionType([instantiateType(root.trueType, combinedMapper || mapper), instantiateType(root.falseType, mapper)]);
8290+
}
8291+
// Instantiate the extends type including inferences for 'infer T' type parameters
8292+
const inferredExtendsType = combinedMapper ? instantiateType(root.extendsType, combinedMapper) : extendsType;
8293+
// Return trueType for a definitely true extends check. The definitely assignable relation excludes
8294+
// type variable constraints from consideration. Without the definitely assignable relation, the type
8295+
// type Foo<T extends { x: any }> = T extends { x: string } ? string : number
8296+
// would immediately resolve to 'string' instead of being deferred.
8297+
if (checkTypeRelatedTo(checkType, inferredExtendsType, definitelyAssignableRelation, /*errorNode*/ undefined)) {
8298+
return instantiateType(root.trueType, combinedMapper || mapper);
8299+
}
82888300
}
82898301
// Return a deferred type for a check that is neither definitely true nor definitely false
82908302
const erasedCheckType = getActualTypeParameter(checkType);

tests/baselines/reference/inferTypes1.errors.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,4 +191,19 @@ tests/cases/conformance/types/conditional/inferTypes1.ts(134,40): error TS2322:
191191
type B<T> = string extends T ? { [P in T]: void; } : T; // Error
192192
~
193193
!!! error TS2322: Type 'T' is not assignable to type 'string'.
194+
195+
// Repro from #22302
196+
197+
type MatchingKeys<T, U, K extends keyof T = keyof T> =
198+
K extends keyof T ? T[K] extends U ? K : never : never;
199+
200+
type VoidKeys<T> = MatchingKeys<T, void>;
201+
202+
interface test {
203+
a: 1,
204+
b: void
205+
}
206+
207+
type T80 = MatchingKeys<test, void>;
208+
type T81 = VoidKeys<test>;
194209

tests/baselines/reference/inferTypes1.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,21 @@ type C2<S, U extends void> = S extends A2<infer T, U> ? [T, U] : never;
133133

134134
type A<T> = T extends string ? { [P in T]: void; } : T;
135135
type B<T> = string extends T ? { [P in T]: void; } : T; // Error
136+
137+
// Repro from #22302
138+
139+
type MatchingKeys<T, U, K extends keyof T = keyof T> =
140+
K extends keyof T ? T[K] extends U ? K : never : never;
141+
142+
type VoidKeys<T> = MatchingKeys<T, void>;
143+
144+
interface test {
145+
a: 1,
146+
b: void
147+
}
148+
149+
type T80 = MatchingKeys<test, void>;
150+
type T81 = VoidKeys<test>;
136151

137152

138153
//// [inferTypes1.js]

tests/baselines/reference/inferTypes1.symbols

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,3 +575,47 @@ type B<T> = string extends T ? { [P in T]: void; } : T; // Error
575575
>T : Symbol(T, Decl(inferTypes1.ts, 133, 7))
576576
>T : Symbol(T, Decl(inferTypes1.ts, 133, 7))
577577

578+
// Repro from #22302
579+
580+
type MatchingKeys<T, U, K extends keyof T = keyof T> =
581+
>MatchingKeys : Symbol(MatchingKeys, Decl(inferTypes1.ts, 133, 55))
582+
>T : Symbol(T, Decl(inferTypes1.ts, 137, 18))
583+
>U : Symbol(U, Decl(inferTypes1.ts, 137, 20))
584+
>K : Symbol(K, Decl(inferTypes1.ts, 137, 23))
585+
>T : Symbol(T, Decl(inferTypes1.ts, 137, 18))
586+
>T : Symbol(T, Decl(inferTypes1.ts, 137, 18))
587+
588+
K extends keyof T ? T[K] extends U ? K : never : never;
589+
>K : Symbol(K, Decl(inferTypes1.ts, 137, 23))
590+
>T : Symbol(T, Decl(inferTypes1.ts, 137, 18))
591+
>T : Symbol(T, Decl(inferTypes1.ts, 137, 18))
592+
>K : Symbol(K, Decl(inferTypes1.ts, 137, 23))
593+
>U : Symbol(U, Decl(inferTypes1.ts, 137, 20))
594+
>K : Symbol(K, Decl(inferTypes1.ts, 137, 23))
595+
596+
type VoidKeys<T> = MatchingKeys<T, void>;
597+
>VoidKeys : Symbol(VoidKeys, Decl(inferTypes1.ts, 138, 59))
598+
>T : Symbol(T, Decl(inferTypes1.ts, 140, 14))
599+
>MatchingKeys : Symbol(MatchingKeys, Decl(inferTypes1.ts, 133, 55))
600+
>T : Symbol(T, Decl(inferTypes1.ts, 140, 14))
601+
602+
interface test {
603+
>test : Symbol(test, Decl(inferTypes1.ts, 140, 41))
604+
605+
a: 1,
606+
>a : Symbol(test.a, Decl(inferTypes1.ts, 142, 16))
607+
608+
b: void
609+
>b : Symbol(test.b, Decl(inferTypes1.ts, 143, 9))
610+
}
611+
612+
type T80 = MatchingKeys<test, void>;
613+
>T80 : Symbol(T80, Decl(inferTypes1.ts, 145, 1))
614+
>MatchingKeys : Symbol(MatchingKeys, Decl(inferTypes1.ts, 133, 55))
615+
>test : Symbol(test, Decl(inferTypes1.ts, 140, 41))
616+
617+
type T81 = VoidKeys<test>;
618+
>T81 : Symbol(T81, Decl(inferTypes1.ts, 147, 36))
619+
>VoidKeys : Symbol(VoidKeys, Decl(inferTypes1.ts, 138, 59))
620+
>test : Symbol(test, Decl(inferTypes1.ts, 140, 41))
621+

tests/baselines/reference/inferTypes1.types

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ type T60 = infer U; // Error
312312
>U : U
313313

314314
type T61<T> = infer A extends infer B ? infer C : infer D; // Error
315-
>T61 : {}
315+
>T61 : T61<T>
316316
>T : T
317317
>A : A
318318
>B : B
@@ -582,3 +582,47 @@ type B<T> = string extends T ? { [P in T]: void; } : T; // Error
582582
>T : T
583583
>T : T
584584

585+
// Repro from #22302
586+
587+
type MatchingKeys<T, U, K extends keyof T = keyof T> =
588+
>MatchingKeys : MatchingKeys<T, U, K>
589+
>T : T
590+
>U : U
591+
>K : K
592+
>T : T
593+
>T : T
594+
595+
K extends keyof T ? T[K] extends U ? K : never : never;
596+
>K : K
597+
>T : T
598+
>T : T
599+
>K : K
600+
>U : U
601+
>K : K
602+
603+
type VoidKeys<T> = MatchingKeys<T, void>;
604+
>VoidKeys : MatchingKeys<T, void, keyof T>
605+
>T : T
606+
>MatchingKeys : MatchingKeys<T, U, K>
607+
>T : T
608+
609+
interface test {
610+
>test : test
611+
612+
a: 1,
613+
>a : 1
614+
615+
b: void
616+
>b : void
617+
}
618+
619+
type T80 = MatchingKeys<test, void>;
620+
>T80 : "b"
621+
>MatchingKeys : MatchingKeys<T, U, K>
622+
>test : test
623+
624+
type T81 = VoidKeys<test>;
625+
>T81 : "b"
626+
>VoidKeys : MatchingKeys<T, void, keyof T>
627+
>test : test
628+

tests/cases/conformance/types/conditional/inferTypes1.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,3 +135,18 @@ type C2<S, U extends void> = S extends A2<infer T, U> ? [T, U] : never;
135135

136136
type A<T> = T extends string ? { [P in T]: void; } : T;
137137
type B<T> = string extends T ? { [P in T]: void; } : T; // Error
138+
139+
// Repro from #22302
140+
141+
type MatchingKeys<T, U, K extends keyof T = keyof T> =
142+
K extends keyof T ? T[K] extends U ? K : never : never;
143+
144+
type VoidKeys<T> = MatchingKeys<T, void>;
145+
146+
interface test {
147+
a: 1,
148+
b: void
149+
}
150+
151+
type T80 = MatchingKeys<test, void>;
152+
type T81 = VoidKeys<test>;

0 commit comments

Comments
 (0)