Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can intersection types contain conflicting types?

Tags:

typescript

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?

like image 427
joews Avatar asked Mar 11 '17 13:03

joews


People also ask

When would you use an intersection type instead of a union type?

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.

What do you understand by union type and intersection type?

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.


1 Answers

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.

like image 193
joews Avatar answered Sep 21 '22 21:09

joews