Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
Arnavion opened this issue Sep 3, 2015 · 15 comments
Labels
Breaking Change Would introduce errors in existing code

Comments

@Arnavion
Copy link
Contributor

Arnavion commented Sep 3, 2015

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

@Arnavion
Copy link
Contributor Author

Arnavion commented Sep 3, 2015

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?

@mhegazy
Copy link
Contributor

mhegazy commented Sep 3, 2015

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

@mhegazy mhegazy added the Breaking Change Would introduce errors in existing code label Sep 3, 2015
@mhegazy mhegazy added this to the TypeScript 1.6 milestone Sep 3, 2015
@Arnavion
Copy link
Contributor Author

Arnavion commented Sep 3, 2015

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?

@xLama
Copy link

xLama commented Sep 3, 2015

@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.

@Arnavion
Copy link
Contributor Author

Arnavion commented Sep 3, 2015

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

@mhegazy
Copy link
Contributor

mhegazy commented Sep 3, 2015

@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

@mhegazy mhegazy closed this as completed Sep 3, 2015
@drarmstr
Copy link

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

@mhegazy
Copy link
Contributor

mhegazy commented Feb 11, 2016

@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.

@RyanCavanaugh
Copy link
Member

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

@drarmstr
Copy link

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:

@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.

@bjnsn
Copy link

bjnsn commented Mar 10, 2016

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.

@DeDuckProject
Copy link

DeDuckProject commented Aug 10, 2017

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;

@vartan
Copy link

vartan commented Jan 3, 2018

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");

@dkoprowski
Copy link

dkoprowski commented May 9, 2018

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.

@RyanCavanaugh
Copy link
Member

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

@microsoft microsoft locked and limited conversation to collaborators Jul 31, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Breaking Change Would introduce errors in existing code
Projects
None yet
Development

No branches or pull requests

9 participants