Reputation: 3494
When I have a type A with two properties that are functions, and then define a type B that is the intersection type of type A with a new type that has the same function properties as A, how exactly does TS understand this? If this were done with interfaces it would be an error, because interface B would be have a call signature for b that would be incompatible with the call signature from interface A.
type A = {
a(x: number): string,
b(x: number): string
}
type B = A & {
a(x: string): string,
b(x: number): string
}
const b: B = {
a: (x: string | number) => x,
b: (x: number) => x
}
An error is shown on both properties of the const variable b, but not on the actual type definitions. So what would be a valid declaration of a variable of type B?
Upvotes: 3
Views: 1623
Reputation: 328758
TypeScript treats intersections of function types as function overloads. So each additional call signature for the same type (or, in your case above, each additional method declaration for the same name) behaves as an additional call signature in the list of overloads for that function (or method). This is not particularly clear from the documentation. There's a section in the (largely outdated) TypeScript specification document that says this about intersections of callable types:
The apparent members of an intersection type I are determined as follows... When one or more constituent types of I have a call signature S, I has the apparent call signature S. The signatures are ordered as a concatenation of the signatures of each constituent type in the order of the constituent types within I.
That basically means that an intersection of functions behaves like a function with each call signature from the intersection in the same order as in the intersection. And a function with an ordered list of call signatures is what "overloaded function" means, at least in TypeScript.
So, that intersection is not an error, but a way of specifying that you want B
to have an a
method with two differing signatures, and a b
method with two identical signatures, like this:
interface IB {
a(x: number): string;
a(x: string): string;
b(x: number): string;
b(x: number): string;
}
That's a bit weird, especially for b
, but not an error.
The reason why it doesn't work for A
as an interface and B extends A
is because extends
doesn't work exactly the same as an intersection. For B extends A
you're not allowed to declare incompatible properties at all. That's true even for non-functions. interface C {x: number | string}
and interface D extends C {x: string | boolean}
won't work because string | boolean
is not assignable to number | string
. It's not the same as the intersection (number | string) & (string | boolean)
which is just string
.
Anyway, you got an error in your b
variable. Why? Well, it's because both your a
and b
methods don't return the right type. Your a
returned string | number
and your b
returned number
. But all the methods of B
are supposed to return string
.
Let's fix that:
const b: B = {
a: (x: string | number) => String(x),
b: (x: number) => String(x)
} // no error now
Looks okay now. Okay, hope that helps; good luck!
Upvotes: 3