Reputation: 303
I was playing with the idea of a simple wrapper function using TypeScript. I ended up getting something working with the following:
export function logFn<T extends (...args: any[]) => any>(
fn: T,
): (...args: Parameters<T>) => ReturnType<T> {
const log = (...args: Parameters<T>): ReturnType<T> => {
console.log('log')
return fn(...args)
}
return log
}
This works and the compiler is happy. My question is around my initial attempt which looked more like this
export function logFn<T extends (...args: any[]) => any>(
fn: T,
): T {
const log: T = (...args) => {
console.log('log')
return fn(...args)
}
return log
}
This was giving me an error at the log
variable declaration with the error:
Type '(...args: any[]) => any' is not assignable to type 'T'.
'(...args: any[]) => any' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '(...args: any[]) => any'.
It seems that there's some subtype relationship constraint that I can't fully wrap my head around (this error isn't doing me too many favors either). Was hoping someone with a better mental grasp on this stuff could give me a decent explanation so I can be less confused by this behavior (which I'm sure is correct).
Upvotes: 4
Views: 499
Reputation: 1905
The "problem" is this: T extends (...args: any[]) => any
Its more visualisable if you see the below version:
export function logFn<T extends (...args: any[]) => any>(fn: T): T {
return (a, b) => fn(a, b);
}
You will see the error holds
Type '(a: any, b: any) => any' is not assignable to type 'T'. '(a: any, b: any) => any' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '(...args: any[]) => any'.
Both of the below are valid as far as T extends (...args: any[]) => any
goes
logFn((a, b) => a + b)
logFn((a, b, c) => c)
But if you refer back to the example I gave, the inner definition as:
return (a, b) => fn(a, b);
So option 2. will throw an error here, which is why typescript is warning you about it.
logFn<T extends (...args: any[]) => any>(fn: T): T
We are going to receive a type T
and return a type T
. return (a, b) => fn(a, b);
is a valid return type, it does extend (...args: any[]) => any
but how can you be sure that the value passed to fn
(T
) matches that signature? (i.e. it could be another incompatible subtype of (...args: any[]) => any
)
Not sure if I explained well enough, but thats my understanding
The reason your workaround is fine is because by adding (...args: Parameters<T>) => ReturnType<T>
you are telling the compiler the parameters & return types must match those of the passed function, as opposed to T
which could be any other function definition
Upvotes: 4