diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6f78f3fbfb5ef..188a8351c3acb 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15223,10 +15223,18 @@ namespace ts { function getConditionalType(root: ConditionalRoot, mapper: TypeMapper | undefined, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { let result; let extraTypes: Type[] | undefined; + let tailCount = 0; // We loop here for an immediately nested conditional type in the false position, effectively treating // types of the form 'A extends B ? X : C extends D ? Y : E extends F ? Z : ...' as a single construct for - // purposes of resolution. This means such types aren't subject to the instantiation depth limiter. + // purposes of resolution. We also loop here when resolution of a conditional type ends in resolution of + // another (or, through recursion, possibly the same) conditional type. In the potentially tail-recursive + // cases we increment the tail recursion counter and stop after 1000 iterations. while (true) { + if (tailCount === 1000) { + error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite); + result = errorType; + break; + } const isUnwrapped = isTypicalNondistributiveConditional(root); const checkType = instantiateType(unwrapNondistributiveConditionalTuple(root, getActualTypeVariable(root.checkType)), mapper); const checkTypeInstantiable = isGenericType(checkType); @@ -15270,6 +15278,9 @@ namespace ts { root = newRoot; continue; } + if (canTailRecurse(falseType, mapper)) { + continue; + } } result = instantiateType(falseType, mapper); break; @@ -15280,7 +15291,12 @@ namespace ts { // type Foo = T extends { x: string } ? string : number // doesn't immediately resolve to 'string' instead of being deferred. if (inferredExtendsType.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(inferredExtendsType))) { - result = instantiateType(getTypeFromTypeNode(root.node.trueType), combinedMapper || mapper); + const trueType = getTypeFromTypeNode(root.node.trueType); + const trueMapper = combinedMapper || mapper; + if (canTailRecurse(trueType, trueMapper)) { + continue; + } + result = instantiateType(trueType, trueMapper); break; } } @@ -15296,6 +15312,32 @@ namespace ts { break; } return extraTypes ? getUnionType(append(extraTypes, result)) : result; + // We tail-recurse for generic conditional types that (a) have not already been evaluated and cached, and + // (b) are non distributive, have a check type that is unaffected by instantiation, or have a non-union check + // type. Note that recursion is possible only through aliased conditional types, so we only increment the tail + // recursion counter for those. + function canTailRecurse(newType: Type, newMapper: TypeMapper | undefined) { + if (newType.flags & TypeFlags.Conditional && newMapper) { + const newRoot = (newType as ConditionalType).root; + if (newRoot.outerTypeParameters) { + const typeParamMapper = combineTypeMappers((newType as ConditionalType).mapper, newMapper); + const typeArguments = map(newRoot.outerTypeParameters, t => getMappedType(t, typeParamMapper)); + const newRootMapper = createTypeMapper(newRoot.outerTypeParameters, typeArguments); + const newCheckType = newRoot.isDistributive ? getMappedType(newRoot.checkType, newRootMapper) : undefined; + if (!newCheckType || newCheckType === newRoot.checkType || !(newCheckType.flags & (TypeFlags.Union | TypeFlags.Never))) { + root = newRoot; + mapper = newRootMapper; + aliasSymbol = undefined; + aliasTypeArguments = undefined; + if (newRoot.aliasSymbol) { + tailCount++; + } + return true; + } + } + } + return false; + } } function getTrueTypeFromConditionalType(type: ConditionalType) { @@ -16316,8 +16358,8 @@ namespace ts { if (!couldContainTypeVariables(type)) { return type; } - if (instantiationDepth === 500 || instantiationCount >= 5000000) { - // We have reached 500 recursive type instantiations, or 5M type instantiations caused by the same statement + if (instantiationDepth === 100 || instantiationCount >= 5000000) { + // We have reached 100 recursive type instantiations, or 5M type instantiations caused by the same statement // or expression. There is a very high likelyhood we're dealing with a combination of infinite generic types // that perpetually generate new type identities, so we stop the recursion here by yielding the error type. tracing?.instant(tracing.Phase.CheckTypes, "instantiateType_DepthLimit", { typeId: type.id, instantiationDepth, instantiationCount }); diff --git a/tests/baselines/reference/tailRecursiveConditionalTypes.js b/tests/baselines/reference/tailRecursiveConditionalTypes.js new file mode 100644 index 0000000000000..8ae35aad15147 --- /dev/null +++ b/tests/baselines/reference/tailRecursiveConditionalTypes.js @@ -0,0 +1,49 @@ +//// [tailRecursiveConditionalTypes.ts] +type Trim = + S extends ` ${infer T}` ? Trim : + S extends `${infer T} ` ? Trim : + S; + +type T10 = Trim<' hello '>; +type T11 = Trim<' hello '>; + +type GetChars = GetCharsRec; +type GetCharsRec = + S extends `${infer Char}${infer Rest}` ? GetCharsRec : Acc; + +type T20 = GetChars<'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'>; + +type Reverse = any[] extends T ? T : ReverseRec; +type ReverseRec = + T extends [infer Head, ...infer Tail] ? ReverseRec : Acc; + +type T30 = Reverse<[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]>; +type T31 = Reverse; + +type TupleOf = number extends N ? T[] : TupleOfRec; +type TupleOfRec = + Acc["length"] extends N ? Acc : TupleOfRec; + +type T40 = TupleOf; +type T41 = TupleOf; + + +//// [tailRecursiveConditionalTypes.js] +"use strict"; + + +//// [tailRecursiveConditionalTypes.d.ts] +declare type Trim = S extends ` ${infer T}` ? Trim : S extends `${infer T} ` ? Trim : S; +declare type T10 = Trim<' hello '>; +declare type T11 = Trim<' hello '>; +declare type GetChars = GetCharsRec; +declare type GetCharsRec = S extends `${infer Char}${infer Rest}` ? GetCharsRec : Acc; +declare type T20 = GetChars<'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'>; +declare type Reverse = any[] extends T ? T : ReverseRec; +declare type ReverseRec = T extends [infer Head, ...infer Tail] ? ReverseRec : Acc; +declare type T30 = Reverse<[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]>; +declare type T31 = Reverse; +declare type TupleOf = number extends N ? T[] : TupleOfRec; +declare type TupleOfRec = Acc["length"] extends N ? Acc : TupleOfRec; +declare type T40 = TupleOf; +declare type T41 = TupleOf; diff --git a/tests/baselines/reference/tailRecursiveConditionalTypes.symbols b/tests/baselines/reference/tailRecursiveConditionalTypes.symbols new file mode 100644 index 0000000000000..001b338b1653d --- /dev/null +++ b/tests/baselines/reference/tailRecursiveConditionalTypes.symbols @@ -0,0 +1,118 @@ +=== tests/cases/compiler/tailRecursiveConditionalTypes.ts === +type Trim = +>Trim : Symbol(Trim, Decl(tailRecursiveConditionalTypes.ts, 0, 0)) +>S : Symbol(S, Decl(tailRecursiveConditionalTypes.ts, 0, 10)) + + S extends ` ${infer T}` ? Trim : +>S : Symbol(S, Decl(tailRecursiveConditionalTypes.ts, 0, 10)) +>T : Symbol(T, Decl(tailRecursiveConditionalTypes.ts, 1, 23)) +>Trim : Symbol(Trim, Decl(tailRecursiveConditionalTypes.ts, 0, 0)) +>T : Symbol(T, Decl(tailRecursiveConditionalTypes.ts, 1, 23)) + + S extends `${infer T} ` ? Trim : +>S : Symbol(S, Decl(tailRecursiveConditionalTypes.ts, 0, 10)) +>T : Symbol(T, Decl(tailRecursiveConditionalTypes.ts, 2, 22)) +>Trim : Symbol(Trim, Decl(tailRecursiveConditionalTypes.ts, 0, 0)) +>T : Symbol(T, Decl(tailRecursiveConditionalTypes.ts, 2, 22)) + + S; +>S : Symbol(S, Decl(tailRecursiveConditionalTypes.ts, 0, 10)) + +type T10 = Trim<' hello '>; +>T10 : Symbol(T10, Decl(tailRecursiveConditionalTypes.ts, 3, 6)) +>Trim : Symbol(Trim, Decl(tailRecursiveConditionalTypes.ts, 0, 0)) + +type T11 = Trim<' hello '>; +>T11 : Symbol(T11, Decl(tailRecursiveConditionalTypes.ts, 5, 170)) +>Trim : Symbol(Trim, Decl(tailRecursiveConditionalTypes.ts, 0, 0)) + +type GetChars = GetCharsRec; +>GetChars : Symbol(GetChars, Decl(tailRecursiveConditionalTypes.ts, 6, 170)) +>S : Symbol(S, Decl(tailRecursiveConditionalTypes.ts, 8, 14)) +>GetCharsRec : Symbol(GetCharsRec, Decl(tailRecursiveConditionalTypes.ts, 8, 41)) +>S : Symbol(S, Decl(tailRecursiveConditionalTypes.ts, 8, 14)) + +type GetCharsRec = +>GetCharsRec : Symbol(GetCharsRec, Decl(tailRecursiveConditionalTypes.ts, 8, 41)) +>S : Symbol(S, Decl(tailRecursiveConditionalTypes.ts, 9, 17)) +>Acc : Symbol(Acc, Decl(tailRecursiveConditionalTypes.ts, 9, 19)) + + S extends `${infer Char}${infer Rest}` ? GetCharsRec : Acc; +>S : Symbol(S, Decl(tailRecursiveConditionalTypes.ts, 9, 17)) +>Char : Symbol(Char, Decl(tailRecursiveConditionalTypes.ts, 10, 22)) +>Rest : Symbol(Rest, Decl(tailRecursiveConditionalTypes.ts, 10, 35)) +>GetCharsRec : Symbol(GetCharsRec, Decl(tailRecursiveConditionalTypes.ts, 8, 41)) +>Rest : Symbol(Rest, Decl(tailRecursiveConditionalTypes.ts, 10, 35)) +>Char : Symbol(Char, Decl(tailRecursiveConditionalTypes.ts, 10, 22)) +>Acc : Symbol(Acc, Decl(tailRecursiveConditionalTypes.ts, 9, 19)) +>Acc : Symbol(Acc, Decl(tailRecursiveConditionalTypes.ts, 9, 19)) + +type T20 = GetChars<'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'>; +>T20 : Symbol(T20, Decl(tailRecursiveConditionalTypes.ts, 10, 81)) +>GetChars : Symbol(GetChars, Decl(tailRecursiveConditionalTypes.ts, 6, 170)) + +type Reverse = any[] extends T ? T : ReverseRec; +>Reverse : Symbol(Reverse, Decl(tailRecursiveConditionalTypes.ts, 12, 86)) +>T : Symbol(T, Decl(tailRecursiveConditionalTypes.ts, 14, 13)) +>T : Symbol(T, Decl(tailRecursiveConditionalTypes.ts, 14, 13)) +>T : Symbol(T, Decl(tailRecursiveConditionalTypes.ts, 14, 13)) +>ReverseRec : Symbol(ReverseRec, Decl(tailRecursiveConditionalTypes.ts, 14, 58)) +>T : Symbol(T, Decl(tailRecursiveConditionalTypes.ts, 14, 13)) + +type ReverseRec = +>ReverseRec : Symbol(ReverseRec, Decl(tailRecursiveConditionalTypes.ts, 14, 58)) +>T : Symbol(T, Decl(tailRecursiveConditionalTypes.ts, 15, 16)) +>Acc : Symbol(Acc, Decl(tailRecursiveConditionalTypes.ts, 15, 18)) + + T extends [infer Head, ...infer Tail] ? ReverseRec : Acc; +>T : Symbol(T, Decl(tailRecursiveConditionalTypes.ts, 15, 16)) +>Head : Symbol(Head, Decl(tailRecursiveConditionalTypes.ts, 16, 20)) +>Tail : Symbol(Tail, Decl(tailRecursiveConditionalTypes.ts, 16, 35)) +>ReverseRec : Symbol(ReverseRec, Decl(tailRecursiveConditionalTypes.ts, 14, 58)) +>Tail : Symbol(Tail, Decl(tailRecursiveConditionalTypes.ts, 16, 35)) +>Head : Symbol(Head, Decl(tailRecursiveConditionalTypes.ts, 16, 20)) +>Acc : Symbol(Acc, Decl(tailRecursiveConditionalTypes.ts, 15, 18)) +>Acc : Symbol(Acc, Decl(tailRecursiveConditionalTypes.ts, 15, 18)) + +type T30 = Reverse<[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]>; +>T30 : Symbol(T30, Decl(tailRecursiveConditionalTypes.ts, 16, 83)) +>Reverse : Symbol(Reverse, Decl(tailRecursiveConditionalTypes.ts, 12, 86)) + +type T31 = Reverse; +>T31 : Symbol(T31, Decl(tailRecursiveConditionalTypes.ts, 18, 171)) +>Reverse : Symbol(Reverse, Decl(tailRecursiveConditionalTypes.ts, 12, 86)) + +type TupleOf = number extends N ? T[] : TupleOfRec; +>TupleOf : Symbol(TupleOf, Decl(tailRecursiveConditionalTypes.ts, 19, 29)) +>T : Symbol(T, Decl(tailRecursiveConditionalTypes.ts, 21, 13)) +>N : Symbol(N, Decl(tailRecursiveConditionalTypes.ts, 21, 15)) +>N : Symbol(N, Decl(tailRecursiveConditionalTypes.ts, 21, 15)) +>T : Symbol(T, Decl(tailRecursiveConditionalTypes.ts, 21, 13)) +>TupleOfRec : Symbol(TupleOfRec, Decl(tailRecursiveConditionalTypes.ts, 21, 82)) +>T : Symbol(T, Decl(tailRecursiveConditionalTypes.ts, 21, 13)) +>N : Symbol(N, Decl(tailRecursiveConditionalTypes.ts, 21, 15)) + +type TupleOfRec = +>TupleOfRec : Symbol(TupleOfRec, Decl(tailRecursiveConditionalTypes.ts, 21, 82)) +>T : Symbol(T, Decl(tailRecursiveConditionalTypes.ts, 22, 16)) +>N : Symbol(N, Decl(tailRecursiveConditionalTypes.ts, 22, 18)) +>Acc : Symbol(Acc, Decl(tailRecursiveConditionalTypes.ts, 22, 36)) + + Acc["length"] extends N ? Acc : TupleOfRec; +>Acc : Symbol(Acc, Decl(tailRecursiveConditionalTypes.ts, 22, 36)) +>N : Symbol(N, Decl(tailRecursiveConditionalTypes.ts, 22, 18)) +>Acc : Symbol(Acc, Decl(tailRecursiveConditionalTypes.ts, 22, 36)) +>TupleOfRec : Symbol(TupleOfRec, Decl(tailRecursiveConditionalTypes.ts, 21, 82)) +>T : Symbol(T, Decl(tailRecursiveConditionalTypes.ts, 22, 16)) +>N : Symbol(N, Decl(tailRecursiveConditionalTypes.ts, 22, 18)) +>T : Symbol(T, Decl(tailRecursiveConditionalTypes.ts, 22, 16)) +>Acc : Symbol(Acc, Decl(tailRecursiveConditionalTypes.ts, 22, 36)) + +type T40 = TupleOf; +>T40 : Symbol(T40, Decl(tailRecursiveConditionalTypes.ts, 23, 66)) +>TupleOf : Symbol(TupleOf, Decl(tailRecursiveConditionalTypes.ts, 19, 29)) + +type T41 = TupleOf; +>T41 : Symbol(T41, Decl(tailRecursiveConditionalTypes.ts, 25, 29)) +>TupleOf : Symbol(TupleOf, Decl(tailRecursiveConditionalTypes.ts, 19, 29)) + diff --git a/tests/baselines/reference/tailRecursiveConditionalTypes.types b/tests/baselines/reference/tailRecursiveConditionalTypes.types new file mode 100644 index 0000000000000..a61bbd5c1497b --- /dev/null +++ b/tests/baselines/reference/tailRecursiveConditionalTypes.types @@ -0,0 +1,53 @@ +=== tests/cases/compiler/tailRecursiveConditionalTypes.ts === +type Trim = +>Trim : Trim + + S extends ` ${infer T}` ? Trim : + S extends `${infer T} ` ? Trim : + S; + +type T10 = Trim<' hello '>; +>T10 : "hello" + +type T11 = Trim<' hello '>; +>T11 : "hello" + +type GetChars = GetCharsRec; +>GetChars : GetChars + +type GetCharsRec = +>GetCharsRec : GetCharsRec + + S extends `${infer Char}${infer Rest}` ? GetCharsRec : Acc; + +type T20 = GetChars<'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'>; +>T20 : "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z" | "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z" | "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" + +type Reverse = any[] extends T ? T : ReverseRec; +>Reverse : Reverse + +type ReverseRec = +>ReverseRec : ReverseRec + + T extends [infer Head, ...infer Tail] ? ReverseRec : Acc; + +type T30 = Reverse<[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]>; +>T30 : [9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0] + +type T31 = Reverse; +>T31 : string[] + +type TupleOf = number extends N ? T[] : TupleOfRec; +>TupleOf : TupleOf + +type TupleOfRec = +>TupleOfRec : TupleOfRec + + Acc["length"] extends N ? Acc : TupleOfRec; + +type T40 = TupleOf; +>T40 : [any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any] + +type T41 = TupleOf; +>T41 : any[] + diff --git a/tests/cases/compiler/tailRecursiveConditionalTypes.ts b/tests/cases/compiler/tailRecursiveConditionalTypes.ts new file mode 100644 index 0000000000000..b0ea920147d07 --- /dev/null +++ b/tests/cases/compiler/tailRecursiveConditionalTypes.ts @@ -0,0 +1,30 @@ +// @strict: true +// @declaration: true + +type Trim = + S extends ` ${infer T}` ? Trim : + S extends `${infer T} ` ? Trim : + S; + +type T10 = Trim<' hello '>; +type T11 = Trim<' hello '>; + +type GetChars = GetCharsRec; +type GetCharsRec = + S extends `${infer Char}${infer Rest}` ? GetCharsRec : Acc; + +type T20 = GetChars<'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'>; + +type Reverse = any[] extends T ? T : ReverseRec; +type ReverseRec = + T extends [infer Head, ...infer Tail] ? ReverseRec : Acc; + +type T30 = Reverse<[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]>; +type T31 = Reverse; + +type TupleOf = number extends N ? T[] : TupleOfRec; +type TupleOfRec = + Acc["length"] extends N ? Acc : TupleOfRec; + +type T40 = TupleOf; +type T41 = TupleOf;