@@ -15223,10 +15223,18 @@ namespace ts {
15223
15223
function getConditionalType(root: ConditionalRoot, mapper: TypeMapper | undefined, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type {
15224
15224
let result;
15225
15225
let extraTypes: Type[] | undefined;
15226
+ let tailCount = 0;
15226
15227
// We loop here for an immediately nested conditional type in the false position, effectively treating
15227
15228
// 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.
15229
15232
while (true) {
15233
+ if (tailCount === 1000) {
15234
+ error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite);
15235
+ result = errorType;
15236
+ break;
15237
+ }
15230
15238
const isUnwrapped = isTypicalNondistributiveConditional(root);
15231
15239
const checkType = instantiateType(unwrapNondistributiveConditionalTuple(root, getActualTypeVariable(root.checkType)), mapper);
15232
15240
const checkTypeInstantiable = isGenericType(checkType);
@@ -15270,6 +15278,9 @@ namespace ts {
15270
15278
root = newRoot;
15271
15279
continue;
15272
15280
}
15281
+ if (canTailRecurse(falseType, mapper)) {
15282
+ continue;
15283
+ }
15273
15284
}
15274
15285
result = instantiateType(falseType, mapper);
15275
15286
break;
@@ -15280,7 +15291,12 @@ namespace ts {
15280
15291
// type Foo<T extends { x: any }> = T extends { x: string } ? string : number
15281
15292
// doesn't immediately resolve to 'string' instead of being deferred.
15282
15293
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);
15284
15300
break;
15285
15301
}
15286
15302
}
@@ -15296,6 +15312,34 @@ namespace ts {
15296
15312
break;
15297
15313
}
15298
15314
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
+ }
15299
15343
}
15300
15344
15301
15345
function getTrueTypeFromConditionalType(type: ConditionalType) {
@@ -16316,8 +16360,8 @@ namespace ts {
16316
16360
if (!couldContainTypeVariables(type)) {
16317
16361
return type;
16318
16362
}
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
16321
16365
// or expression. There is a very high likelyhood we're dealing with a combination of infinite generic types
16322
16366
// that perpetually generate new type identities, so we stop the recursion here by yielding the error type.
16323
16367
tracing?.instant(tracing.Phase.CheckTypes, "instantiateType_DepthLimit", { typeId: type.id, instantiationDepth, instantiationCount });
0 commit comments