简体   繁体   中英

Typescript cannot infer correct argument types for an object of functions

I've written an update function that takes an object, T , and an "updater" object, which takes the same keys (or a subset) as T and provides a function for updating the corresponding value of the original object.

type UpdaterObj<T> = {
    [K in keyof Partial<T>]: (t: T[K]) => T[K]
};
declare function update<T>(obj: T, updaterObj: UpdaterObj<T>): T

Sample usage is like this:

const foo = {
    x: 1,
    y: "1"
};

update(foo, {
    x: (xx: number) => xx + xx,
    y: (yy: string) => yy + yy,
}); // Result is { x: 2, y: "11" }

This works, but I'd really like to remove those annotations and just write x: (xx) => xx + xx and y: (yy) => yy + yy , however I get the Parameter implicitly has type 'any' error, for both xx and yy .

It's odd because if I mouse over x: , it correctly infers what the corresponding value should but, and it complains if I annotate xx or yy as the wrong type, and I'm pretty sure I've seen typescript make similar inferences before.

The annotations are pretty simple in this case, but can get annoying if I'm deep within objects or with more complex types, so inference would be really helpful here.

Is there any simple tweak to my definitions or my usage that can get typescript to infer this for me?

I'm not sure if there's an existing issue in GitHub about how type inference of function parameters interacts with inference for generic type parameters, so I don't know if there's a simple tweak of the function signature which makes it work without affecting the call site.

Looking at the TypeScript spec , I see that for TypeScript function expressions (like xx => xx + xx ),

When a function expression with no type parameters and no parameter type annotations is contextually typed by a type T and a contextual signature S can be extracted from T , the function expression is processed as if it had explicitly specified parameter type annotations as they exist in S .

[snip]

A contextual signature S is extracted from a function type T as follows:

  • If T is a function type with exactly one call signature, and if that call signature is non-generic , S is that signature.

  • If T is a union type... [snip]

  • Otherwise, no contextual signature can be extracted from T .

So it kind of looks like since ( xx => xx + xx ) in your original call has a function type of the form (t: T['x']) => T['x'] that includes the type parameter T , it doesn't process the function as if it had explicit type parameters. Presumably that means the compiler tries to extract a contextual signature before it eliminates the generic parameter.


If it's okay to change the call site, then the simplest suggestion I can make which demonstrably works is to break the two-argument update() function into a curried function like so:

declare function updateCurried<T>(obj: T): (updaterObj: UpdaterObj<T>) => T

which would be used as follows (note the sequence of function calls):

updateCurried(foo)({
  x: xx => xx + xx,
  y: yy => yy + yy,
}); // okay

This essentially forces the compiler to evaluate and eliminate the generic type parameter by the time it gets to evaluating the updaterObj prarameter, at which point it can do the contextual function parameter inference as described in the above excerpt from the spec.

Hope that's helpful to you. Good luck!

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