TypeScript Version: 3.7.2, 3.8.0-dev.20191102 (worked in 3.6)
Search Terms:
- Type instantiation is excessively deep and possibly infinite.ts(2589)
- Mapped types
- Generics
- Conditional types
Code
Note: this issue manifests itself only in our codebase. When you run the same code in TypeScript Playground, it seems to be working fine.
The snippet is hardly minimal, but I reduced it as much as I could. I recorded a video where exactly the same code yields an error different than the one in TypeScript Playground. I tried with two versions of TypeScript: 3.7.2
and 3.8.0-dev.20191102
. It worked correctly with 3.6
.
Since @sheetalkamat and @DanielRosenwasser have access to our repository, you’re welcome to have a look at this PR. Copy-paste the code below anywhere in the project to see the error.
The versions of types used:
@types/history@4.7.3
@types/react@16.9.11
@types/react-router-dom@5.1.0
@types/recompose@0.30.7
Note: Interestingly enough, if you change:
- declare const Button: React.FunctionComponent<Omit<Props, never>>; + declare const Button: React.FunctionComponent<Props>;
it works again despite the fact Omit<Props, never>
should be the same as just Props
.
Source code
import { History } from 'history'; // "4.7.3" import * as React from 'react'; // "16.9.11" import { LinkProps, RouteComponentProps, withRouter } from 'react-router-dom'; // "5.1.0" import { getDisplayName } from 'recompose'; // "0.30.7" declare function isDefined<T>(candidate: T | null | undefined): candidate is T; declare function isString(value?: any): value is string; type ObjectOmit<T extends K, K> = Omit<T, keyof K>; type OnClick = NonNullable<React.ComponentProps<'button'>['onClick']>; type OnClickProp = { /** If there is a custom click handler, we must preserve it. */ onClick?: OnClick; }; type ProvidedProps = OnClickProp; type InputProps = OnClickProp & { /** Note: we want this helper to work with all sorts of modals, not just those backed by query * parameters (e.g. `/photo/:id/info`), which is why this must accept a full location instead of a * `Modal` type. * */ to: Exclude<LinkProps['to'], Function>; }; const buildClickHandler = ({ to, onClick, history, }: InputProps & { history: History; }): OnClick => { const navigate = () => { // https://github.com/Microsoft/TypeScript/issues/14107 isString(to) ? history.push(to) : history.push(to); }; return event => { [onClick, navigate].filter(isDefined).forEach(callback => callback(event)); }; }; /** See the test for an example of usage. */ export const enhance = <ComposedProps extends ProvidedProps>( ComposedComponent: React.ComponentType<ComposedProps>, ) => { type PassThroughComposedProps = ObjectOmit<ComposedProps, ProvidedProps>; type OwnProps = InputProps & RouteComponentProps<never> & PassThroughComposedProps; type Props = OwnProps; const displayName = `CreateModalLink(${getDisplayName(ComposedComponent)})`; const ModalLink: React.FunctionComponent<Props> = ({ to, onClick, history, // We specify these just to omit them from rest props below location, match, staticContext, ...passThroughComposedProps }) => { const clickHandler = buildClickHandler({ to, onClick, history }); const composedProps: ComposedProps = { // Note: this is technically unsafe, since the composed component may have props // with names matching the ones we're omitting. // https://github.com/microsoft/TypeScript/issues/28884#issuecomment-503540848 ...((passThroughComposedProps as unknown) as PassThroughComposedProps), onClick: clickHandler, } as ComposedProps; return <ComposedComponent {...composedProps} />; }; ModalLink.displayName = displayName; return withRouter(ModalLink); }; type Props = React.ComponentPropsWithoutRef<'button'> & Required<Pick<React.ComponentPropsWithoutRef<'button'>, 'type'>>; /** * This one errors. */ declare const Button: React.FunctionComponent<Omit<Props, never>>; /** * This one works. */ // declare const Button: React.FunctionComponent<Props>; const EnhancedButton = enhance(Button); /** * Type instantiation is excessively deep and possibly infinite.ts(2589). */ () => <EnhancedButton></EnhancedButton>;
Expected behavior:
I should get a proper error about missing properties (not the one about type instantiation):
Type '{}' is missing the following properties from type 'Readonly<Pick<OwnProps, "form" | "style" | "title" | "onClick" | "to" | "key" | "autoFocus" | "disabled" | "formAction" | "formEncType" | "formMethod" | "formNoValidate" | "formTarget" | ... 252 more ... | "onTransitionEndCapture">>': to, type(2739)
Actual behavior:
I’m getting this:
Type instantiation is excessively deep and possibly infinite.ts(2589).
Playground Link:
Playground Link
Related Issues:
- Conditional types break with property chaining #32735
- This is not for TypeScript but for everybody facing type instantiation too deep #34850
Issue
Playground link
I have these general definitions:
type Module<P extends Payloads, C extends Children> = {
payloads: P;
children: C;
};
type Children = Record<string, any>; // some values will be nested Modules
type Payloads = Record<string, any>;
type ExtractPayloads<M extends Module<any, any>> = M extends Module<infer P, any> ? P : never;
type ExtractChildren<M extends Module<any, any>> = M extends Module<any, infer C> ? C : never;
Basically, Modules are types that specify a children
type, which can contain nested Modules.
I have this type that can generate Action
s based on a Module’s payload
type:
type ModuleRootActions<
MODULE extends Module<any, any>,
PAYLOADS = ExtractPayloads<MODULE>,
> = {
[NAME in keyof PAYLOADS]: {
type: NAME;
payload: PAYLOADS[NAME];
};
}[keyof PAYLOADS];
Next, I have a recursive type that helps me generate Action
s for ALL Modules in a Module’s tree (i.e., including its child modules, and grandchildren, etc):
type AllModuleActions<
MODULE extends Module<any, any>,
CHILDREN = ExtractChildren<MODULE>,
> =
| ModuleRootActions<MODULE>
| {
[KEY in keyof CHILDREN]: CHILDREN[KEY] extends Module<any, any>
? AllModuleActions<CHILDREN[KEY]>
: never;
}[keyof CHILDREN];
Finally, I have these concrete examples:
type C = Module<{
"incrementBy": number;
}, {}>;
type B = Module<{
"setIsSignedIn": boolean;
}, {
c: C;
}>;
type A = Module<{
"concat": string;
"setIsDarkMode": boolean;
}, {
b: B;
}>;
All my types thus far are correct — I’ve verified this manually. Now, I’m writing a function that takes in an Action
of a generic Module
. I can successfully define these types:
type ConcreteAction<M extends Module<any, any>> = AllModuleActions<M>;
const concreteAction: ConcreteAction<A> = {
type: "concat",
payload: "str",
}
But once I try to put them in a generic function, I get the error in the title.
const composedReducer = <MODULE extends Module<any, any>>(
action: AllModuleActions<MODULE>,
) => {
if (action) {
}
};
You’ll notice in the Playground link that action
has the error: «Type instantiation is excessively deep and possibly infinite». I assume this is happening because the MODULE
type is generic, and it’s possible that there could be cycles in the Module
definition, even though semantically I know that it’s a tree.
How can I fix this error? Is there a way to tell the compiler that the graph will always be a tree and never contain infinite cycles?
Solution
The AllModuleActions<M>
type is recursive in a way that the compiler cannot handle very well. You are indexing arbitrarily deep into an object type all at once to produce a big union type; I’ve run into this problem before here when generating all the dotted paths of an object type (e.g., {a: {b: string, c: number}}
becomes "a.b" | "a.c"
).
You don’t usually see the problem when you evaluate something like AllModuleActions<M>
when M
is a specific type; but when it’s an unspecified generic type parameter (or a type that depends on such a type parameter), you can run into trouble. You might see that «excessively deep» error. Even worse, the compiler tends to get bogged down, spiking CPU usage and slowing down your IDE. I don’t know exactly why this happens.
Probably the best advice is not to build types like this. If you have to, then there are some ways I’ve found to help, but they aren’t foolproof:
Sometimes you can cause the compiler to defer evaluation of one of these types by rephrasing it as a distributive conditional type. If M
is a generic type parameter and AllModuleActions<M>
gives you trouble, maybe M extends any ? AllModuleActions<M> : never
won’t:
const generic = <M extends Module<any, any>>(
action: M extends any ? AllModuleActions<M> : never, // <-- defer
) => {
action; // okay
};
If that doesn’t work you can try to explicitly depth-limit your recursive type so that by default things only descend three or four levels:
type Prev = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
type AllModuleActions<M extends Module<any, any>, D extends Prev[number] = 4> =
[D] extends [never] ? never :
| ModuleRootActions<M>
| {
[K in keyof M["children"]]: M["children"][K] extends Module<any, any>
? AllModuleActions<M["children"][K], Prev[D]>
: never;
}[keyof M["children"]];
This is similar to yours, except we have added a D
parameter which (by default) starts at 4
and decreases each time AllModuleActions
is evaluated (note that Prev[4]
is 3
and Prev[3]
is 2
) until eventually it reaches never
and the recursive type bails out:
const generic = <M extends Module<any, any>>(
action: AllModuleActions<M>
) => {
action; // okay
};
These workarounds may or may not help for a particular use case, and there may be observable side effects (e.g., types might not be identical; type inference may behave differently; displayed quickinfo might be more complicated), so be careful!
Playground link to code
Answered By — jcalz
nikhilag opened this issue 2 years ago · comments
For a simple type such as:-
export const ZCustomer = z.object({
name: ZString()
});
where ZString is:-
export const ZString = () => z.string().min(1)
I am getting a typescript error:-
semantic error TS2589: Type instantiation is excessively deep and possibly infinite
I am using typescript 4.3.2 with Zod 3.2.0 in a tsdx 0.14.1 setup. Not sure what’s wrong here.
Got the same problem on a relatively simple type:
z.object({ name: z.string().nonempty(), city: z.string().min(3), })
I’m using Zod 3.2.0 too but with an older Typescript version, 4.0.5.
@ingro Can you try with Typescript 4.1+? As mentioned in the documentation, Zod 3 requires TS 4.1+
@nikhilag You’re right, this is resolved by upgrading typescript.
Unfortunately it’s happening in my case even with the latest typescript (see my message above). Do I need to downgrade typescript to an older version in case latest is not supported?
Having trouble replicating this @nikhilag, make sure your version of TypeScript is the one being used by VSCode. Otherwise try creating a replication repo.
Thanks for checking this @colinhacks
I was able to resolve the issue by moving to Zod 3.1 and Typescript 4.1.5.
I am not able to reproduce this issue in a smaller project. I am using Zod for all my types in a tsdx package where I am consistently hitting this issue with Zod 3.2 and I am not sure why. Spent a lot of time figuring this out but ultimately got lucky with trying a lower version of Zod. If at all possible, I can do a screen share and debug this issue with you.
Also running into this issue with Typecsript 4.3.2 and Zod 3.2. It occurs intermittently, which makes replicating likely difficult. Running tsc or jest twice in a row will throw the error one time and compile perfectly fine the next.
So far it seems to only occur on object schemas, even simple ones.
Maybe something to do with memory pressure?
The issue in my case was with tsdx. It seems tsdx was using an old version of typescript to build and hence I was running into that error. See jaredpalmer/tsdx#1044 for reference along with suggested fix.
@nikhilag Two other people just reported similar issues but I still can’t reproduce it. I know it’s been a while but do you think you could put together a reproduction repo?
@colinhacks Having the same issue on a random z.object()
- Project uses Lerna/Yarn workspaces
- Upgraded TS to
"typescript": "^4.4.2"
in both the Workspace and all the packages - Upgraded Zod to 3.8 in all 3 of the packages that used it.
- DELETED the lock-file
rm -rf node_modules
yarn install
And that seems to have resolved my errors.
I had a complex schema that was working fine and now I get this error from time to time.
OS: Ubuntu WSL 2
TS: 4.4.2
zod: 3.8.1
Tried removing node_modules and reinstalling, didn’t help. Reduced schema to
var PostOffer = z.object({ offer: z.object({}), });
which helped for this yarn watch
execution, but rerruning it broke it again.
4 offer: z.object({}),
~~~~~~~~~~~~
```.
I tried making repository that replicates my issue, but failed to.
Just as an update for others running into the problem:
- Ubuntu 22.04 (OS typescript 4.5.4, project typescript 4.7.4)
- zod 3.17.10
- Yarn workspaces
- Medium-sized schema (70 LoC, nothing recursive, just three levels deep, but several
z.discriminatedUnion()
s)
After deriving a .deepPartial()
from that, VScode started complaining about ts(2589)
.
rm -rf yarn.lock node_modules */node_modules; yarn
fixed it; thanks to @agentlewis’ advice.
(Restarting VScode before going rm -rf
didn’t fix it, so it must have been something persistent; I have no clue what on earth could be the reason for that behavior and why the reinstall fixed it. If it happens again, I will take a snapshot first and compare with the result after the reinstall.)
(Maybe the workspace from which the schema is imported had been compiled with the OS tsc
(4.5.4) instead of the project tsc
(4.7.4); but recompiling it with the old one did not make the problem resurface. Strange.)
i18next has embedded type definitions. If you want to enhance IDE Experience and prevent errors (such as type coercion), you should follow the instructions below in order to get the t function fully-type safe (keys and return type).
This is an optional feature and may affect the compilation time depending on your project’s size. If you opt not to leverage the type enhancements suggested here, you can ignore this section.
Create a declaration file
Create a declaration file
TypeScript definitions for i18next can be extended by using
Type Augmentation
and
Merging Interfaces
. So the first step is creating a declaration file (i18next.d.ts
), for example:
// import the original type declarations
// import all namespaces (for the default language, only)
import ns1 from «locales/en/ns1.json»;
import ns2 from «locales/en/ns2.json»;
declare module «i18next» {
// Extend CustomTypeOptions
interface CustomTypeOptions {
// custom namespace type, if you changed it
Or, if you want to include all namespaces at once, you can use our preferred approach:
export const defaultNS = «ns1»;
export const resources = {
i18n.use(initReactI18next).init({
import { resources, defaultNS } from «./i18n»;
declare module «i18next» {
interface CustomTypeOptions {
defaultNS: typeof defaultNS;
resources: typeof resources[«en»];
We recommend creating a @types
directory under src
or above it and placing all your type declarations there. E.g.: @types/i18next.d.ts
We provide a few options that can improve TypeScript for i18next
. All options come with default values, and if you want to change them, you just need to add them under CustomTypeOptions
interface in your i18next type declaration file (i18next.d.ts
).
option |
default |
description |
---|---|---|
defaultNS |
‘translation’ |
Default namespace. This is more practical in React applications, so when you call |
resources |
object |
Resources to initialize with. This is the most important option that is used to infer the appropriate keys and return types. |
keySeparator |
‘.’ |
Char to separate keys. |
nsSeparator |
‘:’ |
Char to split namespace from key. |
returnNull |
true |
Allows null values as valid translation. |
returnEmptyString |
true |
Allows empty string as valid translation. |
jsonFormat |
‘v4’ |
Json Format Version — V4 allows plural suffixes. See here for more information about Plurals. |
allowObjectInHTMLChildren |
false |
Flag that allows HTML elements to receive objects. This is only useful for React applications where you pass objects to HTML elements so they can be replaced to their respective interpolation values (mostly with Trans component). |
In order to fully type the t
function, we recursively map all nested keys from your primary locale files or objects. Depending on the number of keys your project have, the compilation time could be noticeably affected. If this is negatively influencing your productivity, this feature might not be the best choice for you. If needed, you can always open an issue on Github to get some help from us.
Type error — template literal
Type error — template literal
Argument of type ‘string’ is not assignable to parameter of type …
When using the following approach (template literal with an expression):
i18next.t(`${expression}.title`);
const { t } = useTranslation();
t(`${expression}.title`);
const { t } = useTranslation(`${ns}Default`);
TypeScript will lose the literal value, and it will infer the key
as string, which will cause to throw the error above. In this case, you will need to assert the template string as const
, like this:
i18next.t(`${expression}.title` as const);
const { t } = useTranslation();
t(`${expression}.title` as const);
For now, this is the only possible workaround. This is a TypeScript limitation that will be address at some point in the future.
Type error — excessively deep and possibly infinite
Type error — excessively deep and possibly infinite
If you face this issue whenever calling the t
function:
TS2589: Type instantiation is excessively deep and possibly infinite.
That probably means you did not set up your type declaration correctly, so review your configuration or check
here
for some similar cases that may help you. If needed, you can always open an issue on Github to get some help from us.
Tagged Template Literal (react-i18next
only)
Tagged Template Literal (react-i18next
only)
If you are using the tagged template literal syntax for the t
function, like this:
The keys
and return
type inference will not work, because
TemplateStringsArray
does not accept generic types yet. You can use Tagged Template Literal syntax, but it will accept any string as argument.
Argument of type ‘DefaultTFuncReturn’ is not assignable to parameter of type xyz
Argument of type ‘DefaultTFuncReturn’ is not assignable to parameter of type xyz
t
function can return null
, this behaviour is
set by default
, if you want to change it, set returnNull
type to false
.
declare module ‘i18next’ {
interface CustomTypeOptions {