Typescript: “Type instantiation is excessively deep and possibly infinite” but only in a large codebase

Created on 6 Nov 2019  ·  26Comments  ·  Source: microsoft/TypeScript


TypeScript Version: 3.7.2, 3.8.0-dev.20191102 (worked in 3.6)


Search Terms:

  • Type instantiation is excessively deep and possibly infinite.ts(2589)
  • Mapped types
  • Generics
  • Conditional types

Code

Note: this issue manifests itself only in our codebase. When you run the same code in TypeScript Playground, it seems to be working fine.

The snippet is hardly minimal, but I reduced it as much as I could. I recorded a video where exactly the same code yields an error different than the one in TypeScript Playground. I tried with two versions of TypeScript: 3.7.2 and 3.8.0-dev.20191102. It worked correctly with 3.6.

Since @sheetalkamat and @DanielRosenwasser have access to our repository, you're welcome to have a look at this PR. Copy-paste the code below anywhere in the project to see the error.

The versions of types used:

Note: Interestingly enough, if you change:

- declare const Button: React.FunctionComponent<Omit<Props, never>>;
+ declare const Button: React.FunctionComponent<Props>;

it works again despite the fact Omit<Props, never> should be the same as just Props.

Source code

import { History } from 'history'; // "4.7.3"
import * as React from 'react'; // "16.9.11"
import { LinkProps, RouteComponentProps, withRouter } from 'react-router-dom'; // "5.1.0"
import { getDisplayName } from 'recompose'; // "0.30.7"

declare function isDefined<T>(candidate: T | null | undefined): candidate is T;
declare function isString(value?: any): value is string;

type ObjectOmit<T extends K, K> = Omit<T, keyof K>;

type OnClick = NonNullable<React.ComponentProps<'button'>['onClick']>;

type OnClickProp = {
  /** If there is a custom click handler, we must preserve it. */
  onClick?: OnClick;
};

type ProvidedProps = OnClickProp;

type InputProps = OnClickProp & {
  /** Note: we want this helper to work with all sorts of modals, not just those backed by query
   * parameters (e.g. `/photo/:id/info`), which is why this must accept a full location instead of a
   * `Modal` type.
   * */
  to: Exclude<LinkProps['to'], Function>;
};

const buildClickHandler = ({
  to,
  onClick,
  history,
}: InputProps & {
  history: History;
}): OnClick => {
  const navigate = () => {
    // https://github.com/Microsoft/TypeScript/issues/14107
    isString(to) ? history.push(to) : history.push(to);
  };

  return event => {
    [onClick, navigate].filter(isDefined).forEach(callback => callback(event));
  };
};

/** See the test for an example of usage. */
export const enhance = <ComposedProps extends ProvidedProps>(
  ComposedComponent: React.ComponentType<ComposedProps>,
) => {
  type PassThroughComposedProps = ObjectOmit<ComposedProps, ProvidedProps>;
  type OwnProps = InputProps & RouteComponentProps<never> & PassThroughComposedProps;
  type Props = OwnProps;

  const displayName = `CreateModalLink(${getDisplayName(ComposedComponent)})`;

  const ModalLink: React.FunctionComponent<Props> = ({
    to,
    onClick,

    history,
    // We specify these just to omit them from rest props below
    location,
    match,
    staticContext,

    ...passThroughComposedProps
  }) => {
    const clickHandler = buildClickHandler({ to, onClick, history });

    const composedProps: ComposedProps = {
      // Note: this is technically unsafe, since the composed component may have props
      // with names matching the ones we're omitting.
      // https://github.com/microsoft/TypeScript/issues/28884#issuecomment-503540848
      ...((passThroughComposedProps as unknown) as PassThroughComposedProps),
      onClick: clickHandler,
    } as ComposedProps;

    return <ComposedComponent {...composedProps} />;
  };

  ModalLink.displayName = displayName;

  return withRouter(ModalLink);
};

type Props = React.ComponentPropsWithoutRef<'button'> &
  Required<Pick<React.ComponentPropsWithoutRef<'button'>, 'type'>>;

/**
 * This one errors.
 */
declare const Button: React.FunctionComponent<Omit<Props, never>>;

/**
 * This one works.
 */
// declare const Button: React.FunctionComponent<Props>;

const EnhancedButton = enhance(Button);

/**
 * Type instantiation is excessively deep and possibly infinite.ts(2589).
 */
() => <EnhancedButton></EnhancedButton>;


Expected behavior:

I should get a proper error about missing properties (not the one about type instantiation):

Type '{}' is missing the following properties from type 'Readonly<Pick<OwnProps, "form" | "style" | "title" | "onClick" | "to" | "key" | "autoFocus" | "disabled" | "formAction" | "formEncType" | "formMethod" | "formNoValidate" | "formTarget" | ... 252 more ... | "onTransitionEndCapture">>': to, type(2739)

Actual behavior:

I'm getting this:

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

Playground Link:

Playground Link

Related Issues:

  • #32735
  • #34850
Bug Big Unions Rescheduled

Most helpful comment

I ran into this with xstate. The types work with 3.7.5 but upgrading to 3.8.2 gives error TS2589: Type instantiation is excessively deep and possibly infinite.

I made a quick gist to reproduce the issue, hope it helps: https://gist.github.com/qtiki/5f97233f609e516b5439e926514e85d9

Download the gist and run yarn and yarn tsc to get the error (or some npm commands if you prefer).

All 26 comments

| ... 252 more ... |

That's a lot of properties ._.

@AnyhowStep That's correct. The component in question mixes-in the props defined by JSX.IntrinsicElements['button']. There is a lot going on, and it is complicated, but it's a regression nonetheless because it worked in TypeScript 3.6.3.

We have the same issue in one of our larger projects. Works fine with 3.6.4 but not with 3.7.2

Using type keyof JSX.IntrinsicElements

Same issue, using keyof JSX.IntrinsicElements type.

in src/compiler/checker.ts on line 13208 (in v3.7.2) there is the following code:

function instantiateType(type: Type | undefined, mapper: TypeMapper | undefined): Type | undefined {
    if (!type || !mapper || mapper === identityMapper) {
        return type;
    }
    if (instantiationDepth === 50 || instantiationCount >= 5000000) {
        // We have reached 50 recursive type instantiations and there is a very high likelyhood we're dealing
        // with a combination of infinite generic types that perpetually generate new type identities. We stop
        // the recursion here by yielding the error type.
        error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite);
        return errorType;
    }
    instantiationCount++;
    instantiationDepth++;
    const result = instantiateTypeWorker(type, mapper);
    instantiationDepth--;
    return result;
}

So the value is hard-coded. Perhaps a configuration option to change this might be warranted. As a workaround for now, you could try upping this limit and rebuilding the compiler.

There have been proposals made to increase the limit. They've been rejected because it's seen as an "implementation detail" that should not be exposed.

PR: https://github.com/microsoft/TypeScript/pull/29602


Comment by @ahejlsberg

I'm very reluctant to expose the instantiation depth as a configurable parameter as it is an implementation detail that could change. Plus it is next to impossible to explain and reason about what value it should have.

https://github.com/microsoft/TypeScript/issues/29511#issuecomment-458620791

I agree with this part,

it is next to impossible to explain and reason about what value it should have.


If you're writing library code, you should not be messing with this limit. If you're writing application code and can force everyone on your team to use a hacked version of TS...

You can go ahead and hack the .js files for tsc and tsserver. Then, use https://www.npmjs.com/package/patch-package and commit the patch files

@Stianhn @rista404 Have you managed to reproduce this issue using a more reduced example? If what we're dealing with is a valid use case, then understanding it could help us come up with a heuristic for when type instantiation limit should not be applied.

I agree that messing with the internals leads nowhere — after all, you need to draw the boundary somewhere.

As far as I know, this limit is not something new. It existed before, yet somehow this code stopped working. I wonder what changed that directly influenced our use cases.

git bisect? =x

Can you grant @weswigham permission to the repository as well?

@DanielRosenwasser Yes! Invite sent @weswigham.

Another example in https://github.com/elastic/kibana/pull/47188/checks?check_run_id=303150613 where @timroes is trying to upgrade TS to 3.7.2:

x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/heap_memory/index.ts:58:10 - error TS2589: Type instantiation is excessively deep and possibly infinite.

  58   return fetchAndTransformMetrics({
              ~~~~~~~~~~~~~~~~~~~~~~~~~~
  59     setup,
    ~~~~~~~~~~
... 
  70     additionalFilters: [{ term: { [SERVICE_AGENT_NAME]: 'java' } }]
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  71   });

Related pull request:
https://github.com/elastic/kibana/pull/47188

Related file:
x-pack/legacy/plugins/apm/server/lib/metrics/fetch_and_transform_metrics.ts

edit: this issue will be fixed with the aforementioned pull request. We've added an explicit intermediary type (https://github.com/elastic/kibana/pull/47188/commits/ef912fc12ab95bca7b117a66cadabecc84d9a16a) that resolves the issue for 3.7.

Worth noting that if you're upgrading from TS 3.5, something about how constraintDepth works was changed that can cause the max depth error, for code that was previously working fine (and nesting 100+ layers deep).

https://github.com/microsoft/TypeScript/issues/33541


I'm bringing this up because it looks like @dgieselaar is migrating from TS 3.5


I also have a todo on my personal project to investigate this, https://github.com/AnyhowStep/tsql/issues/25

But I'm not focusing on TS 3.7 support yet

We have a similar issue with this code (roughly):

export type IconProps = TestProps & SizeProps & SpaceProps & DisplayProps & ColorProps<any>

// these are more than 450 props
export type ReactSvgProps = Omit<SVGProps<SVGSVGElement>, 'height' | 'width'>

export const createSvgIcon = (Component: FunctionComponent<ReactSvgProps>) => styled(Component)<IconProps>({display: 'inline-block', flex: '0 0 auto' }, compose(size, space, display, color))

// here we get the error
const MyIcon = createElement(Icon, { color: 'blue', marginRight: 'xs', marginLeft: '-xs'  })

I have a similar issue with grommet-icons v4.4.0:

If I use the trash icon directly, all fine. If I wrap it with styled from styled component, I get the same message. No problem until typescript 3.5.2.

No Problem:

import { FormTrash } from 'grommet-icons';
export const AdProgramView = (props: PropType) => {
    return (
        <Container>
            <h3>
               {props.headline}
                <FormTrash onClick={deleteIntentCallback} />
            </h3>
        </Container>
    )
}

Problem with 3.7.2:

import { FormTrash } from 'grommet-icons';
const FormTrashStyled = styled(FormTrash)`
  vertical-align: bottom;
  cursor: pointer;
`
export const AdProgramView = (props: PropType) => {
    return (
        <Container>
            <h3>
               {props.headline}
                <FormTrashStyled onClick={deleteIntentCallback} />
            </h3>
        </Container>
    )
}

Same issue with styled components extending rmwc components:

import * as React from 'react'
import styled from 'styled-components'
import { Button } from '@rmwc/button'

const ButtonStyled = styled(Button)`
`

export function ConnectionSetting() {
  return <React.Fragment>
    <ButtonS />
  </React.Fragment>
}

So what is the fix here. Nothing yet ? and we should not update to latest ts ? or refactor application code within limits ?

I've noticed that strict mode fixes it.
I just added "strict": true to tsconfig.json and it solved

I just added "strict": true to tsconfig.json and it solved

That does not solve it for me :/

I ran into this with xstate. The types work with 3.7.5 but upgrading to 3.8.2 gives error TS2589: Type instantiation is excessively deep and possibly infinite.

I made a quick gist to reproduce the issue, hope it helps: https://gist.github.com/qtiki/5f97233f609e516b5439e926514e85d9

Download the gist and run yarn and yarn tsc to get the error (or some npm commands if you prefer).

Here is a small snippet that causes the issue. It is recursive but not using React.


export declare type NoMethods<T> = Pick<T, Exclude<{
    [P in keyof T]: T[P] extends Function ? never : P;
}[keyof T], undefined>>;

export type PC<T, TChild=T> = {
    [K in keyof NoMethods<TChild>]-?: PC<T, Exclude<TChild[K], undefined>>
} & {get(): string}


function test<T>(values: PC<T, unknown>[]): string[] {
    return values.map(curr => curr.get()); //ERROR HERE
}

The strange thing is if I remove the map and change it to a forEach, this works fine. As in:

function test<T>(values: PC<T, unknown>[]): string[] {
    const results: string[] = [];
    values.forEach(curr => {
        results.push(curr.get());
    });
    return results;
}

has no errors.

I'm getting it on the following snippet:

export class BaseConfig<T extends typeof BaseConfig> {
  public constructor(private readonly environment: Pick<InstanceType<T>, keyof T>) {
    // ...
  }
}

I was attempting to get the public props as constructor arguments type.

Ran into the problem implementing a recursive mapper type:

export interface Route {
  path: string;
  label: string;
  icon: React.ReactElement;
}

export type Routes = Record<string, Route>;

export interface NestedRoute extends Route {
  children: NestedRoutes;
}

export type NestedRoutes = Record<string, NestedRoute>;

export type GetNestedRouteKey<R extends NestedRoutes> = {
  [key in keyof R]:
    | key
    | keyof R[key]['children']
    | GetNestedRouteKey<R[key]['children']>;
}[keyof R];

// * Example
type A = {
  ideas: {
    path: 'ideas';
    label: 'Discover';
    icon: ReactElement;
    children: {
      create: {
        path: 'create';
        label: 'Create';
        icon: ReactElement;
        children: {};
      };
      my: {
        path: 'my';
        label: 'My Ideas';
        icon: ReactElement;
        children: {
          published: {
            path: 'published';
            label: 'Published';
            icon: ReactElement;
            children: {};
          };
        };
      };
    };
  };
};
type B = GetNestedRouteKey<A>; // "ideas" | "create" | "my" | "published"

export type FlattenRoutes<R extends NestedRoutes> = Record<GetNestedRouteKey<R>, Route>; // Type instantiation is excessively deep and possibly infinite.

I get this issue right now but only in watch mode and only after file changes

Got the issue with the latest discord.js release (12.3.0) and ts-auto-mock (2.3.4).
More details on the ts-auto-mock issue.

I have isolated an example of hitting this error when trying to provide types for a function that deals with async iterables. Error is only hit when the function implementation is available and replacing the implementation with a declaration removes the problem even though the types have to be instantiated all the same.

The recursive type looks like this.

type ZipResult 0: ZipResult, Prepend<(InferIteratorValue> | undefined), Result>>,
1: Reverse
}[Length extends 0 ? 1 : 0];

The input array that instantiates this type is two elements long, so, perhaps naively, I would expect the recursion depth to be 6 (3 for recursion into ZipResult itself and 3 for recursion into Reverse). Nowhere near the limit of 50 mentioned earlier in the thread.

Code is available in the playground:

https://www.typescriptlang.org/play?target=6&jsx=0&ts=4.0.2#code/PQKhCgAIUgVALAlgZ0gYwPYBMCmlcC2GAdsgC4BOAhmTqlZAESwCeADnoqWVcWYjUQlIKSDgAeaOskQA3HABsW+HDjaReWSGwzIZAIyUjiAMy6JajMRQoYKxyLIAsAOgAMLgExQYPuPDwAcxxiHGpaLQApAGV0bDwKAFdSOJscNDIlFz8EPDJ2OkhkeAwAd2IHMgDIMgwMBX42VAU6gGtIAHkAaWzofzwSIzZbQxwCEVQqvDDbCl7fPoBJMkgCKmV9BMUaHC1ayESZYkCNCqpkFmI0SGDQ8LtIEwepkQI2BTGQnn4SAH5-vwAJTGGFkXBOL0Qbw+BC+gmEmkgFDUCioaHBIhWpQs8FOYgAjok5FQPnwVGhUeEhBUABQSKRsFZPewvEAASjECihXB2k2qMzsPh8wHA4HyHEgi1MYWWYRodgAaiTEjgADywAB8kAAvFBIPrYGJxLRiFhULLqKNVVwTGFIAq3Frfva3JAAFx6g1Gk1myW0e4Ua3S+wKgCMTvtofdnrg3pCvoAghcrhaqFabXaFZ4I1no-qvRIfagk5c0KnaoGMyGAMw56t5-OheQUADcovFeAAMiFAlV1XHTahkq1iGViABtAC6Wu1cHHAHJSb34PPJ22xQVIAAJHBULD9wvxofEEdjqcz2OHweQcfD0flAA0kBcL7vZ8nk8gztg47cn7dkBNmE64dnAVCIAoB7GkeBwnveE7TjqMbAMAKijgAtFyfDoYggSjsigEYOhEjvIg6JkOhvAsDGNI0i+LhkABsActqWpvuUHJXr6tGMacLBPvRPAQQBVYuixbFwWOHK-DGDoNvqQGtu2m4AArIhwpqqgAok+hpccep7lOeSH5pAtEBHuAE6c+L5UBQgTIEx4mwYZxCcdB160fRdkOSJwZiTqEmudJLrunAIGbsCzbIGqekeb67EIU+HQDglklGZ+s7GbOADeMZuABUVhDF6rgZBmpPmpajxqqO57uqGrJRqGoxqGAEdOAAC+47dscfaaqlqCus6UYAX+EUSgAWogbDAsgiQNFBRYufBU5PnNC0rPpK3vjqN6Ibl+UAdNs10JtpUQQ1lXqTVNJSraFDloqypqnV+6alqAA+sG4GYoRYGy61nQ0zUPq1hU4NFaobSDXU9T2-Vattw2QKNkDjaKKGQFpgKAh0gJMZuXDkLw-DwhUoj0tIciKMouBqKcWg6HoiCGMoNrmLQ4BY7gGFYRRuH4XgGEkVy5GUcQLDoQAIos0QJgAQp2WnS+A5ylo8yQZNSMAAF4zUtMGJeedEvhYcqjI5cBsgBJYpv6aYfKqJ0w2QDVanlpk8yERH8zheF2MLRGi2RFgS9RpkfCs5sBqgs4x47dAuGsbA0gnoyBWZ6cfBo9CS2y47RCwBD6PULjq-bcoVpONJsmybaRzg0dkHHZlUKU4ErGpGAECgODlwoChpw7FbIHXydUKnFiZzSOX4CQOAASYJIxU+CcViJKydXXDf5lHSJjOBxAYvHLcuEuVS7-qpRIDnNKUCqHKe6Ze9Nwf80NHtU5Xy-zJmfv08MAmExGPSAz8X4v0QMA4eLgsALyfjGCBEDkQfzIC4Nghx4A0mSL9Lgux66IKQZ1TkMUwGEKQa-FYaBEhpDJLOdundMQuHXnYFwoRjS1x-hQ0yUCzLUNoWguBoQEHcNEUwoReBZwPxwFwsRpl0LoWRGscwxxZGiM6uQ7hKDNroMwTSfhyI+AuFkC9AhciNEUIsUg3hNIlFH3BCIsR3s+ZcAFgHAiItxCkXFlRTRpkWCIEUFobRn9zh8TUZAYhihSHgK0U3GhxAIlWP1BojR3NULAneGiDEkJoSfD4OTSA2Iqg1CQKgXAFI7KFKUaCQoLwBQUHSRUykeATBax+BUfWbBDbXmNtOU25d7JW2YrbZMZYHZWhdsDN2H11z73EHtLpNJxyMCoIwJ8jB9DrKYGgRgk4nzjlDA+TwD5qyTgIeAIAA

Was this page helpful?
0 / 5 - 0 ratings

Related issues

manekinekko picture manekinekko  ·  3Comments

seanzer picture seanzer  ·  3Comments

siddjain picture siddjain  ·  3Comments

uber5001 picture uber5001  ·  3Comments

fwanicka picture fwanicka  ·  3Comments