You've run into TypeScript's less-than-stellar support for what I've been calling correlated record types. Right now it sees that b.fun
is a union type "f1" | "f2"
, and that b.value
is a union type [string] | [number]
, and it treats these as uncorrelated. Normally a union of functions can only be called with an intersection of its parameters, which in this case would be [string] & [number]
. Since [string] | [number]
is not assignable to [string] & [number]
, the compiler complains. In fact no value is assignable to [string] & [number]
, since its first element would need to be a string & number
which is equivalent to never
.
In order for the compiler to verify that map[b.fun].apply(null, b.value)
is type safe, it would essentially need to analyze the code multiple times, one for each possible type of b
. You can make this happen explicitly by duplicating the code in a switch
/case
or if
/else
statement, but it doesn't happen automatically (which makes sense because it would lead to exponentially increasing compile times as the number of union-typed values in code increases), and you can't even ask the compiler to do it on an opt-in basis.
For now, the only reasonable workaround is to accept that you know more than the compiler about the type safety of the code, and to use a type assertion or the equivalent to tell the compiler not to worry about it, like this:
type MapVals = typeof map[keyof typeof map];
type LooselyTypedMapFunction = (...x: Parameters<MapVals>) => ReturnType<MapVals>
const test1 = (b: MyType) => {
(map[b.fun] as LooselyTypedMapFunction).apply(null, b.value);
};
Here the type LooseLyTypedMapFunction
ends up taking the types of f1
and f2
and blurring them into a single function that accepts and produces string | number
. And we use the type assertion map[b.fun] as LooselyTypedMapFunction
to lie a little bit to the compiler. We're telling it that map[b.fun]
will accept a string
and it will also accept a number
. This is false, of course, but will not run into a problem as long as you pass in the correlated b.value
list and not some random thing like `[Math.random()<0.5 ? "oops" : 123]).
That type assertion might be more work than you want to do, but at least it might catch if you passed anything too crazy into map[b.fun]
, since [string] | [number]
will at least prohibit [{foo: string}]
. You can use a less safe assertion like this:
const test2 = (b: MyType) => {
(map[b.fun] as Function).apply(null, b.value);
};
which doesn't require type juggling of a LooselyTypedMapFunction
but will also accept (map[b.fun] as Function).apply(null, [{foo: 123}]);
. So be careful.
Okay, hope that gives you some direction. Good luck!
Playground link to code
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…