I need to be able to infer the argument type of a function that is passed as a parameter, based on another parameter where the type is known. It's kinda hard to explain so I wrote a little demo , reproduced here:
interface UnaryFunction<In, Out> {
(arg: In): Out
}
interface WithNumber<In, Out> extends UnaryFunction<In, Out> {
num: number
}
function testExtends<Arg, Fn extends UnaryFunction<Arg, any>>(arg: Arg, fn: Fn): Fn {
return fn;
}
function testInferred<Arg, Out>(arg: Arg, fn: UnaryFunction<Arg, Out>): typeof fn {
return fn
}
function mapWithCaller<From, To>(morphism: UnaryFunction<From, To>): WithNumber<From, To> {
return Object.assign((x: From) => morphism(x), {num: 5})
}
// Error: property length does not exist
testExtends('1', mapWithCaller(x => x.length)).num
// Error: property num does not exist
testInferred('1', mapWithCaller(x => x.length)).num
As you can see, with testExtends
the return type of the function is preserved, while with testInferred
the parameter type of the passed function is inferred.
I need to be able to combine the best of both worlds, and it's driving me crazy, is this not possible at all? Is it a bug? By design?
The problem with testInferred
's return type is fairly obvious - it's non-generic and the return type is fixed at exactly UnaryFunction
The problem with testExtends
is more complex. The root cause here is that you need the argument to mapWithCaller
to be contextually typed by a function type that is capable of providing a contextual type to the argument.
You actually have a somewhat indirect contextual type coming in here because mapWithCaller
is itself generic, but because the other argument to testInferred
is provided and TypeScript can route the expected return type of mapWithCaller
back into its arguments, it's not a problem.
Anyway, testInferred
works here because UnaryFunction<In, Out>
is a non-generic function type with a single call signature.
Why doesn't testExtends
work for this? Because it's not safe to apply a contextual type to a parameter based on the bound in the extends
clause. Consider this call, which is legal (apart from the error in the lambda expression):
interface OtherFunction<Out> {
(arg: string | number): Out
}
testExtends<string, OtherFunction<number>>('1', mapWithCaller(x => x.length));
Based on a legal generic instantiation here , mapWithCaller
is allowed to pass a number
to its callback. TypeScript can't really assume that given a string argument in one position, the correct inferred bound for the function expression is exactly that.
I don't think there's a way to write the function declaration here in a way that preserves both behaviors you want, but I could be wrong.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.