Reputation: 2839
I am implementing a call
function that receives object and property name and then invokes function resided in passed object at passed property.
function call<
T,
K extends {
[K in keyof T]: T[K] extends Function ? K : never
}[keyof T],
>(t: T, func: K) {
t[func]();
}
Looks like ts is happy with it and correctly filters properties (no prop
, only do
available):
But it also shows an error on the line where I invoke function: t[func]();
:
Cannot invoke an expression whose type lacks a call signature.
Type '{}' has no compatible call signatures.
I have found similar issue [Type deduction using mapped types and generics] however it is closed as duplicate of another issue [Call signatures of union types]. Apparently problem is that second generic parameter is actually a union type of all property names that satisfy the condition.
Are there other ways to fix this except for casting to any
?
PS: Playground
Upvotes: 2
Views: 155
Reputation: 249646
Typescript will not be able to work out that your filtering of the properties will mean that T[K]
is always a function. You can reverse your constraint a bit, to specify that T
extends something that has properties of type ()=>void
function call<
T extends { [P in K]: ()=> void },
K extends keyof T
>(t: T, func: K) {
t[func]();
}
class Test {
prop: string = "";
doStuff(): void {}
}
call(new Test(), "prop") // error
call(new Test(), "doStuff") // ok
On note though, you might consider using an assertion in your original function, the version above while avoiding assertions and being type safe does not help intelisense figure out what possible values can be assigned to the second parameter, so instead of this:
While assertions should usually be avoided in this case we are sure that the type will actually be correct because we have constraints on key, so this version should be ok too:
function call2<
T,
K extends {
[K in keyof T]: T[K] extends ()=> void ? K : never
}[keyof T],
>(t: T, func: K) {
(t[func] as any as ()=> void)();
}
Upvotes: 1