From a type like this:
type Def = {
m1(a1: A1, b1: B1): R1
m2(a2: A2): R2
m3(a3: A3, b3: B3, c3: C3): R3
}
I would like to compute a type like this:
type F =
((name: 'm1', a1: A1, b1: B1) => R1) &
((name: 'm2', a2: A2) => R2) &
((name: 'm2', a3: A3, b3: B3, c3: C3) => R3)
that is the overloaded function type for a dispatcher method that would be dynamically generated.
I have managed to add the name parameter, i.e.:
type H = {
m1: (name: 'm1', a1: A1, b1: B1) => R1,
m2: (name: 'm2', a2: A2) => R2,
m3: (name: 'm3', a3: A3, b3: B3, c3: C3) => R3,
}
through simple object mapping and the Parameter and ReturnType helpers, but all my attempt at merging this into a single call signature have failed.
(see playground link)
>Solution :
Here’s one possible approach:
type IntersectMethods<T> = {
[K in keyof T]: T[K] extends (...args: infer A) => infer R ?
(x: ((name: K, ...args: A) => R)) => void :
never
} extends Record<string, (x: infer I) => void> ? I : never
This uses conditional type inference and infers intersections instead of unions because the type to infer has been moved to a contravariant position (see Difference between Variance, Covariance, Contravariance and Bivariance in TypeScript for more about variance). It’s essentially the same technique as in Transform union type to intersection type with the wrinkle that we are finding only the methods and prepending a name parameter to each one.
Let’s test it out:
type Def = {
m1(a1: A1, b1: B1): R1
m2(a2: A2): R2
m3(a3: A3, b3: B3, c3: C3): R3
}
type F = IntersectMethods<Def>
/* type F =
((name: "m1", a1: A1, b1: B1) => R1) &
((name: "m2", a2: A2) => R2) &
((name: "m3", a3: A3, b3: B3, c3: C3) => R3)
*/
Looks good!