Given the following TypeScript code:
interface A { /* ... */ }
interface B extends A { /* ... */ }
type Mystery = A & B;
Objects of the Mystery
type should have all the properties of A
and all the properties of B
because it is an intersection type. Objects of type B
should already have all the properties of A
because B extends A
.
Given these definitions, is there a difference between type Mystery
and type B
?
To a first approximation, if B extends A
, then A & B
and B
are the same. The compiler will even consider A & B
and B
mutually assignable:
function foo<A, B extends A>(u: B, m: A & B) {
u = m; // okay
m = u; // okay
}
although not identical:
function foo<A, B extends A>(u: B, m: A & B) {
var v: B;
var v: A & B; // error, not considered identical
}
In practice there are going to be situations in which the compiler treats A & B
and B
differently. Some of these are compiler implementation details that are seen as bugs or design limitations; I'd have to go digging for these.
But one specific place where A & B
and B
could easily be different has to do with how intersections of call signatures are interpreted as overloaded functions which can be called in multiple ways, whereas extensions of interfaces with call signatures tend to just override the parent interface call signature and can only be called one way. For example:
interface A { method(p: any): void }
interface B extends A { method(): void }
This is allowed because functions of fewer parameters are assignable to functions of more parameters. The type B
only sees the zero-argument method, so you get the following behavior:
declare const a: A;
a.method(0); // okay
declare const b: B;
b.method(); // okay
b.method(0); // error!
Since method()
is overridden in B
, b.method()
with no arguments is an error (even though a zero-arg method is assignable to a multi-arg method, you still cannot intentionally call a function with too many arguments without a warning).
Compare this to the intersection:
type Mystery = A & B;
declare const m: Mystery;
m.method(""); // okay
m.method(); // okay
If you inspect m.method
, you'll see that it has two overloads:
// 1/2 method(p: any): void
// 2/2 method(): void
and therefore can be called in either of the two ways.