I think this is a simplification the compiler makes when performing certain operations on generic types. Instead of representing every operation as a possibly increasingly complex generic type, it widens the generic type parameter to its constraint and uses that. You can see this happen when you index into a generic-typed object with a key it's known to have:
function foo<T extends { a: any }>(obj: T) {
const a1 = obj.a; // any, why not T['a']?
const a2: T['a'] = obj.a; // this works though
}
See microsoft/TypeScript#33181 for more information. In the above, the compiler sees obj.a and widens obj from T to {a: any} before accessing its a property. So a1 is of type any. If the compiler had instead deferred the widening, it could have represented this property as the lookup type T['a']. And indeed, if you explicitly annotate the variable you're saving it to as T['a'], the compiler does not complain.
The same seems to happen for calling a function of a generic type (although I haven't found canonical documentation mentioning this):
function bar<T extends () => any>(fn: T) {
const r1 = fn(); // any, why not ReturnType<T> ?
const r2: ReturnType<T> = fn(); // this works though
}
As you can see, r1 is of type any because the compiler widens fn from T to its constraint, () => any, before it is called. If the compiler had instead deferred the widening, it could have represented the return type as ReturnType<T> (see documentation for ReturnType). And again, if you manually annotate the value as ReturnType<T>, the compiler does not complain about it.
This leads me to what I think is the right solution/workaround for you: manually annotate the return type of your function:
const f = <T extends () => any>(callback: T): ReturnType<T> => callback()
That compiles with no error, and now when you call f on a callback you get a better return type:
const r = f(() => 1); // number
Playground link to code