I am having difficulties in writing the type of configArgs
Can anybody help?
const path = {
A: 'home',
B: 'support',
C: 'contact'
} as const
type B = unknown
const B: B = {};
const config = {
[path.A]: () => null,
[path.B]: (): B => B,
[path.C]: ({
active,
}: {
active: boolean
}) => B,
} as const
type Config = typeof config
type ConfigArgs = {
[P in keyof Config]: Config[P] extends (a: infer A) => any ? A : never // this is not working
}
function getConfig<T extends A>(
path: A,
args?: ConfigArgs[R]
): B | null {
return config[path](args)
}
I would expect to see the correct arguments type whenever i call the getConfig
function with the path.
getConfig("pathA") // no type errors
getConfig("pathC", {active:true}) // no type errors
getConfig("pathC", {id:"xxx"}) // has type errors, as parameters are wrong
getConfig("pathC") // has type errors, as function needs params.
getConfig("pathA", {active:true}) // has type errors, cause the function does not expect any params
If all you need to support is strong typing for the callers of
getConfig()
, then you can make it generic in the typeK
of thepath
argument, and use theParameters<T>
and theReturnType<T>
utility types to represent the input/output relationship:which works as expected:
However, you'll find that the compiler won't be able to verify that the implementation of this function is correct:
That's a current limitation of TypeScript; generic conditional types like
Parameters<Config[K]>
andReturnType<Config[K]>
are essentially opaque to the compiler. You can still use these types, but you'll need to use type assertions in the implementation to suppress errors, and take care that you've done the work correctly because the compiler can't help here:If you want the compiler to follow the generic logic, you will need to refactor to using indexed accesses on generic mapped types as described in microsoft/TypeScript#47109:
Here we define both
ConfigArgs
andConfigRet
as a mapped type onConfig
, and then define the_config
variable as a mapped type of functions takingConfigArgs[K]
and returningConfigRet[K]
for each keyK
. The compiler lets us assignconfig
to this variable, because it's able to expand that mapped type into the same specific type asconfig
.And now the
getConfig()
function is seen is performing an operation on_config[path]
, a value of type(...args: ConfigArgs[K]) => ConfigRet[K]
, and therefore it accepts a rest argument of typeConfigArgs[K]
and produces an output of typeConfigRet[K]
.So everything still works from the caller's side, and the implementation is also type checked.
Note that there is conceptually no difference between
typeof config
andtypeof _config
, but the compiler seestypeof _config
as representing the generic relationship between input and output in a way it cannot see fortypeof config
.Playground link to code