Reputation: 12223
I'm trying to make a general purpose switch expression function that can take any discriminated union type (for simplicity using a type property as the discriminator) and a map of its discriminator values to callback functions and return the result of the appropriate callback.
e.g.
type One = {
type: 'one',
numeric: number
};
type Two = {
type: 'two',
text: string
};
type Union = One | Two;
const union: Union = ... // some appropriate assignment
// The function switchExp should be aware of what the map should
// look like based on the type of its first arg. The argument
// passed to each callback should be properly discriminated based
// on the key in the map.
let result: number | string = switchExp(union, {
one: u => u.numeric, // compiler should know that u is of type One
two: u => u.text // compiler should know that u is of type Two
});
Upvotes: 2
Views: 102
Reputation: 250106
We can use a mapped type and the conditional type ReturnValue
to get the desired effect. There is a hitch however in the way we can infer the types for the function parameters. If we try to do it in a single function call the parameters will be typed as any
.
So for example this will not work as expected:
function switchExp2<T extends { type: string }, R extends { [P in (T["type"]]: (v: Extract<T, { type: P }>) => any }>(u: T, o: R): ReturnType<R[keyof R]> {
return null as any;
}
let result2 = switchExp2(union, {
one: u => u.numeric, // u is implictly tyed as any
two: u => u.text // u is implictly tyed as any
});
The compiler tries to infer T
from all possible sites and just gives up instead of getting to a conclusion. The simple solution is to fix T
first and then have a second call for the maping object:
function switchExp<T extends { type: string }>(u: T) {
return function <R extends { [P in T["type"]]: (v: Extract<T, { type: P }>) => any }>(o: R): ReturnType<R[keyof R]> {
return null as any; // replace with reasonable implementation
}
}
let result: number | string = switchExp(union)({
one: u => u.numeric, //u is of type One
two: u => u.text // u is of type Two
});
Upvotes: 3