sinclairzx81 / typebox

Json Schema Type Builder with Static Type Resolution for TypeScript
Other
4.98k stars 157 forks source link

TS 4.5: Type instantiation is excessively deep and possibly infinite. #120

Closed gmaclennan closed 2 years ago

gmaclennan commented 2 years ago

I know this has already had a quick fix via #119 but with @sinclair/typebox@0.20.6 I still get this error for this very basic code:

import { Static, Type as T } from "@sinclair/typebox";
import got from "got";

const MySchema = T.Object({
  name: T.String(),
});

type MyType = Static<typeof MySchema>;

async function getThing(id: string): Promise<MyType> {
  const url = `https://example.com/myThing.json`;
  return await got(url).json();
}

async function getAllThings(ids: string[]): Promise<MyType[]> {
  return await Promise.all(ids.map((id) => getThing(id)));
}

Error returned:

> tsc --noEmit

index.ts:16:16 - error TS2589: Type instantiation is excessively deep and possibly infinite.

16   return await Promise.all(ids.map((id) => getThing(id)));
                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Minimal repro here: https://github.com/gmaclennan/typebox-error-repro

Error running on CI: https://github.com/gmaclennan/typebox-error-repro/runs/4251888078?check_suite_focus=true

For what it's worth, I checked with other version of Typebox, and this code compiles with v0.11.0 but fails on any later version including v0.20.6

stevejhiggs commented 2 years ago

I also have issues with this even in 0.20.6. My repro is at https://replit.com/@SteveHiggs/GrumpyQuizzicalFields

sinclairzx81 commented 2 years ago

@gmaclennan @stevejhiggs Thanks for reporting. Just taking a deeper look into this now. It looks like there's been fairly significant changes in 4.5. I'm guessing this is probably related to the following https://github.com/microsoft/TypeScript/pull/46429.

I'll start looking at a patch within the next few days. Cheers S

gmaclennan commented 2 years ago

Thanks @sinclairzx81. Sticking to Typescript 4.4 keeps everything working for now :)

StCatfish commented 2 years ago

The simplest build I came up with: Versions:

{
    "@sinclair/typebox": "0.20.6",
    "typescript": "4.5.2"
 }
import {
  Static,
  TSchema,
} from '@sinclair/typebox';

export function validate<ResponseSchema extends TSchema>(
  data: Static<ResponseSchema>
): Static<ResponseSchema> {
  return data;
}

tsc yields an error:

index.ts:9:10 - error TS2589: Type instantiation is excessively deep and possibly infinite.

9   return data;
           ~~~~

Found 1 error.
sinclairzx81 commented 2 years ago

@gmaclennan Have published an update on @sinclair/typebox@0.21.0 which should resolve these issues. The update reinstates the constructor and function arguments, as well as applies a few upstream updates I had been meaning to look at. I have dropped a few notes on the changelog which you can read about here.

These updates are fairly sweeping type inference changes to accommodate these new 4.5 constraints, but these updates should also work in previous revisions of the compiler, have tested back to the minimum version of TS 4.3.5 and things should be ok.

Let me know if you run into any problems Cheers

stevejhiggs commented 2 years ago

Can confirm that 0.21.0 fixes all the issues I was having. Amazing work, thanks!

leoyli-headsup commented 2 years ago

Amazing, much appreciated!

sinclairzx81 commented 2 years ago

Cheers everyone :) Will close off this issue. Thanks for letting me know!

ayZagen commented 1 year ago

I know this issue is old and I don't have problems using with typescript 5 but this error happens when I try to rollup types with api-extractor which is using bundled ts with version 4.8.4.

@sinclairzx81 Do you have any tips or solution regarding this?

Warning: node_modules/.pnpm/@sinclair+typebox@0.28.4/node_modules/@sinclair/typebox/typebox.d.ts:110:47 - (TS2589) Type instantiation is excessively deep and possibly infinite.
sinclairzx81 commented 1 year ago

@ayZagen Hi,

Looking at the line number, it appears to be related to TComposite which is an extremely complicated type. I'm actually trying to get rid of this type (as it's provided for backwards compatibility) but to implement the type correctly, TypeScript needs to perform backflips to infer it. It should be supported in TS 4.2.3 and above, but depending on how complicated your type is, mileage may vary.

If possible, you can use Type.Intersect() over Type.Composite(). The difference between these types is that Type.Intersect() doesn't try to combine the composed types into a singular object (which is a lot more TS friendly). Alternatively you can upgrade to TS 5.0.

Repro

Would it be possible to share a repro of the Type you're having problems with? You should be able post it up on the TypeScript Playground and repro by setting the compiler version to 4.8.4. I'm interested in taking a look.

Hope this helps S

sinclairzx81 commented 1 year ago

@ayZagen Quick code paste for TS 4.8.4. Can't trivially replicate the issue but still interested to trace down the cause from the types you have.

Example

The following example intersects 3 objects with overlapping properties. The overlapping properties are combined into a distributed TIntersect (where each property infers to never, which is expected). This type pushes inference harder than usual as the work to produce the intersection per property is N^2 (3 properties * 3 intersections). It might be possible to explode the inference by having a lot of overlapping properties, so you may wish to check for this in your implementation.

TypeScript Link Here

import { Type, Static } from '@sinclair/typebox'

type T = Static<typeof T>      // type T = { x: never, y: never, z: never }

const T = Type.Composite([     // const T: TObject<{
    Type.Object({              //   x: TIntersect<[TNumber, TString, TBoolean]>, - (TNever)
      x: Type.Number(),        //   y: TIntersect<[TNumber, TString, TBoolean]>, - (TNever)
      y: Type.Number(),        //   z: TIntersect<[TNumber, TString, TBoolean]>, - (TNever)
      z: Type.Number(),        // }>
    }),
    Type.Object({ 
      x: Type.String(),
      y: Type.String(),
      z: Type.String(),
    }),
    Type.Object({ 
      x: Type.Boolean(),
      y: Type.Boolean(),
      z: Type.Boolean(),
    }),
])
ayZagen commented 1 year ago

@sinclairzx81 Thanks for the detailed responses! Strangely though I tried downgrading to 4.8.4 in the project itself and had no issues with it. It only happens when using api-extractor. BTW I tried removing Composite types but api-extractor fails when trying to inline typebox. I believe this issue is specific to api-extractor.

Now I am using dts-bundle-generator and have no issues.

Thanks.

Abe27342 commented 1 year ago

Adding this here as another datapoint in case it's helpful for others--I encountered a similar issue to @ayZagen in a project using Typescript 4.5.5 and api-extractor. We're invoking api-extractor with the --typescript-compiler-folder option which seems like it should internally use 4.5.5, but when doing some local debugging I'm not confident that's actually the case: it looked like api-extractor may have still been using the bundled 4.8.4 version as in the above reproduction.

I agree with the suspicion that it's specific to api-extractor. One interesting thing to note that the types I exported don't reference TComposite at all; I'm not sure why tsc would be trying to instantiate it.

Anyway, this isn't too rough for my use case as I'd rather not have typebox schemas in my public API anyway: the only reason they were there in the first place is because of this api-extractor issue: https://github.com/microsoft/rushstack/issues/3616.

If someone else encounters this and needs to dig deeper, this should be the compiler API invocation which causes these errors in API-extractor: https://github.com/microsoft/rushstack/blob/c31f6c5be835823dfd9004d721f9fc89dca4945d/apps/api-extractor/src/collector/Collector.ts#L195.

Edit: Curiosity got the better of me on the version mismatch. With respect to API extractor seemingly using 4.8.4 when --typescript-compiler-folder is specified, the doc I linked above isn't particularly clear with what that option does. Its CLI doc here makes it more clear this only affects the system typings, which matches the code here. So it seems like expected behavior that api-extractor can only use the typescript version being bundled.