Typescript: Union function type failure (ts2345)

Created on 9 May 2019  ·  3Comments  ·  Source: microsoft/TypeScript

TypeScript Version: 3.4.0-dev.201xxxxx

Code

interface Foo {
    bar: ((payload: string) => void) | ((payload: number) => void);
    //foo: (payload: string | number) => void; // This work fine.
}
const foo: Foo = {
    bar() {
        // payload
    },
};
foo.bar(0);

Expected behavior:
No Error

Actual behavior:
Error message:
>

Argument of type '0' is not assignable to parameter of type 'string & number'.
Type '0' is not assignable to type 'string'.

Playground Link: https://www.typescriptlang.org/play/#src=interface%20Foo%20%7B%0D%0A%20%20%20%20bar%3A%20((payload%3A%20string)%20%3D%3E%20void)%20%7C%20((payload%3A%20number)%20%3D%3E%20void)%3B%0D%0A%20%20%20%20%2F%2Ffoo%3A%20(payload%3A%20string%20%7C%20number)%20%3D%3E%20void%3B%20%2F%2F%20This%20work%20fine.%0D%0A%7D%0D%0Aconst%20foo%3A%20Foo%20%3D%20%7B%0D%0A%20%20%20%20bar()%20%7B%0D%0A%20%20%20%20%20%20%20%20%2F%2F%20payload%0D%0A%20%20%20%20%7D%2C%0D%0A%7D%3B%0D%0Afoo.bar(0)%3B

Most helpful comment

This is working as intended. The caller of bar does not know if the function will be implemented as (payload: string) => void) _or_ (payload: number) => void) as the callee (Foo) is free to choose either.

As a consequence, the only valid behaviour when calling bar is to conservatively provide an argument which would work for either (payload: string) => void) or (payload: number) => void), which is precisely the intersection type string & number.

A concrete example

// Both are legal instance of Foo
const foo1: Foo = {
    bar(s: string) {
        console.log(s.toLocaleLowerCase())
    },
};
foo1.bar(0); // This would be wrong!

const foo2: Foo = {
    bar(n: number) {
        console.log(n + 10);
    },
};
foo2.bar("hello"); // This would also be wrong!

All 3 comments

This is working as intended. The caller of bar does not know if the function will be implemented as (payload: string) => void) _or_ (payload: number) => void) as the callee (Foo) is free to choose either.

As a consequence, the only valid behaviour when calling bar is to conservatively provide an argument which would work for either (payload: string) => void) or (payload: number) => void), which is precisely the intersection type string & number.

A concrete example

// Both are legal instance of Foo
const foo1: Foo = {
    bar(s: string) {
        console.log(s.toLocaleLowerCase())
    },
};
foo1.bar(0); // This would be wrong!

const foo2: Foo = {
    bar(n: number) {
        console.log(n + 10);
    },
};
foo2.bar("hello"); // This would also be wrong!

@jack-williams Thanks!

Is this code also same reason:

type Foo<T> = T extends never ? never : (payload: T) => T;
const foo: Foo<string | number> = payload => payload;
foo(1);

Yep! The type Foo<string | number> evaluates to ((payload: string) => void) | ((payload: number) => void) because that is the behaviour of distrubuted conditional types.

One you have the union of function types, the reason for the error is exactly the same.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

dlaberge picture dlaberge  ·  3Comments

uber5001 picture uber5001  ·  3Comments

fwanicka picture fwanicka  ·  3Comments

jbondc picture jbondc  ·  3Comments

CyrusNajmabadi picture CyrusNajmabadi  ·  3Comments