Reputation: 11
declare function a(props: number): number;
declare function b(props: string): string;
declare function c(props: boolean): boolean;
type GenHooks<H extends (...args: any[]) => any> = [H, Parameters<H>]
const obj = {
a: [a, [1]] as GenHooks<typeof a>,
b: [b, ['2']] as GenHooks<typeof b>,
c: [c, [false]] as GenHooks<typeof c>,
} as const
type ObjType = typeof obj;
type Names = keyof typeof obj;
const keys = Object.keys(obj) as Names[];
let out: {
[K in Names]: ReturnType<ObjType[K][0]>
}
when i try to use forEach, it will error
keys.forEach(name => {
const [fn, args] = obj[name];
out[name] = fn(...args) // A spread argument must either have a tuple type or be passed to a rest parameter.(2556)
})
it's types will get
const args: [props: number] | [props: string] | [props: boolean]
and then i get an error message:
Type 'string | number | boolean' is not assignable to type 'never'. Type 'string' is not assignable to type 'never'.(2322)
but when don not use forEach, it's ok
const [fn, args] = obj.a;
fn(...args)
Upvotes: 1
Views: 127
Reputation: 33051
First issue is in this line fn(...args)
.
Here, fn
is a union of three functions ((props: number) => number) | ((props: string) => string) | ((props: boolean) => boolean)
.
Please read this section:
Likewise, multiple candidates for the same type variable in contra-variant positions causes an intersection type to be inferred.
It means that when you are trying to call fn
, all types of arguments are intersected instead of unionizing. Hence fn
expects number & string & boolean
instead of number | string | boolean
. Please keep in mind that number & string & boolean
is impossible type and it infered as never
.
You can find more explanation in my article.
Second issue is in this line out[name]
. It is impossible to assign return type of fn(...args)
to out[name]
.
Assume fn(...args)
compiles and returns string|number|boolean
. In the same way works out[name]
. It also expects a union of stirng|number|boolean
.
Consider this exmaple:
out[name] = 'str'
out[name] = 42
out[name] = true
All three expression causes an error, because TS is unsure about exact value of name
because we are inside a loop.
name
can be a|b|c
an all of them expects different types. In order to assign any value to out[name]
, you should check the type of name
.
if (name === 'a') {
out[name] = 42 // ok
}
Please see this question with similar issue and my article about mutations in typescript.
However, there several workarounds.
Upvotes: 1