Skip to content
This repository was archived by the owner on Aug 3, 2023. It is now read-only.

TypeScript 3.4+: Type instantiation is excessively deep and possibly infinite. #8

Closed
KSXGitHub opened this issue May 28, 2019 · 20 comments · Fixed by #24
Closed

TypeScript 3.4+: Type instantiation is excessively deep and possibly infinite. #8

KSXGitHub opened this issue May 28, 2019 · 20 comments · Fixed by #24
Labels
bug Something isn't working good first issue Good for newcomers help wanted Extra attention is needed

Comments

@KSXGitHub
Copy link
Member

Since TypeScript 3.4, it refuses to do recursive operation. Must fix it somehow.

@KSXGitHub KSXGitHub changed the title Type instantiation is excessively deep and possibly infinite. TypeScript 3.4+: Type instantiation is excessively deep and possibly infinite. May 28, 2019
@KSXGitHub KSXGitHub added bug Something isn't working good first issue Good for newcomers help wanted Extra attention is needed labels May 28, 2019
This was referenced May 28, 2019
@artalar
Copy link
Contributor

artalar commented May 28, 2019

Maybe write codegen for something like that?

type DeepFlatMap<Tuple> = Tuple extends any[]
  ? Tuple extends []
    ? []
    : Tuple extends [any]
    ? Tuple["0"] extends any[]
      ? Tuple["0"] extends [any]
        ? Tuple["0"]["0"] extends any[]
          ? any // Tuple["0"]["0"] extends [any] // <-- need codegen 
          : Tuple["0"]["0"]
        : any // Tuple["0"] extends [any, any] // <-- need codegen 
      : Tuple["0"]
    : any // Tuple extends [any, any] // <-- need codegen 
  : never;

@KSXGitHub
Copy link
Member Author

Maybe write codegen for something like that?

You meant using a script to generate non-recursive type definition ahead of time? This requires a lot of work. And I am not exactly focusing on TypeScript projects right now. Is there any third-party tool that can help us?

@artalar
Copy link
Contributor

artalar commented May 28, 2019

Yes, I mean generate type anotation for tuples like 20(breadth) x 5(depth) elements.
I can try to write it, but what exactly must do that type? DeepFlatMap or something else?

@KSXGitHub KSXGitHub pinned this issue May 28, 2019
@KSXGitHub
Copy link
Member Author

It would be convenient to have a tool that reads a template file and generates a typescript file. But if there's none, I think writing scripts by hand is good enough.

@artalar
Copy link
Contributor

artalar commented May 28, 2019

Can u add an example of template API? How u want to use it?

@KSXGitHub
Copy link
Member Author

I meant I don't want to use an API at all, just want to use a script to transform a template into TypeScript code.

This code should work just fine:

const UPPER_LIMIT = 100

typescript`
type UpperLimitRange = ${Array(UPPER_LIMIT).join(' | ')};

export type Repeat<T, N extends ${UPPER_LIMIT}> = ${
  Array(UPPER_LIMIT)
    .map((_, i) => i)
    .map(n => `N extends ${n} ? [${Array(n).fill('T').join(', ')}]`)
    .join(':\n')
};
`

But I want it to be simpler, more elegant, and hygiene. Something like this (pseudo-code):

const UPPER_LIMIT = 100

export const Repeat: GenericType =
  (T: any, N: RangeUnion(UPPER_LIMIT)) =>
    for n in range(UPPER_LIMIT):
      addCase(extends(N, n), array(n, T))

I want to find an existing tool for this. If there isn't one, then we must resort to the first code snippet.

@artalar
Copy link
Contributor

artalar commented May 28, 2019

@KSXGitHub
Copy link
Member Author

KSXGitHub commented May 28, 2019

=(
microsoft/TypeScript#30188 (comment)

I tried to reproduce the error, but it didn't occur to me, perhaps it is fixed? I take it back. I got the error.

@KSXGitHub
Copy link
Member Author

I have created microsoft/TypeScript#31619.

For now, is there any other way around this?

@KSXGitHub
Copy link
Member Author

KSXGitHub commented May 28, 2019

Thanks to this comment, I have a workaround: https://gist.github.com/KSXGitHub/a5c453c6212337f032adabb57b99062c

@artalar My focus is not on any TypeScript project at the moment, can you help me with this?

@artalar
Copy link
Contributor

artalar commented May 28, 2019

I dont get it, what workaround u mean?

@KSXGitHub
Copy link
Member Author

I dont get it, what workaround u mean?

The code that causes the error is one that has too many conditional types:

type Foo<T extends 0 | 1 | 2 | 3 | ...> =
  T extends 0 ? [] : 
  T extends 1 ? [0] : 
  T extends 2 ? [0, 1] : 
  T extends 3 ? [0, 1, 2] :
  ...

By not using any conditional type, the error is eliminated:

type Foo<T extends 0 | 1 | 2 | 3 | ...> = [
  [],
  [0],
  [0, 1],
  [0, 1, 2],
  ...
][T]

@KSXGitHub
Copy link
Member Author

KSXGitHub commented May 28, 2019

Oops, I copied the wrong link. The link to the workaround is https://gist.github.com/KSXGitHub/a5c453c6212337f032adabb57b99062c

@artalar
Copy link
Contributor

artalar commented May 28, 2019

Awesome! I can look at it closer few days later

@KSXGitHub
Copy link
Member Author

Tip: ts-morph seems to be cool.

@aexol
Copy link

aexol commented Oct 31, 2019

Hold on everybody I 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

@cyberixae
Copy link

I wrote my own tuple like structure to replace the actual TypeScript tuple. It is very limited since I wrote for a specific use case but perhaps the idea could be extended into an actual library. https://github.com/maasglobal/scrapql/blob/master/src/tuple.ts

@KSXGitHub
Copy link
Member Author

KSXGitHub commented Jan 18, 2020

I wrote my own tuple like structure to replace the actual TypeScript tuple. It is very limited since I wrote for a specific use case but perhaps the idea could be extended into an actual library. https://github.com/maasglobal/scrapql/blob/master/src/tuple.ts

Your "tuple" is an array in appearance but not in spirit, and if I was you, I would use objects instead.

@cyberixae
Copy link

Using array is more convenient since it is easier to destructure

type Stuff = Prepend<number, Prepend<string, Zero>>;

function foo( [ num, [ str ] ]: Stuff ) {
  // ...
}

@cyberixae
Copy link

PR microsoft/TypeScript#39094 will probably make fixing this a lot easier.

@KSXGitHub KSXGitHub unpinned this issue Aug 22, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug Something isn't working good first issue Good for newcomers help wanted Extra attention is needed
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants