简体   繁体   中英

Typescript: Infer type for parameter of passed function

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM