Every time you start typing something, ask yourself: "if I were a compiler, what would I expect?". If you analyze the problem of wrapping a function, you will notice you need to let the compiler know that:
- The
func
parameter satisfies the (...args: any[]) => any
constraint (i.e. "any function").
args
rest parameter has to be inferred (which you solved with Parameters
utility type).
- return type of the resulting function also has to be inferred (this one you missed). It can be easily achieved with
ReturnType
utility type.
What you did not have to tell the compiler, is what the key is - it is smart enough to infer the type of the function from usage. Now, how could you rewrite your type? Combine steps 1 to 3, and this is what you get:
const wrapper = <U extends (...args: any[]) => any>(func: U) => (...args: Parameters<U>) : ReturnType<U> => func(...args);
const one = wrapper(myFunctions.one); //() => number
const two = wrapper(myFunctions.two); //() => number
const echo = wrapper(myFunctions.echo); //(str: string) => string
The resulting generic is not restricted to your myFunctions
type:
const rand = wrapper((a:string,b:number,c:boolean) => {}); //(a: string, b: number, c: boolean) => void
If you need to only accept functions from myFunctions
type, just let the compiler know that U
must match exactly one of the signatures from myFunctions
members.
To achieve that, you need to guarantee that the types are "identical". This can be done with a dual conditional type. Its general form looks something like this:
A extends B ? B extends A ? C : never : never;
Let's apply the idea to the use case and create a helper type:
type IsOneOf<T,F> = { [ P in keyof T ] : F extends T[P] ? T[P] extends F ? T[P] : never : never; }[keyof T];
The above will resolve to never
if there are no matching members of T
and to matching members otherwise. Since nothing is compatible with never
, we get the desired behavior:
type IsOneOf<T,F> = { [ P in keyof T ] : F extends T[P] ? T[P] extends F ? T[P] : never : never; }[keyof T];
const wrapper = <U extends (...args: any[]) => any>(func: U extends IsOneOf<myFunctions, U> ? U : never) => (...args: Parameters<U>) : ReturnType<U> => func(...args);
const one = wrapper(myFunctions.one);
const two = wrapper(myFunctions.two);
const echo = wrapper(myFunctions.echo);
const rand = wrapper((a:string,b:number,c:boolean) => {}); //not assignable to parameter of type 'never'.
const hm = wrapper(() => 'a'); //also error
Playground