1

Intersection of primitives

type foo = number;
type bar = boolean;

type baz = foo & bar;

There's no intersection with foo and bar. Thus baz type is never

Great, that makes sense.

Intersection of Objects

type foo = {
    fizz: number
};

type bar = {
    fuzz: boolean
};

type baz = foo & bar;

There are no properties in foo or bar that intersect. Just like in the primitive intersection example.

But instead of being a never type, the type enforces objects of type baz to have the combination of all properties of baz and bar.

For example:

type foo = {
    fizz: number
};

type bar = {
    fuzz: boolean
};

type baz = foo & bar;


// FAILS
const o: baz = {
    fuzz: true
}

// FAILS
const o: baz = {
    fizz: 1,
}

// PASSES
const o: baz = {
    fizz: 1,
    fuzz: true
}

To recap: The intersection of foo and bar in the primitive example has nothing. Thus, it's type was never.

The intersection of foo and bar in the object example has nothing. Yet, it's type is more of a union than an intersection. i.e. Objects of type baz require properties of both foo and bar.

Question

In the primitive example, it behaves like an intersection. Yet in the object example, it behaves not like an intersection. Why are these behaviors inconsistent?

Why does the intersection of objects behave like a union, and yet the intersection of primitives behave like a proper intersection?

GN.
  • 8,672
  • 10
  • 61
  • 126
  • There are no values that are of type `number & boolean`, but there are values that are of type `{ fizz: number } & { fuzz: number }`. Behaviour is consistent. In both cases it behaves like an intersection. – zerkms Mar 31 '21 at 23:16
  • 2
    This *is* what it means for object types to be intersected. A cat has paws and an apple has a stalk, so if something is a `Cat & Apple` then it must have both paws and a stalk. If it were a union (either a cat or an apple) then it would not necessarily have paws, and it would not necessarily have a stalk. – kaya3 Mar 31 '21 at 23:17
  • If it really did behave like a union `foo | bar`, then your two failing examples - which are assignable to `foo` and `bar` respectively - would not be failing examples, they would be acceptable as members of the union type. – kaya3 Mar 31 '21 at 23:20
  • Also relevant: [Naming of TypeScript's union and intersection types](https://stackoverflow.com/questions/38855908/naming-of-typescripts-union-and-intersection-types) – kaya3 Mar 31 '21 at 23:24
  • Please see [this answer](https://stackoverflow.com/a/59723040/2887218). Object types in TypeScript are open/extendable; they must contain the declared properties, but they may also contain undeclared properties. A value of type `{fizz: number}` must have a `fizz` property of type `number`, but it's not required that it *only* has a `fizz` property. A value `{fizz: 1, fuzz: 2}` is a valid `{fizz: number}`. So it is definitely possible to find a value of a type which is both a `{fizz: number}` and a `{fuzz: number}`... the *intersection* of those two types. – jcalz Apr 01 '21 at 01:50

1 Answers1

2

Why does the intersection of objects behave like a union, and yet the intersection of primitives behave like a proper intersection?

It's not an intersection of objects. It's an intersection of types. An intersection of types results in a new type that has all their properties, not their common properties.

A type intersection is not the same thing as a set intersection, which is where you're tripping up.

The other confusion is that you're trying to do an intersection of primitives (string, number). This is technically not correct, but TypeScript's design includes never erroring on the creation of a type. So their solution was to have the intersection of primitive types resolve to never rather than be a compile-time error.

user1713450
  • 1,307
  • 7
  • 18
  • 1
    A type intersection can be viewed as a set intersection - the set of values assignable to a type `A & B` is equal to the set of values assignable to `A` intersected with the set of values assignable to `B`. – kaya3 Mar 31 '21 at 23:25
  • 1
    @kaya3 The "set intersection" you're describing is a union of sets, not an intersection of sets. A set intersection is the collection of items that belong to BOTH, while a set union is the collection of items that belong to EITHER. (A type intersection is not the same) – user1713450 Apr 01 '21 at 04:49
  • If what you're saying were true, then if `type A = { foo: number}` and `type B = { bar: number}`, then `A & B` would be `{ }` because the intersection of the set of all values assignable to A and the set of all values assignable to B, intersected, is the null set. This is why I made the point that type intersection is not the same thing as set intersection. – user1713450 Apr 01 '21 at 04:52
  • No, it is a set intersection. For example, the object `{fizz: 1, fuzz: 'not a boolean'}` is assignable to `foo` but not to `bar`, so it is not in the intersection type `foo & bar`. – kaya3 Apr 01 '21 at 04:53
  • 1
    You are talking about the set of properties declared on the type, but I am talking about the set of values assignable to the type. The set of values assignable to both `foo` and `bar` is *absolutely not* empty; it contains the object `{fizz: 1, fuzz: true}`, for example. – kaya3 Apr 01 '21 at 04:53
  • you just wrinkled my brain – user1713450 Apr 01 '21 at 04:55
  • I hope it was at least a gentle wrinkling. – kaya3 Apr 01 '21 at 04:56