1

TypeScript v3.7.5 throws Type instantiation is excessively deep and possibly infinite.(2589) when I'm trying to use recursive type alias in generic function.

type State = {
    head: {
        title: string;
        description: string;
        metadata: { type: string; value: string }[];
    };
    body: {
        title: string;
    };
};

cons...

Playground Link

Why does this happen? Is there a way to fix this without providing maximum depth value(iterator)?

2 Answers 2

2

We can make it finite by introduction of some iterator. In below example I limit recursion to 5 levels:

type I = 0 | 1 | 2 | 3 | 4 | 5;
type Iterate<A extends I = 0> = 
    A extends 0 ? 1
    : A extends 1 ? 2
    : A extends 2 ? 3
    : A extends 3 ? 4
    : A extends 4 ? 5
    : A extends 5 ? 5
    : 5

type Paths<Obj, X extends I = 0> = Iterate<X> extends 5 ? [] : Obj extends object
    ? {
          [Key in keyof Obj]: Prepend<Paths<Obj[Key], Iterate<X>>, Key>;
      }[keyof Obj]
    : [];

Core part is

  • Iterate<X> extends 5 ? [] it means that at fifth level we stop the recursion.
  • Paths<Obj[Key], Iterate<X>> every use of Paths gets incremented I

Few things why. TS is behaving like that from 3.4 version. Some info about that

When we hit the instantiation depth limiter we force the type to terminate by resolving to an any. The only change in 3.4 is that we now report an error to let you know that we are delivering truncated (and unpredictable) results. Previously we'd just silently let it pass. So, I'm quite certain you were triggering the limiter before, it's just that you weren't hearing about it.

TS has implicit limit for recursive types equal 50 iterations, but they want to prevent such kind getting to the limit, as such types can slow significantly the compiler. That is why now developer needs to limit the type in order to free the compiler time.

More info can be found here

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks for you answer, but I'm curios why this error happens. Also I'm aware of iterator solution. This was mentioned in the question.
Ah didnt see it. Why - its because such type is possible infinite, it means if you have such types in the codebase you will get very long times of compilation, and this is what TS want to prevent
Addes some more info why
This is probably the safer bet, if you don't use an extra type library (+1)
1

Edit:

Please read the issues mentioned by @jcalz in the comment to make sure you have understand implications of the workaround down under.

If you implement your own custom recursive types, it is also a good idea to safely limit the recursion depth with an iteration counter, like @Maciej Sikora proposed (especially if you publish a public library).


Workaround

With recursive types, this error sometimes cannot be avoided. Fortunately, there is a workaround:

type Paths<Obj> = Obj extends object
    ? {
        [Key in keyof Obj]: Obj[Key] extends infer I ? Prepend<Paths<I>, Key> : never
        // add this         ^                                                       ^
    }[keyof Obj]
    : [];

Above infer declaration will defer type evaluation, so the hard instantiation limit counter is not hit. In consequence that will prevent the Type instantiation is excessively deep compile error. Be sure to test the type thoroughly, as you have disabled a compiler safety check with the escape-hatch.

Alternatively, you can optimize Paths type yourself or use a library like ts-toolbelt, that already has one (the author @pirix-gh discovered above trick and uses this mechanism in his library).

Further links

1 Comment

I feel the need every time I see this to note that this workaround is not supported; see microsoft/TypeScript#26980 for discussion

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.