When using my recursive type on Window or Element, I encounter a type instantiation error stating "Type instantiation is excessively deep and possibly infinite.ts"

Recently, I have been using a type to automatically mock interface types in Jest tests. However, upon updating TypeScript and Jest to the latest versions, I encountered an error message stating

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

It has also come to my attention that computing this type takes a significant amount of time, leading me to believe that I may have designed the type incorrectly.

The main purpose of this type is to take an interface as a generic property and produce a type where the methods are jest.Mock functions. Furthermore, if the properties are not basic types, then it should recursively mock the nested type.

interface Foo {
  foo(): void
  bar: {
    bar(): void
  }
  foobar: string
}

type MockedFoo = MockedInterface<Foo>

// Essentially
interface MockedFoo {
  foo: jest.Mock<void, []>
  bar: {
    bar: jest.Mock<void, []>
  }
  foobar: string
}

The implementation of my type looks like:

type JestMock<T, Y extends Array<any>> = (...args: Y) => T

export interface CalledWithMock<T, Y extends Array<any>> extends JestMock<T, Y> {
  calledWith: (...args: Y) => JestMock<T, Y>;
}

export type MockedInterface<T> = {
  [K in keyof T]: T[K] extends (...args: infer A) => infer B
    ? CalledWithMock<B, A> & T[K]
    : MockedInterface<T[K]> & T[K];
};

However, when attempting to mock something like Window or Element, I encounter the previously mentioned error (possibly due to potential self-referencing).

function foo(_e: Element) {}

const d = document.createElement('div')
const dm = mockElement<HTMLDivElement>()

foo(d)
foo(dm) // Type instantiation is excessively deep and possibly infinite.

You can view a demonstration of my exact scenario on the following playground:

Typescript Playground

Answer №1

One possible recommendation is to update

export type MockedInterface<T> = {
  [K in keyof T]: T[K] extends (...args: infer A) => infer B
    ? CalledWithMock<B, A> & T[K]
    : MockedInterface<T[K]> & T[K];
};

to

export type MockedInterface<T> = {
    [K in keyof T]: T[K] extends (...args: infer A) => infer B
    ? T[K] & CalledWithMock<B, A>
    : T[K] & MockedInterface<T[K]>;
};

This adjustment entails moving the intersection with T[K] to the first part of each expression. In general, unless X and Y have matching function-typed signatures or properties, X & Y is equivalent to Y & X (specifically for intersections of function types, the order of overload call signatures matters when resolving overloads).

You can verify that this modification functions as intended:

foo(dm); // okay

The reason behind why this version performs better isn't definitively established. While it's true that order can impact performance at times (such as in microsoft/TypeScript#43437), there isn't explicit documentation regarding intersections like this.

A plausible explanation would be that the compiler evaluates an intersection type like F<T> & G<T> by first computing F<T>, then calculating G<T>, and ultimately intersecting them. If G<T> is straightforward to compute while F<T> is complex, then

U extends G<T> & F<T>
might offer better performance compared to
U extends F<T> & G<T>
.

In your scenario, the compiler needed to assess if

MockedInterface<HTMLDivElement> extends Element
. To achieve this, it had to iterate through every property of HTMLDivElement and evaluate either
CalledWithMock<B, A> & T[K]
or
MockedInterface<T[K]> & T[K]
. Assuming it always assesses the left term initially, this could lead to recursive evaluations within each property and subproperty of HTMLDivElement before confirming assignability. If HTMLDivElement contains recursive definitions or numerous dependencies (both likely scenarios), the compiler may reach recursion limits.

Conversely, utilizing

T[K] & CalledWithMock<B, A>
or
T[K] & MockedInterface<T[K]>
allows the compiler to promptly recognize that each property K of
MockedInterface<HTMLDivElement>
is assignable to HTMLDivElement[K], establishing that
MockedInterface<HTMLDivElement>
itself is assignable to HTMLDivElement without delving further.

This premise may or may not accurately depict reality; for an authoritative answer, consider raising an issue on TypeScript's GitHub repository to inquire about it (after conducting a thorough search for existing issues).


Irrespective of which branch of the conditional type is selected, since you're intersecting with

T[K]</code, it's plausible to shift it entirely out of the conditional type:</p>
<pre><code>type MockedInterface<T> = T & {
    [K in keyof T]: T[K] & (T[K] extends (...args: infer A) => infer B
    ? CalledWithMock<B, A>
    : MockedInterface<T[K]>);
}

Moving the intersection outside the mapped type altogether won't alter outcomes while simplifying evaluation even more (e.g., something akin to

{[K in keyof T]: T[K] & F<T, K>}
is essentially equivalent to
{[K in keyof T]: T[K]} & {[K in keyof T]: F<T, K>}
, which is essentially comparable to
T & {[K in keyof T]: F<T, K>}
. So, incorporating modifications such as:

type MockedInterface<T> = T & {
    [K in keyof T]: T[K] extends (...args: infer A) => infer B
    ? CalledWithMock<B, A>
    : MockedInterface<T[K]>;
}

Each enhancement incrementally facilitates the compiler in evaluating

MockedInterface<T></code. Certainly, the prompt confirmation of <code>MockedInterface<HTMLDivElement>
being compatible with HTMLDivElement helps streamline the assessment process, sparing the need for recursive exploration down the mapped type.

Hence, my final inclination leans towards this version, where

MockedInterface<T> = T & ...
. Naturally, testing against your distinct scenarios is essential; analogous to cases (e.g., overloads) where X & Y may vary from Y & X, some contexts might warrant disparities between this transposition using T & versus the previous nested subproperty intersection iteration (for instance, if T acts as a function type on its own, {[K in keyof T]: T[K]} wouldn't be callable despite T being so). Tailoring your implementation to align with your specific requirements becomes imperative if such distinctions hold significance.

Access Playground code here

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

Is time-based revalidation in NextJS factored into Vercel's build execution time?

Currently overseeing the staging environment of a substantial project comprising over 50 dynamic pages. These pages undergo time-based revalidation every 5 minutes on Vercel's complimentary tier. In addition, I am tasked with importing data for numer ...

PrismaClient is currently incompatible with this browser environment and has been optimized for use in an unknown browser when performing updates

While attempting to update a single field in my database using server-actions and tanstackQuery, I encountered the following error message: Error: PrismaClient is unable to run in this browser environment, or has been bundled for the browser (running in ...

Ways to retrieve the most recent update of a specialized typing software

When attempting to run typings install in a sample project with the below typings.js file, I received a warning. How can we determine the latest version number and what does the number after the + symbol signify? { "globalDependencies": { "core-js ...

What is the best way to configure eslint or implement tslint and prettier for typescript?

In my React/Redux project, I recently started integrating TypeScript into my workflow. The eslint configuration for the project is set up to extend the airbnb eslint configurations. Here's a snippet of my current eslint setup: module.exports = { // ...

Guide on deactivating the div in angular using ngClass based on a boolean value

displayData = [ { status: 'CLOSED', ack: false }, { status: 'ESCALATED', ack: false }, { status: 'ACK', ack: false }, { status: 'ACK', ack: true }, { status: 'NEW', ack ...

During rendering, the instance attempts to reference the "handleSelect" property or method which is not defined

I've incorporated the Element-UI NavMenu component into my web application to create a navigation bar using Vue.JS with TypeScript. In order to achieve this, I have created a Vue component within a directory called "navBar," which contains the follow ...

Asynchronous handling of lifecycle hooks in TypeScript for Angular and Ionic applications

I'm intrigued by the idea of integrating TypeScript's async/await feature with lifecycle hooks. While this feature is undeniably convenient, I find myself wondering if it's considered acceptable to make lifecycle hooks asynchronous. After ...

import error causing an angular application to crash even with the module installed

Is there a possibility that an error is occurring with the import statement even though the syntax is correct and the required library has been installed? Could the issue lie within the core settings files, specifically the ones mentioned below (package.js ...

Troubleshooting the problem of redirecting a website to www on IIS 10 (Windows Server 2019)

I have a React website running on port 3000. Currently, the website can be accessed with and without the www prefix, causing duplicate SEO issues. I want to ensure that the website always redirects to https://www.pess.org.ua. web.config <?xml version=& ...

Creating a Custom Select Option Component with Ant Design Library

Is it possible to customize options in an antd select component? I have been trying to render checkboxes alongside each option, but I am only seeing the default options. Below are my 'CustomSelect' and 'CustomOption' components: // Cu ...

Error encountered: The combination of NextJS and Mongoose is causing a TypeError where it is unable to read properties of undefined, specifically when trying

Versions: Next.js 14.1 React 18 I am currently developing a profile section where users can update their profile information such as username, name, and profile photo. To achieve this, I have implemented a component that contains a form (using shadcn) to ...

What is the method for obtaining the return type based on the type of a generic function?

Within my api function, I utilize a parser function that is generic and typically returns the same type as its input. However, in some cases, this may be different for simplification purposes. When using the api function, I am able to determine the type t ...

The 'import.meta' meta-property can only be used with the '--module' set to 'es2020', 'esnext', or 'system'.ts(1343)

Whenever I attempt to utilize import.meta.url (as demonstrated in the Parcel docs), I am consistently met with the error message "The 'import.meta' meta-property is only allowed when the '--module' option is 'es2020', 'es ...

Concealing forms within an Angular 5 application

I'm currently working on displaying the terms of use on the initial screen along with two buttons. If the user clicks the accept button, they will be directed to the authentication form. However, if they click refuse, the "Refused Terms" screen will a ...

Is it possible for me to create an interface that is derived from a specific type?

Is there a way to define an interface in TypeScript where the keys are based on a specific type? For example: type FruitTypes = "banana" | "apple" | "orange"; interface FruitInterface { [key: string]: any; // should use FruitTypes as keys instead of str ...

What is the process for extracting the background color from a typescript file in Angular and connecting it to my HTML document?

My typescript array population is not changing the background color of my div based on the values in the array. I've attempted to set the background using [style.backgroundColor]="statusColor[i]", where statusColor is an array declared in my typescrip ...

Separating HTML content and text from a JSON response for versatile use within various sections of an Angular 2 application using Typescript

Hello there! I am currently utilizing Angular 2 on the frontend with a WordPress REST API backend. I'm receiving a JSON response from the service, however, the WP rest API sends the content with HTML tags and images embedded. I'm having trouble s ...

"What could be causing my React application to enter a never-ending re-rendering cycle when I incorporate

Currently, I'm working on a code to update the content of a previous post with image URLs received from the server. However, I'm facing issues with excessive re-renders due to my coding approach. Specifically, when converting the image URLs into ...

To implement a filter in MongoDB, make sure to specify a function argument before

Utilizing TypeScript, Node.js, Mongoose, and MongoDB in my project. I have a function that resembles the following: async function getAllBooks(title?: string, authorName?: string, sortBy?) { const books = await bookModel.find().sort(); return book ...

How can I display images stored locally within a JSON file in a React Native application?

Hey everyone, I'm currently facing an issue with linking a local image from my images folder within my JSON data. Despite trying various methods, it doesn't seem to be working as expected. [ { "id": 1, "author": "Virginia Woolf", " ...