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
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.
Most helpful comment
This is working as intended. The caller of
bardoes 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
baris to conservatively provide an argument which would work for either(payload: string) => void)or(payload: number) => void), which is precisely the intersection typestring & number.A concrete example