Skip to content

Commit 8dfcc36

Browse files
committed
Defer distributive conditional type when check type is generic
1 parent 2d5be24 commit 8dfcc36

File tree

1 file changed

+44
-32
lines changed

1 file changed

+44
-32
lines changed

src/compiler/checker.ts

Lines changed: 44 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -7899,6 +7899,7 @@ namespace ts {
78997899
}
79007900
else if (flags & TypeFlags.Any) {
79017901
includes |= TypeIncludes.Any;
7902+
if (type === wildcardType) includes |= TypeIncludes.Wildcard;
79027903
}
79037904
else if (flags & TypeFlags.Never) {
79047905
includes |= TypeIncludes.Never;
@@ -7950,7 +7951,7 @@ namespace ts {
79507951
return neverType;
79517952
}
79527953
if (includes & TypeIncludes.Any) {
7953-
return anyType;
7954+
return includes & TypeIncludes.Wildcard ? wildcardType : anyType;
79547955
}
79557956
if (includes & TypeIncludes.EmptyObject && !(includes & TypeIncludes.ObjectType)) {
79567957
typeSet.push(emptyObjectType);
@@ -8188,6 +8189,9 @@ namespace ts {
81888189
}
81898190

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

0 commit comments

Comments
 (0)