Skip to content

Commit 1610e19

Browse files
committed
Implement tail-recursion for conditional types and lower general instantiation depth limit to 100
1 parent f9a3d85 commit 1610e19

File tree

1 file changed

+48
-4
lines changed

1 file changed

+48
-4
lines changed

src/compiler/checker.ts

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15223,10 +15223,18 @@ namespace ts {
1522315223
function getConditionalType(root: ConditionalRoot, mapper: TypeMapper | undefined, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type {
1522415224
let result;
1522515225
let extraTypes: Type[] | undefined;
15226+
let tailCount = 0;
1522615227
// We loop here for an immediately nested conditional type in the false position, effectively treating
1522715228
// types of the form 'A extends B ? X : C extends D ? Y : E extends F ? Z : ...' as a single construct for
15228-
// purposes of resolution. This means such types aren't subject to the instantiation depth limiter.
15229+
// purposes of resolution. We also loop here when resolution of a conditional type ends in resolution of
15230+
// another (or, through recursion, possibly the same) conditional type. In the potentially tail-recursive
15231+
// cases we increment the tail recursion counter and stop after 1000 iterations.
1522915232
while (true) {
15233+
if (tailCount === 1000) {
15234+
error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite);
15235+
result = errorType;
15236+
break;
15237+
}
1523015238
const isUnwrapped = isTypicalNondistributiveConditional(root);
1523115239
const checkType = instantiateType(unwrapNondistributiveConditionalTuple(root, getActualTypeVariable(root.checkType)), mapper);
1523215240
const checkTypeInstantiable = isGenericType(checkType);
@@ -15270,6 +15278,9 @@ namespace ts {
1527015278
root = newRoot;
1527115279
continue;
1527215280
}
15281+
if (canTailRecurse(falseType, mapper)) {
15282+
continue;
15283+
}
1527315284
}
1527415285
result = instantiateType(falseType, mapper);
1527515286
break;
@@ -15280,7 +15291,12 @@ namespace ts {
1528015291
// type Foo<T extends { x: any }> = T extends { x: string } ? string : number
1528115292
// doesn't immediately resolve to 'string' instead of being deferred.
1528215293
if (inferredExtendsType.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(inferredExtendsType))) {
15283-
result = instantiateType(getTypeFromTypeNode(root.node.trueType), combinedMapper || mapper);
15294+
const trueType = getTypeFromTypeNode(root.node.trueType);
15295+
const trueMapper = combinedMapper || mapper;
15296+
if (canTailRecurse(trueType, trueMapper)) {
15297+
continue;
15298+
}
15299+
result = instantiateType(trueType, trueMapper);
1528415300
break;
1528515301
}
1528615302
}
@@ -15296,6 +15312,34 @@ namespace ts {
1529615312
break;
1529715313
}
1529815314
return extraTypes ? getUnionType(append(extraTypes, result)) : result;
15315+
// We tail-recurse for generic conditional types that (a) have not already been evaluated and cached, and
15316+
// (b) are non distributive, have a check type that is unaffected by instantiation, or have a non-union check
15317+
// type. Note that recursion is possible only through aliased conditional types, so we only increment the tail
15318+
// recursion counter for those.
15319+
function canTailRecurse(newType: Type, newMapper: TypeMapper | undefined) {
15320+
if (newType.flags & TypeFlags.Conditional && newMapper) {
15321+
const newRoot = (newType as ConditionalType).root;
15322+
if (newRoot.outerTypeParameters) {
15323+
const typeParamMapper = combineTypeMappers((newType as ConditionalType).mapper, newMapper);
15324+
const typeArguments = map(newRoot.outerTypeParameters, t => getMappedType(t, typeParamMapper));
15325+
if (!newRoot.instantiations!.get(getTypeListId(typeArguments))) {
15326+
const newRootMapper = createTypeMapper(newRoot.outerTypeParameters, typeArguments);
15327+
const newCheckType = newRoot.isDistributive ? getMappedType(newRoot.checkType, newRootMapper) : undefined;
15328+
if (!newCheckType || newCheckType === newRoot.checkType || !(newCheckType.flags & (TypeFlags.Union | TypeFlags.Never))) {
15329+
root = newRoot;
15330+
mapper = newRootMapper;
15331+
aliasSymbol = undefined;
15332+
aliasTypeArguments = undefined;
15333+
if (newRoot.aliasSymbol) {
15334+
tailCount++;
15335+
}
15336+
return true;
15337+
}
15338+
}
15339+
}
15340+
}
15341+
return false;
15342+
}
1529915343
}
1530015344

1530115345
function getTrueTypeFromConditionalType(type: ConditionalType) {
@@ -16316,8 +16360,8 @@ namespace ts {
1631616360
if (!couldContainTypeVariables(type)) {
1631716361
return type;
1631816362
}
16319-
if (instantiationDepth === 500 || instantiationCount >= 5000000) {
16320-
// We have reached 500 recursive type instantiations, or 5M type instantiations caused by the same statement
16363+
if (instantiationDepth === 100 || instantiationCount >= 5000000) {
16364+
// We have reached 100 recursive type instantiations, or 5M type instantiations caused by the same statement
1632116365
// or expression. There is a very high likelyhood we're dealing with a combination of infinite generic types
1632216366
// that perpetually generate new type identities, so we stop the recursion here by yielding the error type.
1632316367
tracing?.instant(tracing.Phase.CheckTypes, "instantiateType_DepthLimit", { typeId: type.id, instantiationDepth, instantiationCount });

0 commit comments

Comments
 (0)