Sergey Sokolov
Sergey Sokolov

Reputation: 2839

Typescript. Filtering functional properties and `Cannot invoke an expression whose type lacks a call signature.`

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):

enter image description here

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

Answers (1)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

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:

enter image description here

You get this:enter image description here

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

Related Questions