This is a known bug in TypeScript; see microsoft/TypeScript#36874. It looks like the problem is triggered by F extends (...arg: any) => any instead of F extends (...arg: any[]) => any. That is, your arg rest parameter is of the anything-at-all any type instead of the array-of-anything any[] type. The obvious workaround is therefore to change any to any[], which shouldn't be a problem because function rest parameters are essentially always arraylike:
function call<F extends (...arg: any[]) => any, P extends Parameters<F>>(
fn?: F, ...arg: P): ReturnType<F> | undefined {
return fn?.(...arg) // okay (I used optional chaining here to deal with missing fn)
}
Looks good!
That's the answer to the question as asked, although it is useful to note that generally speaking, the preferred way to do this is to make call() generic in P (the rest parameter type) and R (the return type), and not use the Parameters<T> and the ReturnType<T> utility types:
function call<P extends any[], R>(fn?: (...arg: P) => R, ...arg: P): R | undefined {
return fn?.(...arg); // okay
}
They are similar, but your approach ends up being unable to properly represent what happens when fn is itself generic:
const pair = <T, U>(left: T, right: U): [T, U] => [left, right];
const ret = call(pair, "abc", 123); // old version of call
// const ret: [any, any] | undefined ☹
whereas the approach I recommend can take advantage of the support for higher order type inference from generic functions:
const ret = call(pair, "abc", 123); // new version of call
// const ret: [string, number] | undefined 👍
If you can rewrite generics using conditional types like Parameters<T> and ReturnType<T>, it will sometimes improve the compiler's ability to produce desirable results.
Playground link to code