json-schema-to-ts icon indicating copy to clipboard operation
json-schema-to-ts copied to clipboard

Error: Excessive stack depth comparing types 'ParseMixedSchema<?, T>' and 'ParseMixedSchema<?, T>'.ts(2321)

Open ffxsam opened this issue 2 years ago • 8 comments

As of version 1.6.5, I now get this error in several places in my code.

Excessive stack depth comparing types 'ParseMixedSchema<?, T>' and 'ParseMixedSchema<?, T>'.ts(2321)

It comes from this:

import type { FromSchema } from 'json-schema-to-ts';
import createHttpError, { NamedConstructors } from 'http-errors';
import { URLSearchParams } from 'url';

export type ValidatedAPIGatewayProxyEvent<S> = APIGatewayProxyEvent &
  FromSchema<S>;

which I adopted from the Serverless Framework TS template.

I create a JS/JSON API Gateway schema, e.g.:

const schema = {
  type: 'object',
  properties: {
    body: {
      type: 'object',
      properties: {
        coupon_id: { type: 'string' },
        end_of_term: { type: 'boolean' },
        plan_id: { type: 'string' },
        subscription_id: { type: 'string' },
        reactivate: { type: 'boolean' },
        first_payment: { type: 'boolean' },
      },
      required: ['end_of_term', 'plan_id', 'subscription_id'],
      additionalProperties: false,
    },
  },
} as const;

and pass it to Sentry's wrapHandler:

export const handler = Sentry.AWSLambda.wrapHandler<
      ValidatedAPIGatewayProxyEvent<S>,
      APIGatewayProxyResult | undefined
    >(...

This has worked fine up until the latest release.

ffxsam avatar Jan 22 '22 02:01 ffxsam

Hi @ffxsam and thanks for the issue.

Everything seems fine on json-schema-to-ts side. It's the combination of Sentry and FromSchema that seems to generate too many type computations 🤔 I don't even get why ParseMixedSchema<?, T> is mentioned here, as your schema is not mixed (i.e. you do not provide an array of types but simply "object").

Can you try defining ValidatedAPIGatewayProxyEvent<S> in a separate type ?

type Event = ValidatedAPIGatewayProxyEvent<S>

export const handler = Sentry.AWSLambda.wrapHandler<
      Event,
      APIGatewayProxyResult | undefined
    >(...

...or simply, not providing explicitely the generic types to wrapHandler ? (they can be inferred from the handler itself, I think)

const yourFunc = async (event: ValidatedAPIGatewayProxyEvent<S>) : Promise<APIGatewayProxyResult | undefined> => ...

export const handler = Sentry.AWSLambda.wrapHandler(yourFunc)

What version of json-schema-to-ts did you use before having the error ? Was json-schema-to-ts the only library that you upgraded or did you upgrade Typescript as well ?

ThomasAribart avatar Jan 22 '22 18:01 ThomasAribart

Hey @ThomasAribart, thanks for the reply!

You'll have to excuse me, as my TypeScript ability is passable at best.

type Event = ValidatedAPIGatewayProxyEvent<S>;

This results in an error, Cannot find name 'S'.

Unfortunately, I can't rely on implicit types here. The code for wrapApiHandler is more complex than illustrated here (it took me hours to figure out). I can post it in its entirety if that helps you.

TypeScript was updated, but I reverted it step by step back to version 4.5.2 and the issue still persisted. Then I reverted from json-schema-to-ts version 1.6.5 to 1.6.4 and that resolved it.

ffxsam avatar Jan 22 '22 19:01 ffxsam

S is supposed to be the type of your schema, defined with the as const statement:

import { schema } from './schema'

type S = typeof schema

ThomasAribart avatar Jan 24 '22 18:01 ThomasAribart

Thanks, Thomas! I appreciate the help, but unfortunately, I don't have the bandwidth to try to track down what's going on. I'm guessing it's due to some sloppy TypeScript that json-schema-to-ts was previously more forgiving about. I'll pin my project to version 1.6.4 and call it a wrap.

ffxsam avatar Jan 25 '22 02:01 ffxsam

I've been seein the same error in the latest version in https://github.com/feathersjs/feathers/tree/dove/packages/schema. The smallest example I could come up with to reproduce looks like this:

class Test<S extends JSONSchema> {
  readonly type: FromSchema<S>;
}

const schemaA = {
  type: 'object',
  additionalProperties: false,
  properties: {
    name: { type: 'string' }
  }
} as const;

const schemaB = {
  type: 'object',
  additionalProperties: false,
  properties: {
    name: { type: 'string' }
  }
} as const;

const t: Test<typeof schemaB> = new Test<typeof schemaA>();

Maybe that helps, I can try and do some more digging if you think it's worth investigating. Either way, this is a great project!

daffl avatar Jan 25 '22 04:01 daffl

Oop, I made a duplicate issue here: https://github.com/ThomasAribart/json-schema-to-ts/issues/56

Filed here because I haven't seen this error on any other codebases aside from when I use json-schema-to-ts

But now I'm thinking we have exposed some bug in TypeScript.

ericvicenti avatar Apr 02 '22 21:04 ericvicenti

Typescript 4.7, which introduced variance annotations, has been released. I believe variance annotations could help solve this issue. And overall speed up type inference. @ThomasAribart, could you please look into that?

psznm avatar Jun 02 '22 15:06 psznm

@daffl @psznm @ffxsam @ericvicenti Indeed, it seems like the variance annotation helps a bit. The smallest example you provided is fixed by declaring S as invariant (i.e. with both in and out annotation).

I guess it skips the FromSchema type computation altogether and call it a day if schemaA and schemaB are equal (or not):

class Test<in out S extends JSONSchema> {
  readonly type?: FromSchema<S>;
}

const schemaA = {
  type: "object",
  additionalProperties: false,
  properties: {
    name: { type: "string" },
  },
} as const;

const schemaB = {
  type: "object",
  additionalProperties: false,
  properties: {
    name: { type: "string" },
  },
} as const;

// works fine
const test: Test<typeof schemaB> = new Test<typeof schemaA>();

However, I'm not completely sure this fixes the issue ? 🤔

I've found that defining the expected type as the default value of a second generic type is a functioning work-around though:

// "out" annotation is welcome but not necessary
class Test<S extends JSONSchema, out T = FromSchema<S>> {
  readonly type?: T;
}

const schemaA = {
  type: "object",
  additionalProperties: false,
  properties: {
    name: { type: "string" },
  },
  // this time, you can have different specs between schemas
  required: ['name']
} as const;

const schemaB = {
  type: "object",
  additionalProperties: false,
  properties: {
    name: { type: "string" },
  },
} as const;

// works fine
const test: Test<typeof schemaB> = new Test<typeof schemaA>();

Can you confirm that it work ? Does it close this issue ?

ThomasAribart avatar Jul 26 '22 21:07 ThomasAribart

I think this can be closed. I haven't seen this weirdo error in a while

Now I just need to find workarounds for "Type instantiation is excessively deep and possibly infinite" 😤

ericvicenti avatar Aug 17 '22 17:08 ericvicenti