Skip to content

Commit 71fe8e8

Browse files
committed
Defer resolution of the true and false branches of conditional types
1 parent ae3d1d4 commit 71fe8e8

File tree

2 files changed

+32
-41
lines changed

2 files changed

+32
-41
lines changed

src/compiler/checker.ts

Lines changed: 30 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,6 @@ namespace ts {
394394
const intersectionTypes = createMap<IntersectionType>();
395395
const literalTypes = createMap<LiteralType>();
396396
const indexedAccessTypes = createMap<IndexedAccessType>();
397-
const conditionalTypes = createMap<Type>();
398397
const substitutionTypes = createMap<SubstitutionType>();
399398
const evolvingArrayTypes: EvolvingArrayType[] = [];
400399
const undefinedProperties = createMap<Symbol>() as UnderscoreEscapedMap<Symbol>;
@@ -3648,8 +3647,8 @@ namespace ts {
36483647
context.inferTypeParameters = (<ConditionalType>type).root.inferTypeParameters;
36493648
const extendsTypeNode = typeToTypeNodeHelper((<ConditionalType>type).extendsType, context);
36503649
context.inferTypeParameters = saveInferTypeParameters;
3651-
const trueTypeNode = typeToTypeNodeHelper((<ConditionalType>type).trueType, context);
3652-
const falseTypeNode = typeToTypeNodeHelper((<ConditionalType>type).falseType, context);
3650+
const trueTypeNode = typeToTypeNodeHelper(getTrueTypeFromConditionalType(<ConditionalType>type), context);
3651+
const falseTypeNode = typeToTypeNodeHelper(getFalseTypeFromConditionalType(<ConditionalType>type), context);
36533652
context.approximateLength += 15;
36543653
return createConditionalTypeNode(checkTypeNode, extendsTypeNode, trueTypeNode, falseTypeNode);
36553654
}
@@ -7674,7 +7673,7 @@ namespace ts {
76747673
// just `any`. This result is _usually_ unwanted - so instead here we elide an `any` branch from the constraint type,
76757674
// in effect treating `any` like `never` rather than `unknown` in this location.
76767675
const trueConstraint = getInferredTrueTypeFromConditionalType(type);
7677-
const falseConstraint = type.falseType;
7676+
const falseConstraint = getFalseTypeFromConditionalType(type);
76787677
type.resolvedDefaultConstraint = isTypeAny(trueConstraint) ? falseConstraint : isTypeAny(falseConstraint) ? trueConstraint : getUnionType([trueConstraint, falseConstraint]);
76797678
}
76807679
return type.resolvedDefaultConstraint;
@@ -10377,37 +10376,23 @@ namespace ts {
1037710376
if (checkType === wildcardType || extendsType === wildcardType) {
1037810377
return wildcardType;
1037910378
}
10380-
const trueType = instantiateType(root.trueType, mapper);
10381-
const falseType = instantiateType(root.falseType, mapper);
10382-
const instantiationId = `${root.isDistributive ? "d" : ""}${getTypeId(checkType)}>${getTypeId(extendsType)}?${getTypeId(trueType)}:${getTypeId(falseType)}`;
10383-
const result = conditionalTypes.get(instantiationId);
10384-
if (result) {
10385-
return result;
10386-
}
10387-
const newResult = getConditionalTypeWorker(root, mapper, checkType, extendsType, trueType, falseType);
10388-
conditionalTypes.set(instantiationId, newResult);
10389-
return newResult;
10390-
}
10391-
10392-
function getConditionalTypeWorker(root: ConditionalRoot, mapper: TypeMapper | undefined, checkType: Type, extendsType: Type, trueType: Type, falseType: Type) {
1039310379
// Simplifications for types of the form `T extends U ? T : never` and `T extends U ? never : T`.
10394-
if (falseType.flags & TypeFlags.Never && getActualTypeVariable(trueType) === getActualTypeVariable(checkType)) {
10380+
if (root.falseType.flags & TypeFlags.Never && getActualTypeVariable(root.trueType) === getActualTypeVariable(root.checkType)) {
1039510381
if (checkType.flags & TypeFlags.Any || isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(extendsType))) { // Always true
10396-
return trueType;
10382+
return instantiateType(root.trueType, mapper);
1039710383
}
1039810384
else if (isIntersectionEmpty(checkType, extendsType)) { // Always false
1039910385
return neverType;
1040010386
}
1040110387
}
10402-
else if (trueType.flags & TypeFlags.Never && getActualTypeVariable(falseType) === getActualTypeVariable(checkType)) {
10388+
else if (root.trueType.flags & TypeFlags.Never && getActualTypeVariable(root.falseType) === getActualTypeVariable(root.checkType)) {
1040310389
if (!(checkType.flags & TypeFlags.Any) && isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(extendsType))) { // Always true
1040410390
return neverType;
1040510391
}
1040610392
else if (checkType.flags & TypeFlags.Any || isIntersectionEmpty(checkType, extendsType)) { // Always false
10407-
return falseType;
10393+
return instantiateType(root.falseType, mapper);
1040810394
}
1040910395
}
10410-
1041110396
const checkTypeInstantiable = maybeTypeOfKind(checkType, TypeFlags.Instantiable | TypeFlags.GenericMappedType);
1041210397
let combinedMapper: TypeMapper | undefined;
1041310398
if (root.inferTypeParameters) {
@@ -10425,49 +10410,55 @@ namespace ts {
1042510410
// We attempt to resolve the conditional type only when the check and extends types are non-generic
1042610411
if (!checkTypeInstantiable && !maybeTypeOfKind(inferredExtendsType, TypeFlags.Instantiable | TypeFlags.GenericMappedType)) {
1042710412
if (inferredExtendsType.flags & TypeFlags.AnyOrUnknown) {
10428-
return combinedMapper ? instantiateType(root.trueType, combinedMapper) : trueType;
10413+
return instantiateType(root.trueType, combinedMapper || mapper);
1042910414
}
1043010415
// Return union of trueType and falseType for 'any' since it matches anything
1043110416
if (checkType.flags & TypeFlags.Any) {
10432-
return getUnionType([combinedMapper ? instantiateType(root.trueType, combinedMapper) : trueType, falseType]);
10417+
return getUnionType([instantiateType(root.trueType, combinedMapper || mapper), instantiateType(root.falseType, mapper)]);
1043310418
}
1043410419
// Return falseType for a definitely false extends check. We check an instantiations of the two
1043510420
// types with type parameters mapped to the wildcard type, the most permissive instantiations
1043610421
// possible (the wildcard type is assignable to and from all types). If those are not related,
1043710422
// then no instantiations will be and we can just return the false branch type.
1043810423
if (!isTypeAssignableTo(getPermissiveInstantiation(checkType), getPermissiveInstantiation(inferredExtendsType))) {
10439-
return falseType;
10424+
return instantiateType(root.falseType, mapper);
1044010425
}
1044110426
// Return trueType for a definitely true extends check. We check instantiations of the two
1044210427
// types with type parameters mapped to their restrictive form, i.e. a form of the type parameter
1044310428
// that has no constraint. This ensures that, for example, the type
1044410429
// type Foo<T extends { x: any }> = T extends { x: string } ? string : number
1044510430
// doesn't immediately resolve to 'string' instead of being deferred.
1044610431
if (isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(inferredExtendsType))) {
10447-
return combinedMapper ? instantiateType(root.trueType, combinedMapper) : trueType;
10432+
return instantiateType(root.trueType, combinedMapper || mapper);
1044810433
}
1044910434
}
1045010435
// Return a deferred type for a check that is neither definitely true nor definitely false
10451-
return getDeferredConditionalType(root, mapper, combinedMapper, checkType, extendsType, trueType, falseType);
10436+
return getDeferredConditionalType(root, mapper, combinedMapper, checkType, extendsType);
1045210437
}
1045310438

10454-
function getDeferredConditionalType(root: ConditionalRoot, mapper: TypeMapper | undefined, combinedMapper: TypeMapper | undefined, checkType: Type, extendsType: Type, trueType: Type, falseType: Type) {
10439+
function getDeferredConditionalType(root: ConditionalRoot, mapper: TypeMapper | undefined, combinedMapper: TypeMapper | undefined, checkType: Type, extendsType: Type) {
1045510440
const erasedCheckType = getActualTypeVariable(checkType);
1045610441
const result = <ConditionalType>createType(TypeFlags.Conditional);
1045710442
result.root = root;
1045810443
result.checkType = erasedCheckType;
1045910444
result.extendsType = extendsType;
1046010445
result.mapper = mapper;
1046110446
result.combinedMapper = combinedMapper;
10462-
result.trueType = trueType;
10463-
result.falseType = falseType;
1046410447
result.aliasSymbol = root.aliasSymbol;
1046510448
result.aliasTypeArguments = instantiateTypes(root.aliasTypeArguments, mapper!); // TODO: GH#18217
1046610449
return result;
1046710450
}
1046810451

10452+
function getTrueTypeFromConditionalType(type: ConditionalType) {
10453+
return type.resolvedTrueType || (type.resolvedTrueType = instantiateType(type.root.trueType, type.mapper));
10454+
}
10455+
10456+
function getFalseTypeFromConditionalType(type: ConditionalType) {
10457+
return type.resolvedFalseType || (type.resolvedFalseType = instantiateType(type.root.falseType, type.mapper));
10458+
}
10459+
1046910460
function getInferredTrueTypeFromConditionalType(type: ConditionalType) {
10470-
return type.resolvedInferredTrueType || (type.resolvedInferredTrueType = instantiateType(type.root.trueType, type.combinedMapper || type.mapper));
10461+
return type.resolvedInferredTrueType || (type.resolvedInferredTrueType = type.combinedMapper ? instantiateType(type.root.trueType, type.combinedMapper) : getTrueTypeFromConditionalType(type));
1047110462
}
1047210463

1047310464
function getInferTypeParameters(node: ConditionalTypeNode): TypeParameter[] | undefined {
@@ -12987,8 +12978,8 @@ namespace ts {
1298712978
if ((<ConditionalType>source).root.isDistributive === (<ConditionalType>target).root.isDistributive) {
1298812979
if (result = isRelatedTo((<ConditionalType>source).checkType, (<ConditionalType>target).checkType, /*reportErrors*/ false)) {
1298912980
if (result &= isRelatedTo((<ConditionalType>source).extendsType, (<ConditionalType>target).extendsType, /*reportErrors*/ false)) {
12990-
if (result &= isRelatedTo((<ConditionalType>source).trueType, (<ConditionalType>target).trueType, /*reportErrors*/ false)) {
12991-
if (result &= isRelatedTo((<ConditionalType>source).falseType, (<ConditionalType>target).falseType, /*reportErrors*/ false)) {
12981+
if (result &= isRelatedTo(getTrueTypeFromConditionalType(<ConditionalType>source), getTrueTypeFromConditionalType(<ConditionalType>target), /*reportErrors*/ false)) {
12982+
if (result &= isRelatedTo(getFalseTypeFromConditionalType(<ConditionalType>source), getFalseTypeFromConditionalType(<ConditionalType>target), /*reportErrors*/ false)) {
1299212983
return result;
1299312984
}
1299412985
}
@@ -13147,8 +13138,8 @@ namespace ts {
1314713138
// and Y1 is related to Y2.
1314813139
if (isTypeIdenticalTo((<ConditionalType>source).extendsType, (<ConditionalType>target).extendsType) &&
1314913140
(isRelatedTo((<ConditionalType>source).checkType, (<ConditionalType>target).checkType) || isRelatedTo((<ConditionalType>target).checkType, (<ConditionalType>source).checkType))) {
13150-
if (result = isRelatedTo((<ConditionalType>source).trueType, (<ConditionalType>target).trueType, reportErrors)) {
13151-
result &= isRelatedTo((<ConditionalType>source).falseType, (<ConditionalType>target).falseType, reportErrors);
13141+
if (result = isRelatedTo(getTrueTypeFromConditionalType(<ConditionalType>source), getTrueTypeFromConditionalType(<ConditionalType>target), reportErrors)) {
13142+
result &= isRelatedTo(getFalseTypeFromConditionalType(<ConditionalType>source), getFalseTypeFromConditionalType(<ConditionalType>target), reportErrors);
1315213143
}
1315313144
if (result) {
1315413145
errorInfo = saveErrorInfo;
@@ -15181,12 +15172,12 @@ namespace ts {
1518115172
else if (source.flags & TypeFlags.Conditional && target.flags & TypeFlags.Conditional) {
1518215173
inferFromTypes((<ConditionalType>source).checkType, (<ConditionalType>target).checkType);
1518315174
inferFromTypes((<ConditionalType>source).extendsType, (<ConditionalType>target).extendsType);
15184-
inferFromTypes((<ConditionalType>source).trueType, (<ConditionalType>target).trueType);
15185-
inferFromTypes((<ConditionalType>source).falseType, (<ConditionalType>target).falseType);
15175+
inferFromTypes(getTrueTypeFromConditionalType(<ConditionalType>source), getTrueTypeFromConditionalType(<ConditionalType>target));
15176+
inferFromTypes(getFalseTypeFromConditionalType(<ConditionalType>source), getFalseTypeFromConditionalType(<ConditionalType>target));
1518615177
}
1518715178
else if (target.flags & TypeFlags.Conditional && !contravariant) {
15188-
inferFromTypes(source, (<ConditionalType>target).trueType);
15189-
inferFromTypes(source, (<ConditionalType>target).falseType);
15179+
inferFromTypes(source, getTrueTypeFromConditionalType(<ConditionalType>target));
15180+
inferFromTypes(source, getFalseTypeFromConditionalType(<ConditionalType>target));
1519015181
}
1519115182
else if (target.flags & TypeFlags.UnionOrIntersection) {
1519215183
// We infer from types that are not naked type variables first so that inferences we

src/compiler/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4338,8 +4338,8 @@ namespace ts {
43384338
root: ConditionalRoot;
43394339
checkType: Type;
43404340
extendsType: Type;
4341-
trueType: Type;
4342-
falseType: Type;
4341+
resolvedTrueType: Type;
4342+
resolvedFalseType: Type;
43434343
/* @internal */
43444344
resolvedInferredTrueType?: Type; // The `trueType` instantiated with the `combinedMapper`, if present
43454345
/* @internal */

0 commit comments

Comments
 (0)