Two levels of generic nesting and Parameters<T> results in a TypeScript error. But one level works, and no Parameter<T> works. Why?
TypeScript doesn't always handle constraints on these nested indexes well. You can get around it by declaring a version of Parameters
without the constraint (which will just evaluate to never when applied to a non-function type):
type ParametersUnconstrained<T> = T extends (...args: infer P) => any ? P : never
const callAPI = <
Category extends keyof API,
Name extends keyof API[Category],
>(category: Category, name: Name, param: ParametersUnconstrained<API[Category][Name]>[0]) => {
console.log(`Param to ${name}: ${param}`);
};
or you could add a conditional type to remind TypeScript of the constraint: API[Category][Name] extends (...args: any) => any ? API[Category][Name] : never
, which corresponds to Extract<API[Category][Name], (...args: any) => any>
:
const callAPI = <
Category extends keyof API,
Name extends keyof API[Category],
>(category: Category, name: Name, param: Parameters<Extract<API[Category][Name], (...args: any) => any>>[0]) => {
console.log(`Param to ${name}: ${param}`);
};
And a third option would be to write a conditional type to do the indexing in API
:
type IndexAPI<Category, Name> =
Category extends keyof API ? Name extends keyof API[Category] ? API[Category][Name] : never : never
which makes callAPI
look like this:
const callAPI = <
Category extends keyof API,
Name extends keyof API[Category],
>(category: Category, name: Name, param: Parameters<IndexAPI<Category,Name>>[0]) => {
console.log(`Param to ${name}: ${param}`);
};
Playground link