microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
101.23k stars 12.52k forks source link

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

Open karol-majewski opened 5 years ago

karol-majewski commented 5 years ago

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

Search Terms:

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 ```ts 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(candidate: T | null | undefined): candidate is T; declare function isString(value?: any): value is string; type ObjectOmit = Omit; type OnClick = NonNullable['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; }; 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 = ( ComposedComponent: React.ComponentType, ) => { type PassThroughComposedProps = ObjectOmit; type OwnProps = InputProps & RouteComponentProps & PassThroughComposedProps; type Props = OwnProps; const displayName = `CreateModalLink(${getDisplayName(ComposedComponent)})`; const ModalLink: React.FunctionComponent = ({ 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 ; }; ModalLink.displayName = displayName; return withRouter(ModalLink); }; type Props = React.ComponentPropsWithoutRef<'button'> & Required, 'type'>>; /** * This one errors. */ declare const Button: React.FunctionComponent>; /** * This one works. */ // declare const Button: React.FunctionComponent; const EnhancedButton = enhance(Button); /** * Type instantiation is excessively deep and possibly infinite.ts(2589). */ () => ; ```

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:

koteisaev commented 3 years ago

@koteisaev See my comments above. Its an issue with the @type/express-serve-static-core

I had to change parameter name to a different yet similar for that URL, as a hotfix. Lets hope fix you mentioned will be enrolled soon to NPM,

Ricardo-Marques commented 2 years ago

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 😄

ramblehead commented 2 years ago

It is possible to avoid Subj. issue in certain use cases by utilising UnionToIntersection metafunction as shown in the following SO answer:

https://stackoverflow.com/a/74292294/5000057

thelinuxlich commented 1 year ago

What about raising the instantiationDepth limit to 500?

iway1 commented 1 year ago

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

MohammadKurjieh commented 1 year ago

Any updates on this issue?

Xriuk commented 1 year ago

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

Xriuk commented 1 year ago

Could this also be a problem related to inheritance? We use inheritance a lot, so we have long inheritance chains

rossPatton commented 1 year ago

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

OnkelTem commented 1 year ago

Was also experimenting with Zod schemas. In my case, this would negate all my efforts to validate the API using Zod.

Here is a sandbox, where I try to make a Zod union of basic object keys.

In addition to showing the error message, an unexpected value of unknown starts to appear at the beginning of the tuple. I have no idea where it comes from tho.

image

andelkocvjetkovic commented 1 year ago

I had similar issue with zod, instead using z.merge or z.extend I used spread operators

export const addNewProspectSchema = z.object({
  ...generalSchema.shape,
  ...landSchema.shape,
  ...pvResourceSchema.shape,
  ...windResourceSchema.shape,
  ...productionSchema.shape,
  ...gridSchema.shape,
  ...
});

type AddNewProspectSchema = z.infer<typeof addNewProspectSchema>;

This solved the issue, at least for now. :)

ryan-williams commented 1 year ago

In case it helps someone, I think I was triggering this error attempting to combine some Promise<JSX.Element>s with Promise<ReactNode>s in one array:

const elems: Promise<JSX.Element>[] = []
const nodes: Promise<ReactNode>[] = []
const all = Promise.all([ ...elems, ...nodes ])

This fails to compile:

Type error: Type instantiation is excessively deep and possibly infinite.

  112 |     const elems: Promise<JSX.Element>[] = []
  113 |     const nodes: Promise<ReactNode>[] = []
> 114 |     const all = Promise.all([ ...elems, ...nodes ])
      |                 ^

In my case, both arrays could be refined to Promise<JSX.Element>s, and that fixed the error. but in general the other direction ("widening" JSX.Elements to ReactNodes) should always work.

Amorim33 commented 1 year ago

I had similar issue with zod, instead using z.merge or z.extend I used spread operators

export const addNewProspectSchema = z.object({
  ...generalSchema.shape,
  ...landSchema.shape,
  ...pvResourceSchema.shape,
  ...windResourceSchema.shape,
  ...productionSchema.shape,
  ...gridSchema.shape,
  ...
});

type AddNewProspectSchema = z.infer<typeof addNewProspectSchema>;

This solved the issue, at least for now. :)

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?

const getMonthlyInsuredItemSchema = monthlyInsuredItemSchema.pick({
  month: true,
  processedAt: true,
});

const getInsuredItemSchema = insuredItemSchema.pick({
  externalId: true,
  additionalAttributes: true,
  remainingInstallments: true,
  totalInstallments: true,
  installmentAmount: true,
});

const getInsuredPersonSchema = insuredPersonSchema.pick({
  name: true,
  cpf: true,
  birthdate: true,
  gender: true,
});

const getInsuredItemsResponse = z.array(
  z.object({
    ...getMonthlyInsuredItemSchema.shape,
    ...getInsuredItemSchema.shape,
    insuredPerson: getInsuredPersonSchema,
  }),
);

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.

natew commented 10 months ago

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.

aleczratiu commented 9 months ago

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

The same issue I have using zod, did you find any workarounds for it?

Charismara commented 6 months ago

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

The same issue I have using zod, did you find any workarounds for it?

I got the same issue. Is there a workaround for this?

Threebow commented 5 months ago

What I interpret from this issue, and the comments within, is that that once your codebase/typing logic grows enough in size or complexity to an arbitrary point that TypeScript is unhappy with, you are no longer able to compile the project.

So, am I correct to conclude from this, that TypeScript enforces an upper limit on the size of project that you are able to develop with it, and any larger/more complex projects are entirely unsupported by the compiler?

Somebody please correct me if I am wrong, but the fact that you can spend years working on a project and one day hit an arbitrary "complexity limit" seems grossly incorrect and inappropriate for a tool of this breadth in the industry.

If this is indeed the case, and I am not mistaken, then:

  1. What is the accepted "fix" to spending years on a project just to ultimately hit this limit and be faced with a compiler that outright refuses to compile the project? Are we doomed to throw the project out and rewrite the whole thing in a different stack, or put an indefinite pause on development until this issue is fixed?
  2. Where does TypeScript make this clear enough to a potential user looking to adopt TypeScript, to where they will choose a different tool for a project if they know their project will grow to this arbitrary complexity limit, to completely avoid wasting years of their and their teammates' time?

Open to input and corrections from others who have bumped into this.

natew commented 5 months ago

I think this really needs an option to just increase the size.

The reason this is such a big problem is that - only the biggest projects are affected.

IE, you've spent the most amount of effort investing into TypeScript, to the point where your types are really large. But now it actually comes around and bites you, and often in a way you just can't control. If a library you use everything in your project has complex types and you've used it 2k times across your codebase, and on the 2001 time it suddenly craps out, you're looking at losing type safety on one of your most critical/used pieces of the stack.

The second reason I think we need a way around this is because the warning actually doesn't correlate with performance. I've landed big improvements to performance that caused this warning to occur. So it actually is a safeguard that is working against you oftentimes.

Threebow commented 5 months ago

@natew

I think this really needs an option to just increase the size.

TS has stated five years ago in 2019 that they will not expose this option to users because it's an implementation detail of an internal TypeScript mechanism that they may choose to eliminate in the future.

Another person has said the same thing, over a year later, in 2020.

These decisions seem a little short-sighted in hindsight, given that it's been five years, and that they have seemingly forgotten about this mechanism in its entirety.

IE, you've spent the most amount of effort investing into TypeScript, to the point where your types are really large. But now it actually comes around and bites you, and often in a way you just can't control.

I strongly resonate with this. It feels like a kick in the groin to hear that your usage of the system is outside of what they support, after investing so heavily in the ecosystem and the language itself.

If a library you use everything in your project has complex types and you've used it 2k times across your codebase, and on the 2001 time it suddenly craps out, you're looking at losing type safety on one of your most critical/used pieces of the stack.

This issue is only going to start popping up more and more, as more people start to get into codegen of complicated type systems.

tran-simon commented 5 months ago

I get this issue all the time beacuse I'm using json-schema-to-ts

It's documented in their FAQ, but there doesn't seem to be any fix

Xriuk commented 5 months ago

What I interpret from this issue, and the comments within, is that that once your codebase/typing logic grows enough in size or complexity to an arbitrary point that TypeScript is unhappy with, you are no longer able to compile the project.

More or less, the main problem with increasing type instantiation depth appears to be that some web instances of TypeScript just crash, because TypeScript is meant to be running everywhere apparently. This is frustrating because at least for me TypeScript just compiles code into JavaScript, so no matter the complexity and depth of types, the resulting code should always be the same. That's why we resorted to using a custom TypeScript version with these values increased freely.

Threebow commented 5 months ago

some web instances of TypeScript just crash, because TypeScript is meant to be running everywhere apparently

Now I am curious if there is a limit to where TypeScript crashes simply because the compilation time exceeds an arbitrary "too long to compile" point. It seems strange that a long compilation time would force a crash, as opposed to a simple freeze.

That seems like a separate issue in its entirety, unrelated to this one. I'm also not really sure who's compiling a project with 2000+ Zod usages in their browser at runtime...