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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy