Skip to content

Union of function signatures cannot be invoked because it lacks a call signature #4612

Description

@Arnavion

Reduced test case:

var foo: ((arg: string) => void) | ((arg: any) => void);
foo(null);
  1. With 1.5.3, this compiles fine. With 1.6-beta, this gives error TS2349: Cannot invoke an expression whose type lacks a call signature. null is assignable to both string and any so it should compile.
  2. Maybe related - getting quickinfo for foo on the second line does throw an error TypeError: Cannot read property 'flags' of undefined in playground even though it compiles fine with 1.5.3. I didn't try quickinfo on 1.6
  3. If foo was ((arg: string) => void) | ((arg: number) => void); (number instead of any in the second signature) then both 1.5.3 and 1.6 give that error, even though again null is assignable to both.

For motivation, the original code is https://github.com/Arnavion/libjass/blob/6606413/src/utility/promise.ts#L363 (the error is on handler). It's an implementation of the ES6 promise spec, specifically http://www.ecma-international.org/ecma-262/6.0/#sec-promisereactionjob - handler would be either the fulfilled callback or rejected callback and the argument passed to it would be either the resolution value or the rejection reason accordingly, hence its signature.

(I understand it would be more type-safe to have separate handlers for the two cases with one signature each from the union, but that complicates the implementation slightly.)

Activity

  1. Arnavion commented on Sep 3, 2015

    @Arnavion
    ContributorAuthor

    I guess it has to do with the fact that the union type doesn't contain call signatures unless the signature parameters match exactly in all constituents types 3.11.1. So does that mean the bug was in 1.5.3 to allow it to compile in the first place?

  2. mhegazy commented on Sep 3, 2015

    @mhegazy
    Contributor

    This a result from removing the aggressive subtype reduction that was in 1.5.* (see #3823). Now we are not reducing to subtypes, so the type still contain the two signatures. at time when the call resolution happen 3.11.1 takes effect, and no call signatures are found.

    This is a breaking change from 1.5.3 (will add it to the docs).

    Also related to #3823

  3. Arnavion commented on Sep 3, 2015

    @Arnavion
    ContributorAuthor

    Does not reducing to a subtype with one signature help prevent any errors?

    And is there a way to make this work with 1.6 while still keeping the union type?

  4. xLama commented on Sep 3, 2015

    @xLama

    Arnav Singh (@Arnavion)

    You could use call, apply or bind. I know it´s very ugly but It works.

    var foo: ((arg: string) => void) | ((arg: number) => void) = function(a:string):void{alert(a)};
    var foo2: ((arg: string) => void) | ((arg: number) => void) = function(a:number):void{alert(a)};
    
    foo.call(this, "a"); // Be careful with "this". It´s a context.
    foo2.call(this, 1);  // Be careful with "this". It´s a context.
    
    
  5. Arnavion commented on Sep 3, 2015

    @Arnavion
    ContributorAuthor

    Yeah. Splitting into two handlers is cleaner, so I'll go with that.

  6. mhegazy commented on Sep 3, 2015

    @mhegazy
    Contributor

    Arnav Singh (@Arnavion), the aggressive subtype reduction would causes cause these errors to disappear, but it also caused loss of information from the union type. the fix was to keep the union information as long as possible. Subtype reduction now is only happening on the lever of operators like conditional and Or (see #3823). in this sense something like:

    var foo1: ((arg: string) => void)
    var foo2:  ((arg: any) => void);
    
    var foo = foo1 || foo2; 
    foo(null); // works
  7. drarmstr commented on Feb 11, 2016

    @drarmstr

    Looks like we also get this error when both types are derived from the same base class and we try calling a method of the base class. Is that expected as well?

    class Base {
        foo() { }
    }
    
    class Derived1 extends Base { }
    class Derived2 extends Base { }
    
    var spam : Derived1 | Derived2 = ...
    spam.foo(); <- error lacking call signature
    
  8. mhegazy commented on Feb 11, 2016

    @mhegazy
    Contributor

    Douglas Armstrong (@drarmstr), this should not be an error. i think there is something else going on. also this is an old issue. please file a new one with more information about your code and the version of the compiler you are using.

  9. RyanCavanaugh commented on Feb 12, 2016

    @RyanCavanaugh
    Member

    Douglas Armstrong (@drarmstr) that code doesn't issue an error when I compile with the latest TypeScript

  10. drarmstr commented on Feb 13, 2016

    @drarmstr

    Yeah, the scenario was from a much more complicated code base. If I can get a simpler test case to reproduce then I'll submit a new issue. I switched to using an interface for the common methods, though, and it's been working for me.

    On Feb 12, 2016, at 12:23, Ryan Cavanaugh notifications@github.com wrote:

    Douglas Armstrong (@drarmstr) that code doesn't issue an error when I compile with the latest TypeScript

    —
    Reply to this email directly or view it on GitHub.

  11. bjnsn commented on Mar 10, 2016

    @bjnsn

    Hmmm. A scenario like following still throws this error in 1.8.7, which doesn't seem like it should:

    var arr:(Array<number>|Array<string>) = [5];
    arr.map(String);
    

    However if making it more permissive:

    var arr:Array<number|string> = [5];
    arr.map(String);
    

    or restricted:

    var arr:Array<number> = [5];
    arr.map(String);
    

    fixes the error.

  12. DeDuckProject commented on Aug 10, 2017

    @DeDuckProject

    It seems this is still an issue in TS 2.4.2

    export type MyControllerHandler = (e:MyValueEvent)=>void;
    export type MyPatchHandler = (e: MyPatchEvent)=>void;
    
    export type MyHandler = MyPatchHandler | MyControllerHandler;
    
    
    declare const onChange: MyHandler;
    onChange({}as any);  // Error:(42, 17) TS2349:Cannot invoke an expression whose type lacks a call signature. Type 'MyHandler' has no compatible call signatures.

    This fixes the error:

    export type MyHandler = (e:MyValueEvent | MyPatchEvent)=>void;
  13. vartan commented on Jan 3, 2018

    @vartan

    I am running into this in 2.6.2, it seems odd to me that this doesn't work. Is this working as intended?

    Here's pretty simple example:

    class TestClass {
        public foo(x: string | number) {
        }
    }
    class TestClass2 {
        public foo(x: string) {
        }
    }
    const bar: (TestClass | TestClass2) = new TestClass();
    bar.foo("baz"); // Cannot invoke an expression whose type lacks a call signature. 
                    // Type '((x: string | number) => void) |
                    //       ((x: string) => void)' has no compatible call signatures.

    I am getting around it currently by casting to an interface with the "correct" union.

    interface FooShim {
        foo(x: string | number): void;
    }
    (bar as FooShim).foo("baz");
  14. dkoprowski commented on May 9, 2018

    @dkoprowski

    I have this issue in 2.8.1, those workaround doesn't apply for me because I would like to overload a function.

    Example of my overload:

    type A = (b: boolean) => void;
    type B = (a: string, b?: boolean) => void;
    
    interface Props {
        handler: A | B
    }
    
    (bar as Props).handler(false);  //TS2349: Cannot invoke an expression whose type lacks a call signature. Type 'A | B' has no compatible call signatures.
  15. RyanCavanaugh commented on May 9, 2018

    @RyanCavanaugh
    Member

    Daniel Koprowski (@dkoprowski) You want (bar.handler as A)(false)

  16. locked and limited conversation to collaborators on Jul 31, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Breaking ChangeWould introduce errors in existing code

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions