TypeScript: “Type instantiation is excessively deep and possibly infinite” but only in a large codebase
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:
@types/history@4.7.3
@types/react@16.9.11
@types/react-router-dom@5.1.0
@types/recompose@0.30.7
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:
Related Issues:
About this issue
- Original URL
- State: open
- Created 5 years ago
- Reactions: 129
- Comments: 65 (8 by maintainers)
Commits related to this issue
- Fix Event Typing Error Due to Too Many Overloads (#5908) I'd optimistically increased the number of event overloads, but not consumed it. As part of bumping all packages it turns out we've hit an int... — committed to microsoft/FluidFramework by anthony-murphy 3 years ago
- 🤖 Merge PR #53513 [express-serve-static-core] optional parameters and `.`/`-` delimited parameters by @DetachHead * specify minimum ts version for template literal types * set minimum ts version fo... — committed to DefinitelyTyped/DefinitelyTyped by DetachHead 3 years ago
- feat(compilerOption): add instantiationDepthLimit and instantiationCountLimit Close #34933 — committed to jjangga0214/TypeScript by jjangga0214 3 years ago
- [Security Solution] Swap rule unions out for discriminated unions to improve validation error messages (#171452) **Epics:** https://github.com/elastic/security-team/issues/8058, https://github.com/e... — committed to elastic/kibana by marshallmain 7 months ago
- [Security Solution] Swap rule unions out for discriminated unions to improve validation error messages (#171452) **Epics:** https://github.com/elastic/security-team/issues/8058, https://github.com/e... — committed to pgayvallet/kibana by marshallmain 7 months ago
Guys, please show your interest in #44997 (thumb up!) to resolve the issue faster
Currently, the limitation thresholds are hard-coded, to
50
and5000000
.src/compiler/checker.ts
To make this configurable, I pushed a PR #44997, which makes those values be able to be set by
compilerOptions
that can be passed through CLI options ortsconfig.json
, like an example below.Though the PR is incomplete, just showing your interest would make its priority higher.
Thanks.
50 is really not that deep if you’re using fancy typescript inference… upping the limit could be pretty nice. I know TS Core in the past has been against inference but the TS community as a whole is using it more and more
Any updates on this issue?
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
andyarn tsc
to get the error (or somenpm
commands if you prefer).Hitting this issue. Using zod to merge several sub-schemas into a “complete” schema, resulting in the error. Hard-coding the resultant large schema works fine, but is more of a pain to maintain/less re-usable
That does not solve it for me 😕
I had similar issue with zod, instead using z.merge or z.extend I used spread operators
This solved the issue, at least for now. 😃
TypeScript team - is there any thought of putting in some syntax that could help guide the compiler during inference beyond a heuristic? For libraries that do schema translation this is going to be a continual headache. Perhaps something like a
resolve
keyword to tell the compiler to fully compute a type resolution, cache it and from then on treat the type as if it were declared by the user explicitly? Is the compiler already doing this as it’s resolving types?Intellisense could also benefit from this as a lot of mapped types end up being unreadable as they show complicated expansions. If the
resolve
keyword were used, intellisense could display the simplest form of the type declaration and not continue expanding when it encounters a “resolved type.” From my experience these deeply recursive types coincide with very generic API boundaries 90% of the time, so it’s a natural fit.So what is the fix here. Nothing yet ? and we should not update to latest ts ? or refactor application code within limits ?
So to anyone who lands here and is getting this error in Node with express.
Our pipeline suddenly broke. No major code changes on our part… and it appears npm updated some packages
4.0.2
to4.3.4
(i think)14.14.22
to14.14.44
4.17.11
to4.17.12
4.17.19
to4.17.22
Running
npx tsc
was after these updates was causing thedeep and infinite
error. And very strangely… for instance:and even more odd… is that this STILL throws the error
and so I started playing around:
having
:userOpportunity
anywhere in my route was throwing the error. butuserOpportunity
,:userOpp
,something else
etc would be fine.I had three routes throwing errors… all of them were acting similar but with different words… 2 routes had
:userOpportunity
it and the third was complaining about:recommendationId
This made me think that the express types were being naughty. I started rolling things back slowly. Thus, I added
@type/express-serve-static-core
to my package.json (it was just a dependency) and forced it back to4.17.21
and now the TS errors are gone.On a side note… this only seems to fail in a unix environment for some reason.
UPDATE looks like the package will get fixed soon: https://github.com/DefinitelyTyped/DefinitelyTyped/commit/6973d2cc7f0ac4601f449273e2b0ff8790fdcf72#r52861403
What’s the likelihood of this issue getting addressed in the upcoming 4.2.0 release? Is someone actively working on this issue?
Same issue with styled components extending rmwc components:
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
in the meantime, one can use patch-package to create a per-project patch to implement the temporary fix:
edit
node_modules/typescript/lib/tsc.js
: search for5000000
orinstantiationCount
and increate both theinstantiationDepth
andinstantiationCount
limitsrun
node_modules/.bin/patch-package typescript
(viayarn patch-package typescript
ornpx patch-package typescript
to generate the patchshould end up something like this in the
patches/
folder:make sure to always apply the patch (e.g. after installation) by following patch-package’s docs for this purpose (also note this).
if using typescript project references, beware of hoisting of typescript itself - it didn’t work for me and resolved to some random package’s typescript install - do this in the root
package.json
(thenohoist
):Note: after some testing, it seems there’s no value that’s enough at least for my use case, even though it is not infinitely recursive…
P.S. If a project is using something like
babel-loader
through webpack (e.g. CRA), I do not know how to configure the tsc executable there or whatever other changes would be needed.If someone finds this - please let me know.
in
src/compiler/checker.ts
on line 13208 (in v3.7.2) there is the following code: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.
The same issue I have using zod, did you find any workarounds for it?
The depth fix mentioned in this article resolved my problem, even when sticking to an initial depth of 1 https://www.angularfix.com/2022/01/why-am-i-getting-instantiation-is.html
I would love to know why 😄
tsconfig
I hope to adjust the limit by tsconfig. How do you think?
This is definitely a problem for us, as we make an intensive use of type inference, and the type sometimes works and sometimes it produces this error, it’s not recursive, maybe simply too complex/deep. Also an indication about where (in the nested types) this error happens would be useful, as we cannot determine if it is really an error on our side or just TypeScript limitations
Here is a small snippet that causes the issue. It is recursive but not using React.
The strange thing is if I remove the map and change it to a forEach, this works fine. As in:
has no errors.
I’ve noticed that strict mode fixes it. I just added
"strict": true
totsconfig.json
and it solvedThat’s a lot of properties ._.
One point in favor of upping the limit besides the weeks of my life wasted that I’ve spent battling these problems, is that I actually could easily speed up the tamagui types by 2x, except that it starts hitting complexity limits.
So in fact the complexity limit is making things slower in practice rather than faster.
Ran into the problem implementing a recursive mapper type:
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:
Problem with 3.7.2:
Same issue, using
keyof JSX.IntrinsicElements
type.I get this issue right now but only in watch mode and only after file changes
Very nice!!
I have a similar problem.
I was using merge, pick and extend. After seeing your comment, I simplified the schema using the spread. However, the error continues to appear, probably the cause is now the pick method.
Any ideas on how to resolve it?
Update
The problem was in
additionalAttributes
.I believe that what causes the error is the fact that the field is a jsonb in the database, receives a type transformation from drizzle-zod, and is then extended in another schema, generating many recurring type instantiations.
What I did was remove the additionalAttributes from the pick and redefine it explicitly in the
getInsuredItemsResponse
schema.I’m getting it on the following snippet:
I was attempting to get the public props as constructor arguments type.
We have a similar issue with this code (roughly):
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
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
https://github.com/microsoft/TypeScript/issues/29511#issuecomment-458620791
I agree with this part,
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