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

4.6.2 regression: Type instantiation is excessively deep and possibly infinite. #48552

Closed
seansfkelley opened this issue Apr 4, 2022 · 5 comments Β· Fixed by #48613
Closed

4.6.2 regression: Type instantiation is excessively deep and possibly infinite. #48552

seansfkelley opened this issue Apr 4, 2022 · 5 comments Β· Fixed by #48613
Assignees
Labels
Bug A bug in TypeScript

Comments

@seansfkelley
Copy link

Bug Report

πŸ”Ž Search Terms

recursive conditional types

πŸ•— Version & Regression Information

  • This changed between versions 4.5.5 and 4.6.2

⏯ Playground Link

Playground Link

πŸ’» Code

type Query<T> = {
  [P in DeepKeys<T>]?: string;
};

type Primitive =
	| null
	| undefined
	| string
	| number
	| boolean
	| symbol
	| bigint;

type DeepKeys<T> = T extends Primitive
  ? never
  :
      | (keyof T & string)
      | {
          [P in keyof T & string]: T[P] extends (infer Inner)[]
            ? `${P}.${DeepKeys<Inner>}`
            : T[P] extends object
            ? `${P}.${DeepKeys<T[P]>}`
            : P;
        }[keyof T & string];

πŸ™ Actual behavior

Type instantiation is excessively deep and possibly infinite. at the ${P}.${DeepKeys<T[P]>} snippet.

πŸ™‚ Expected behavior

No error. Type properly resolves to dot-notation keys of input object.

@RyanCavanaugh
Copy link
Member

Bisected to #47341

@RyanCavanaugh RyanCavanaugh added the Bug A bug in TypeScript label Apr 4, 2022
@ahejlsberg
Copy link
Member

ahejlsberg commented Apr 8, 2022

In #47341 we added code to not explore circular constraints, which for sure is correct. But with that code in place we now run into #30639, which by its nature explores more type relations. In this case, the constraints of the types we explore just balloon and we run into the instantiation limiter after creating >5M instantiations.

The quick fix is to rewrite the DeepKeys<T> to be more efficient:

type DeepKeys<T> = T extends Primitive ? never :
    (keyof T & string) | { [P in keyof T & string]: DeepSubKeys<T, P> }[keyof T & string];

type DeepSubKeys<T, P extends keyof T & string> =
    T[P] extends (infer Inner)[] ? `${P}.${DeepKeys<Inner>}` :
    T[P] extends object ? `${P}.${DeepKeys<T[P]>}` :
    never;

It's subtle, but moving the template type in the mapped type into a separate type alias causes us to do more caching and explore fewer implied constraints (because there are fewer containing conditional types), and that in turn eliminates the ballooning.

It seems that we might also be able to reduce the number of types we explore in getResolvedBaseConstraint. I'll look into that and put up a PR so we can see the effects.

@seansfkelley
Copy link
Author

Thanks for the detailed explanation! I had fiddled with some solutions using two related types, but I don't think I ever tried breaking them across these lines with mutual recursion.

@ahejlsberg
Copy link
Member

@seansfkelley You're welcome. BTW, here's an alternate formulation that avoids mapped types which is likely more efficient:

type DeepKeys<T> =
    object extends T ? string :
    T extends readonly unknown[] ? DeepKeys<T[number]> :
    T extends object ? keyof T & string | DeepSubKeys<T, keyof T & string> :
    never;

type DeepSubKeys<T, K extends string> = K extends keyof T ? `${K}.${DeepKeys<T[K]>}` : never;

The separate type for subkeys here is needed to ensure distribution over K.

@seansfkelley
Copy link
Author

Indeed! Anecdotally, that is far more efficient to respond to autocompletes than the type I was using before. Definitely more readable, too.

This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants