Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

This is not for TypeScript but for everybody facing type instantiation too deep #34850

Closed
aexol opened this issue Oct 31, 2019 · 7 comments
Closed
Labels
Discussion Issues which may not have code impact

Comments

@aexol
Copy link

aexol commented Oct 31, 2019

Hold on everybody facing "Type instantiation is excessively deep and possibly infinite" I think I've just found a solution for type instantiation too deep error in
https://github.com/graphql-editor/graphql-zeus this repo. The solution is to create a conditional type even if you know that the type is the type. I can crawl out the rabbit hole now!

type Func<P extends any[], R> = (...args: P) => R;
type AnyFunc = Func<any, any>;

type IsType<M, T, Z, L> = T extends M ? Z : L;
type IsScalar<T, Z, L> = IsType<string | boolean | number, T, Z, L>;
type IsObject<T, Z, L> = IsType<{} | Record<string,any>, T, Z, L>;

type WithTypeNameValue<T> = T & {
  __typename?: true;
};

type AliasType<T> = WithTypeNameValue<T> & {
  __alias?: Record<string, WithTypeNameValue<T>>;
};

export type AliasedReturnType<T> = IsObject<T,{
  [P in keyof T]: T[P];
} &
Record<
  string,
  {
    [P in keyof T]: T[P];
  }
>,never>;

export type ResolverType<F> = F extends Func<infer P, any>
  ? P[0]
  : undefined;

type ArgsType<F extends AnyFunc> = F extends Func<infer P, any> ? P : never;
type OfType<T> = T extends Array<infer R> ? R : T;
type FirstArgument<F extends AnyFunc> = OfType<ArgsType<F>>;

interface GraphQLResponse {
  data?: Record<string, any>;
  errors?: Array<{
    message: string;
  }>;
}

export type State<T> = {
  [P in keyof T]?: T[P] extends (Array<infer R> | undefined)
    ? Array<AliasedReturnType<State<R>>>
    : T[P] extends AnyFunc
    ? AliasedReturnType<State<ReturnType<T[P]>>>
    : IsScalar<T[P], T[P], IsObject<T[P],AliasedReturnType<State<T[P]>>,never>>;
};

export type PlainObject<T> = {
  [P in keyof T]?: T[P] extends (Array<infer R> | undefined)
    ? Array<PlainObject<R>>
    : T[P] extends AnyFunc
    ?  PlainObject<ReturnType<T[P]>>
    : IsScalar<T[P], T[P], IsObject<T[P],PlainObject<T[P]>,never>>;
};

type ResolveValue<T> = T extends Array<infer R>
  ? SelectionSet<R>
  : T extends AnyFunc
  ? IsScalar<
      ReturnType<T>,
      [FirstArgument<T>],
      [FirstArgument<T>, SelectionSet<OfType<ReturnType<T>>>]
    >
  : IsScalar<T, T extends undefined ? undefined : true, IsObject<T,SelectionSet<T>,never>>;

export type SelectionSet<T> = IsScalar<
  T,  T extends undefined ? undefined : true
, IsObject<T,AliasType<
    {
      [P in keyof T]?: ResolveValue<T[P]>;
    }
  >,never>>;

type GraphQLReturner<T> = T extends Array<infer R> ? SelectionSet<R> : SelectionSet<T>;

type OperationToGraphQL<V,T> = (o: GraphQLReturner<V>) => Promise<AliasedReturnType<State<T>>>;

type ResolveApiField<T> = T extends Array<infer R>
  ? IsScalar<R, R, State<R>>
  : T extends AnyFunc
  ? IsScalar<OfType<ReturnType<T>>, T, State<OfType<ReturnType<T>>>>
  : IsScalar<T, T, State<T>>;

type ApiFieldToGraphQL<V,T> = (o: ResolveValue<V>) => Promise<ResolveApiField<T>>;

type fetchOptions = ArgsType<typeof fetch>;

So whats going on here? I had a problem with this error on large schemas, but... not anymore after adding IsObject type. The main thing is to drop the type in the type system as never even if you know it is a correct type. This is the only way as you tell the compiler that going down is conditional.

@orta can you look at it? Correct me if I am wrong but it looks like when the conditional types are closed somehow you can create very generic types.

@AnyhowStep
Copy link
Contributor

AnyhowStep commented Oct 31, 2019

This doesn't always work. But it is one way that can work in some cases.

I'm always happy when someone figures out a solution to the max depth/count error for their situation!

Down with artificial constraints!
Long live hack-y workarounds!


To elaborate, there's an upper limit to the number of times you can nest conditional types before getting the max depth error again.

So, it is likely that a deeply nested graphql query will still trigger the error.

Have you tried nesting your types 100+ layers deep to see if it's okay?

I tend to aim for at least 60-80 layers, in general, before being satisfied that a "normal" user of my libraries will never encounter the max depth error

@RyanCavanaugh RyanCavanaugh added the Discussion Issues which may not have code impact label Oct 31, 2019
@aexol
Copy link
Author

aexol commented Nov 5, 2019

I had tried with bigger and bigger schemas and... got an error again. Right now the only thing i need to solve is optimising this:

export type AliasedReturnType<T> = IsObject<T,{
  [P in keyof T]: T[P];
} &
Record<
  string,
  {
    [P in keyof T]: T[P];
  }
>,never>;

Any ideas?

@AnyhowStep
Copy link
Contributor

AnyhowStep commented Nov 5, 2019

Umm.... I don't have any ideas, off the top of my head.
Do you have a repro? Maybe a github repository I can look at?

When ever I experience the max depth/count issue, I just... Brute force it until I find a way to make it work.

See, the problem here is that given a type T, there are infinitely many ways to express it syntactically. And in TS' case, some of those syntactical expressions give you the max depth/count error, some of those do not. (And, sometimes, we have to force TS to emit types a certain way to avoid the max depth/count error)

What we want is to find ways of expressing T that do not run into the max depth/count error. However, I've found that it is almost never intuitive why certain syntax works, while others do not.

See,


Also, things work differently between method chaining/function nesting and just having a giant computation.

In your case, you're probably using method chaining/function nesting. But I can't give an answer unless I play with the repro for a bit

@AnyhowStep
Copy link
Contributor

More importantly, complex type operators tend to cause the max depth/count error.

TS gives us a bunch of primitive type operators,

  • Union
  • Intersection
  • Mapped Object Type
  • Mapped Tuple Type
  • Conditional Types
  • etc.

These type operators take one or more types as input, and can produce a completely new type as an output.


We, as library/application developers, use these primitive type operators to form more complex type operators.

As you have seen, given a type operator that we want to implement, we can implement it a million other ways. Some of these implementations cause the max depth/count errors. Other implementations sidestep the issue magically. (Generator trampolines, copy-pasting code instead of using recursive types, using conditional types, forcing TS to re-use an existing type, other unintuitive hacks, etc.)

At the moment, finding ways around these issues and finding that "perfect" implementation that doesn't cause a max depth/count issue feels a lot like a dark art, rather than a science.

@aexol
Copy link
Author

aexol commented Nov 6, 2019

Found the way to resolve even on very big schemas. I will release new version of Zeus with it this week - stay tuned ! I used Mapped Types combined with inference from Union.

@aexol
Copy link
Author

aexol commented Nov 8, 2019

https://github.com/graphql-editor/graphql-zeus

Now with union mapped types! Works with really big schemas without error!!!

Tested on 3.7

type GraphQLReturner<T> = T extends Array<infer R> ? SelectionSet<R> : SelectionSet<T>;
type IsUnion<T, YES, NO, U extends T = T> = (T extends any
  ? (U extends T ? false : true)
  : never) extends false
  ? NO
  : YES;

type Anify<T> = { [P in keyof T]?: any };

type MapType<SRC extends Anify<DST>, DST> = DST extends boolean ? SRC : IsUnion<
  SRC,
  State<SRC>,
  DST extends {
    __alias: any;
  }
    ? {
        [A in keyof DST['__alias']]: Required<SRC> extends Anify<DST['__alias'][A]>
          ? MapType<Required<SRC>, DST['__alias'][A]>
          : never;
      } &
        {
          [Key in keyof Omit<DST, '__alias'>]: DST[Key] extends boolean
            ? SRC[Key]
            : DST[Key] extends [any, infer R]
            ? ReturnType<Required<SRC>[Key]> extends Array<infer RETURNED> ? MapType<RETURNED, R>[] : MapType<ReturnType<Required<SRC>[Key]> ,R>
            : Required<SRC>[Key] extends Array<infer SRCArray>
            ? MapType<SRCArray, DST[Key]>[]
            : MapType<Required<SRC>[Key], DST[Key]>;
        }
    : {
        [Key in keyof DST]: DST[Key] extends boolean
          ? SRC[Key]
          : DST[Key] extends [any, infer R]
          ? ReturnType<Required<SRC>[Key]> extends Array<infer RETURNED> ? MapType<RETURNED, R>[] : MapType<ReturnType<Required<SRC>[Key]> ,R>
          : Required<SRC>[Key] extends Array<infer SRCArray>
          ? MapType<SRCArray, DST[Key]>[]
          : MapType<Required<SRC>[Key], DST[Key]>;
      }
>;

type OperationToGraphQL<V, T> = <Z>(o: Z | GraphQLReturner<V>) => Promise<MapType<T, Z>>;

So creating union mapped types helped me to work around the error!

@aexol
Copy link
Author

aexol commented Nov 12, 2019

Final form available on my repo
https://github.com/graphql-editor/graphql-zeus
Thanks for the discussion.

@aexol aexol closed this as completed Nov 12, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Discussion Issues which may not have code impact
Projects
None yet
Development

No branches or pull requests

3 participants