TypeScript icon indicating copy to clipboard operation
TypeScript copied to clipboard

Calling recursively Generic<infer T>, the T becomes any

Open CptFabulouso opened this issue 1 year ago • 0 comments

Bug Report

Please refer to the playground, I am not able to explain the issue very much. Possibly that when calling a type recursively, which has infered generic param type, than the infered type becomes any at 2 levels deep.

🔎 Search Terms

  • typescript recursive infered generic param is any
  • typescript infer results to any
  • typescript generic infer any
  • type inference changes nested mapped types issue

🕗 Version & Regression Information

Since version 4.8.x

  • This changed between versions 4.7 and 4.8

⏯ Playground Link

Playground link with relevant code This issue arrises in react-navigation (issue) I extracted the relevant code and removed unnecessary parts. I don't understand the need for the union in the NavigatorScreenParams type, but it is necessary in order for it to work correctly. I tried simplifying it even more by removing the dependency on NavigatorScreenParams, but the level1 always resolved to any, therefore I think it's a bug and not a wrong type definition.

💻 Code

// helpers
type NavigatorScreenParams<ParamList> = {
    screen?: never;
    params?: never;
} | {
    [RouteName in keyof ParamList]: {
        screen: RouteName;
        params: ParamList[RouteName];
    };
}[keyof ParamList];

type PathConfig<ParamList extends {}> = {
	screens?: PathConfigMap<ParamList>
	initialRouteName?: keyof ParamList
}

type PathConfigMap<ParamList extends {}> = {
    [RouteName in keyof ParamList]?: NonNullable<ParamList[RouteName]> extends NavigatorScreenParams<infer T extends {}> ? string | PathConfig<T> : string | Omit<PathConfig<{}>, 'screens' | 'initialRouteName'>;
};

// usage
type RootNav = {
	root: NavigatorScreenParams<{
		level1: NavigatorScreenParams<{
			level2: {
				someOption: string
			}
		}>
	}>
}

const navConfig: PathConfigMap<RootNav> = {
	root: {
		initialRouteName: 'level1',
		screens: {
			level1: {
				initialRouteName: 'level2',
				screens: {
					level2: {
						//
					},
				},
			},
		},
	},
}

🙁 Actual behavior

From the playground, with TS 4.8.4 and above which has the result as: Snímek obrazovky 2023-05-05 v 15 07 29

🙂 Expected behavior

From the playground, with TS 4.7.4 which has the correct result as: Snímek obrazovky 2023-05-05 v 15 07 11

CptFabulouso avatar May 05 '23 13:05 CptFabulouso