Given two interfaces with conflicting member types:
interface A { x: number }
interface B { x: string }
It is not possible to define an interface that extends both:
interface I extends A, B
// error TS2320: Interface 'I' cannot simultaneously extend types 'A' and 'B'.
// Named property 'x' of types 'A' and 'B' are not identical.
It is possible to define an intersection type that includes A
and B
:
let c = A & B
type C = A & B
// no type errors
Although it is not possible to create an instance of this type:
let withNumber: C = { x: 10 }
error TS2322: Type '{ x: number; }' is not assignable to type 'A & B'.
Type '{ x: number; }' is not assignable to type 'B'.
Types of property 'x' are incompatible.
Type 'number' is not assignable to type 'string'.
let withString: C = { x: "foo" }
// The same type error, with `number` and `string` reversed
Is there a technical reason that intersection types do not report conflicts when they are defined?
Intersection types are closely related to union types, but they are used very differently. An intersection type combines multiple types into one. This allows you to add together existing types to get a single type that has all the features you need.
Defined in the Advanced Types section of Typescript, an intersection type is a type that combines several types into one; a value that can be any one of several types is a union type. The & symbol is used to create an intersection, whereas the | symbol is used to represent a union.
type
operands can be type parameters; interface... extends
operands must be concrete types.
That distinction makes type
more flexible than interface
(it's pretty much the motivation for type
).
One consequence is that the type
operator cannot know its complete set of member types ahead of time, so conflicts like this example cannot be detected.
To quote Anders Hejlsberg,
Now, the ability for the operands to be type parameters also deeply affects the (possible) semantics of the operator. In particular, the operator has to consistently work for any two operands with an unknown set of members because it isn't possible to meaningfully report errors during type instantiation (i.e. when real types are substituted for type parameters). This what gives rise to the differences between & and extends. Because the actual types are always known with extends we can do more checking and forbid constructs that we deem wrong from a classical OOP perspective. For example, we can error when properties of the same name but different types conflict with extends, whereas we merge (i.e. intersect) their types with &.
For more information see a Github issue with a similar question and the pull request for intersection types.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With