Max depth limit does not trigger. Gives up and resolves type to any
See original GitHub issueTypeScript Version: 3.5.1
Search Terms:
- Type instantiation is excessively deep and possibly infinite.ts(2589)
- Chaining methods
- Fluent API
- Generic class
- Generic method
Code
Using DataT
,
type AppendToArray<ArrT extends any[], T> = (
(
ArrT[number]|
T
)[]
);
interface Data {
arr : number[]
};
type AppendToFoo<C extends Data, N extends number> = (
Foo<{arr:AppendToArray<C["arr"], N>}>
);
class Foo<DataT extends Data> {
arr! : DataT["arr"];
//Using `DataT`
x<N extends number> (n:N) : AppendToFoo<DataT, N> {
return null!;
}
}
declare const foo0 : Foo<{arr:0[]}>;
/*
const foo12: Foo<{
arr: (0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12)[];
}>
*/
const foo12 = foo0
.x(1).x(2).x(3).x(4).x(5).x(6).x(7).x(8).x(9).x(10).x(11).x(12);
/*
Expected:
+ Similar to foo12
OR
+ Type instantiation is excessively deep and possibly infinite.ts(2589)
Actual:
const foo13: Foo<{
arr: any[];
}>
*/
const foo13 = foo12.x(13);
Using this
,
type AppendToArray<ArrT extends any[], T> = (
(
ArrT[number]|
T
)[]
);
interface Data {
arr : number[]
};
type AppendToFoo<C extends Data, N extends number> = (
Foo<{arr:AppendToArray<C["arr"], N>}>
);
class Foo<DataT extends Data> {
arr! : DataT["arr"];
//Using `this`
x<N extends number> (n:N) : AppendToFoo<this, N> {
return null!;
}
}
declare const foo0 : Foo<{arr:0[]}>;
/*
const foo9: Foo<{
arr: (0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9)[];
}>
*/
const foo9 = foo0
.x(1).x(2).x(3).x(4).x(5).x(6).x(7).x(8).x(9);
/*
Expected:
+ Similar to foo9
OR
+ Type instantiation is excessively deep and possibly infinite.ts(2589)
Actual:
const foo10: Foo<{
arr: any[];
}>
*/
const foo10 = foo9.x(10);
Expected behavior:
Both examples should resolve correctly or trigger, Type instantiation is excessively deep and possibly infinite.ts(2589)
See code for more details.
Actual behavior:
Both examples resolve to any
without errors.
Both examples seem to have a different limit before this happens?
See code for more details.
Playground Link:
Using DataT
, the limit seems to be 12
.
Using this
, the limit seems to be 9
.
Related Issues:
I have no clue how to search for this.
I came across this weird bug by accident, actually.
I was working on a personal project and this would be the 4th, or 6th time I’ve rewritten the project. I’ve noticed that in all the rewrites, there’s this particular method on generic class where the max number of times I could chain it was always 20.
The 21st call would always trigger the max depth error.
I was sick of it and decided to investigate possible ways to work around this limit.
I decided to write a simple repro before messing with it. (The above code snippets). However, the simplified repro behaved very weirdly, and would not trigger the error.
It boggles me how TS can resolve crazy types like this,
but will choke on simple types like the above snippets.
It’s also super weird because my super complex examples have a limit of 20 calls. And these super simple examples have a super low limit.
Issue Analytics
- State:
- Created 4 years ago
- Comments:18 (18 by maintainers)
Top GitHub Comments
Absolutely - the depth limit is as low as it is simply because if it were much higher you’d get a stack overflow because instantiation is written as a set of mutually recursive functions. That’s why I opened https://github.com/microsoft/TypeScript/pull/32611 - all we really want to limit is really complex types (instantiation is effectively type execution, given conditionals, and we do not want to let types execute forever ❤️ ), not deep types, and using a trampoline allows us to remove the stack limit-based constraint.
@weswigham
Here’s an even more mind boggling example,
Playground
It is similar to the
WhyDoesThisWorkFoo<>
example because you get the max depth error at.x(25)
.However it is also similar to the original example because
.x(10)
onward gives youany
.What’s mind boggling is that you do not get any errors and still get
any
from.x(10)
till.x(24)
.I have no idea why.
Expected:
.x(10)
onward should give error (or resolve correctly).Actual:
.x(10)
onward givesany
without error..x(25)
onward gives max depth error. (Just like theWhyDoesThisWorkFoo<>
example)@fatcerberus From
10
to24
, it’s like the compiler is in a superposition of having given up and not given up on resolving the type.I dub it Schrödinger’s type resolution