I think it's clearer to try it on TS Playground:
function identity<T extends (...args: any[]) => any>(fn: T): T {
return fn;
}
function fn(args: {
cb: (foo: number) => void,
}) {}
fn({
cb: identity((foo /* infers number */) => {}),
});
function fn2(args: {
cb?: (foo: number) => void,
}) {}
fn2({
cb: identity((foo /* doesn't infer number */) => {}),
});
function fn3(args: {
cb: (foo: number) => void,
} | {}) {}
fn3({
cb: identity((foo /* infers number */) => {}),
});
For fn and fn3, TS was able to infer that foo is a number. However, for fn2, TS just typed foo as any. The typing for fn2 and fn3 are functionally the same thing, so I'm wondering why TS couldn't infer the type of foo.
The realistic use-case for this is React's useCallback, I was trying to infer the argument types for functions that pass through useCallback.
Why does TS behave like this? Is there a less hacky solution?