I encountered an error when attempting to utilize a recursive type alias in a generic context

When attempting to use a recursive type alias in a generic function, TypeScript v3.7.5 throws the error message:

Type instantiation is excessively deep and possibly infinite.(2589)
.

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

cons...

Playground Link

What causes this issue? Is there a solution that doesn't involve setting a maximum depth value (iterator)?

Answer №1

To ensure finiteness, we can implement an iterator to control recursion levels. In the following example, I restrict recursion to a maximum of 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]
    : [];

The key aspect is:

  • Iterate<X> extends 5 ? [] signifies halting the recursion at the fifth level.
  • Paths<Obj[Key], Iterate<X>>
    increments the I value with each use of Paths.

There have been changes in TypeScript behavior since version 3.4 leading to truncation errors when hitting type instantiation depth limits.

Upon reaching the instantiation depth limit, TypeScript now generates an error to notify users about truncated and unpredictable results. Previously, this behavior was not explicitly communicated. It is likely that you were encountering the limiter before but without receiving any notification.

TypeScript imposes an implicit limit of 50 iterations for recursive types to prevent performance slowdowns during compilation. Developers are now required to manually set boundaries on types to optimize compiler efficiency.

Further details can be found here

Answer №2

Revision:

To ensure you fully comprehend the implications of the solution provided, please review the issues raised by @jcalz in the comment section below.

If you decide to create your own recursive types, it's advisable to incorporate a mechanism, such as the iteration counter suggested by @Maciej Sikora (especially if your code will be part of a public library).


Solution

In cases involving recursive types, encountering this error is sometimes inevitable. However, there exists a solution:

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

The use of infer declaration defers type evaluation, preventing the issue of hitting the hard instantiation limit counter and subsequently avoiding the

Type instantiation is excessively deep
compile error. It is essential to thoroughly test the type after implementing this workaround, as disabling a compiler safety check introduces potential risks.

Alternatively, you can optimize the Paths type manually or utilize a library like ts-toolbelt, which already includes a similar mechanism (discovered by the author @pirix-gh who uses this approach in their library).

Additional Resources

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the best way to remove an exported JavaScript file from Node.js?

In my Node.js library package called "OasisLib," there is a file named TypeGenerator.ts. The specific logic within the file is not crucial, but it requires access to files in the filesystem during the project build process. To achieve this, we utilized let ...

Guide to updating component after closing MatDialog and updating data in database using Angular 6

Currently, I am in the process of learning a MEAN stack app with Angular 6. My main focus right now is on refreshing the component after making any changes, such as adding or updating new clients/cars/drivers/bookings. The issue I'm facing is that aft ...

As time passes, the Azure Service Bus Consumer experiences a decline in performance

My issue involves managing different topics with subscriptions, each tied to a consumer. Over time, I've noticed a decline in the number of messages received. Despite trying to utilize maxconcurrentcalls, it seems to only be effective at the start. My ...

Can inner function calls be mimicked?

Consider this scenario where a module is defined as follows: // utils.ts function innerFunction() { return 28; } function testing() { return innerFunction(); } export {testing} To write a unit test for the testing function and mock the return value ...

What is the proper method for typing unidentified exports that are to be used in TypeScript through named imports?

Currently, I am developing an NPM package that takes the process.env, transforms it, and then exports the transformed environment for easier usage. The module is structured like this: const transformedEnv = transform(process.env) module.exports = transf ...

Is there a method to categorize an array of objects by a specific key and generate a new array of objects based on the grouping in JavaScript?

Suppose I have an array structured like this. It contains objects with different values but the same date. [ { "date": "2020-12-31T18:30:00.000Z", "value": 450 }, { "date": "20 ...

Building a Next.js application that supports both Javascript and Typescript

I currently have a Next.js app that is written in Javascript, but I am looking to transition to writing new code in Typescript. To add Typescript to my project, I tried creating a tsconfig.json file at the project root and then ran npm install --save-dev ...

Linting: Add "`··` eslint(prettier/prettier)" to a project using React with TypeScript and Styled Components

I'm facing a challenge with conflicting rules between Eslint and Prettier in my React project that uses TypeScript and Styled Components. When working in VSCode, I keep getting this error message: "Insert ·· eslint(prettier/prettier)" T ...

What should I do when dealing with multiple submit buttons in NextJS?

How can I differentiate between two submit buttons in a form component created in Next.js? I'm struggling to determine which button was pressed and need help resolving this issue. import React from "react"; const LoginPage = () => { as ...

What is the best approach for utilizing Inheritance in Models within Angular2 with TypeScript?

Hey there, I am currently dealing with a Model Class Question and a ModelClass TrueFalseQuestion. Here are the fields: question.model.ts export class Question { answerId: number; questionTitle: string; questionDescription: string; } truefals ...

Struggling to Decode Octet-stream Data in Angular 6 HttpClient: Encountering Parsing Failure with Error Prompt: "Failed to parse HTTP response for..."

Is there a way to make a non-JSON request to the server using Angular 6 HttpClient (@angular/common/http) in order to receive an Octet-stream? Below is the code I have tried: getFile(file: any) { let headers = new HttpHeaders({ 'Content-T ...

Ensuring Uniform Data Types Across Objects (Using Typescript)

After much trial and error, I have finally reached this point where everything seems to be working perfectly. function test<types extends Record<string,any>>(dict: dictionary<types>){} type dictionary<types extends Record<string, a ...

Angular 8 experiencing unexpected collision issues

Currently, I am utilizing Angular 8 with "typescript": "~3.5.3". My objective is to handle the undefined collision in my code. const { testLocation } = this.ngr.getState(); this.step2 = testLocation && testLocation.step2 ? testLocat ...

Enabling Cross-Site Request Forgery (CSRF) through Angular's HttpClientModule is causing an error message to appear: TS2552 -

As a novice in front-end development, I am looking to incorporate CSRF tokens into certain requests within a frontend application. The app is built using Angular version 12.2.17, and I am following the guidelines outlined in the Angular documentation: http ...

Tips on narrowing down the type of callback event depending on the specific event name

I've been working on implementing an event emitter, and the code is pretty straightforward. Currently, tsc is flagging the event type in eventHandler as 'ErrorEvent' | 'MessageEvent'. This seems to be causing some confusion, and I ...

How does Typescript's dynamic tuple typing tool display all available options in Autocompletion/Intellisense?

Being new to TypeScript, I am facing an issue that I am unsure how to resolve. I want to generate a list of tuples from a list of components. Each tuple should consist of the component's name (keyof MyComponents) and its attributes. (Refer to the co ...

Is your Typescript struggling to infer types correctly?

I created a function that converts an Array into a Map: function toMap<T,TKey,TElement>(items: Array<T>, keySelector: (item: T) => TKey, elementSelector: (item: T) => TElement ): Map<TKey,TElement> { var ma ...

Unable to retrieve a method from the model class in Angular 4

I attempted to utilize a method from the model class, but encountered an error: ERROR TypeError: act.sayHi is not a function Below is my code snippet: Model Class myModelClass.ts export interface IMymodel { name: string; addres ...

Gatsby, in combination with TSC, does not properly transpile the rest operator

I'm attempting to integrate TypeScript with Gatsby following the guidelines provided here. However, I am encountering an issue where the tsc command is failing to transpile the spread (...) operator and producing the error shown below: WAIT Compili ...

Running the nestjs build command is impossible without the existence of the node_modules folder

Currently, I am in the process of creating a Nestjs micro-service and everything is going smoothly. To run the build found within the dist folder, I use the command below: node dist/main.js However, I encountered a problem where this command does not exec ...