Intersections
Sometimes it is useful to create a type which is all of a set of other types. For example, you might want to write a function which accepts a value that implements two different interfaces:
1interface Serializable {2 serialize(): string;3}4
5interface HasLength {6 length: number;7}8
9function func(value: Serializable & HasLength) {10 // ...11}12
13func({14 length: 3,15 serialize() {16 return '3';17 },18}); // Works19
20func({length: 3}); // Error! Doesn't implement both interfaces
20:6-20:16: Cannot call `func` with object literal bound to `value` because property `serialize` is missing in object literal [1] but exists in `Serializable` [2]. [prop-missing]
Intersection type syntax
Intersection types are any number of types which are joined by an ampersand &
.
Type1 & Type2 & ... & TypeN
You may also add a leading ampersand which is useful when breaking intersection types onto multiple lines.
type Foo =
& Type1
& Type2
& ...
& TypeN
Each of the members of a intersection type can be any type, even another intersection type.
type Foo = Type1 & Type2;
type Bar = Type3 & Type4;
type Baz = Foo & Bar;
Intersection types require all in, but one out
Intersection types are the opposite of union types. When calling a function that accepts an intersection type, we must pass in all of those types. But inside of our function we only have to treat it as any one of those types.
1type A = {a: number, ...};2type B = {b: boolean, ...};3type C = {c: string, ...};4
5function func(value: A & B & C) {6 const a: A = value;7 const b: B = value;8 const c: C = value;9}
Even as we treat our value as just one of the types, we do not get an error because it satisfies all of them.
Intersection of function types
A common use of intersection types is to express functions that return different results based on the input we pass in. Suppose for example that we want to write the type of a function that:
- returns a string, when we pass in the value
"string"
, - returns a number, when we pass in the value
"number"
, and - returns any possible type (
mixed
), when we pass in any other string.
The type of this function will be
1type Fn =2 & ((x: "string") => string)3 & ((x: "number") => number)4 & ((x: string) => mixed);
Each line in the above definition is called an overload, and we say that functions
of type Fn
are overloaded.
Note the use of parentheses around the arrow types. These are necessary to override the precedence of the "arrow" constructor over the intersection.
Calling an overloaded function
Using the above definition we can declare a function fn
that has the following behavior:
1declare const fn:2 & ((x: "string") => string)3 & ((x: "number") => number)4 & ((x: string) => mixed);5
6const s: string = fn("string"); // Works7const n: number = fn("number"); // Works8const b: boolean = fn("boolean"); // Error!
8:20-8:32: Cannot assign `fn(...)` to `b` because mixed [1] is incompatible with boolean [2]. [incompatible-type]
Flow achieves this behavior by matching the type of the argument to the first
overload with a compatible parameter type. Notice for example that the argument
"string"
matches both the first and the last overload. Flow will
just pick the first one. If no overload matches, Flow will raise an error at the
call site.
Declaring overloaded functions
An equivalent way to declare the same function fn
would be by using consecutive
"declare function" statements
1declare function fn(x: "string"): string;2declare function fn(x: "number"): number;3declare function fn(x: string): mixed;
A limitation in Flow is that it can't check the body of a function against
an intersection type. In other words, if we provided the following implementation
for fn
right after the above declarations
1function fn(x: mixed) {2 if (x === "string") { return ""; }3 else if (x === "number") { return 0; }4 else { return null; }5}
Flow silently accepts it (and uses Fn
as the inferred type), but does not check
the implementation against this signature. This makes this kind of declaration
a better suited candidate for library definitions, where implementations are omitted.
Intersections of object types
When you create an intersection of inexact object types, you are saying that your object satisfies each member of the intersection.
For example, when you create an intersection of two inexact objects with different sets of properties, it will result in an object with all of the properties.
1type One = {foo: number, ...};2type Two = {bar: boolean, ...};3
4type Both = One & Two;5
6const value: Both = {7 foo: 1,8 bar: true9};
When you have properties that overlap by having the same name, Flow follows the same strategy as with overloaded functions: it will return the type of the first property that matches this name.
For example, if you merge two inexact objects with a property named prop
, first with a
type of number
and second with a type of boolean
, accessing prop
will return
number
.
1type One = {prop: number, ...};2type Two = {prop: boolean, ...};3
4declare const both: One & Two;5
6const prop1: number = both.prop; // Works7const prop2: boolean = both.prop; // Error!
7:24-7:32: Cannot assign `both.prop` to `prop2` because number [1] is incompatible with boolean [2]. [incompatible-type]
To combine exact object types, you should use object type spread instead:
1type One = {foo: number};2type Two = {bar: boolean};3
4type Both = {5 ...One,6 ...Two,7};8
9const value: Both = {10 foo: 1,11 bar: true12};
Note: When it comes to objects, the order-specific way in which intersection types are implemented in Flow, may often seem counter-intuitive from a set theoretic point of view. In sets, the operands of intersection can change order arbitrarily (commutative property). For this reason, it is a better practice to define this kind of operation over object types using object type spread where the ordering semantics are better specified.
Impossible intersection types
Using intersection types, it is possible to create types which are impossible to create at runtime. Intersection types will allow you to combine any set of types, even ones that conflict with one another.
For example, you can create an intersection of a number and a string.
1type NumberAndString = number & string;2
3function func(value: NumberAndString) { /* ... */ }4
5func(3.14); // Error! 6func('hi'); // Error!
5:6-5:9: Cannot call `func` with `3.14` bound to `value` because number [1] is incompatible with string [2]. [incompatible-call]6:6-6:9: Cannot call `func` with `'hi'` bound to `value` because string [1] is incompatible with number [2]. [incompatible-call]
But you can't possibly create a value which is both a number and a string, but you can create a type for it. There's no practical use for creating types like this, but it's a side effect of how intersection types work.
An accidental way to create an impossible type is to create an intersection of exact object types. For example:
1function func(obj: {a: number} & {b: string}) { /* ... */ }2
3func({a: 1}); // Error! 4func({b: 'hi'}); // Error! 5func({a: 1, b: 'hi'}); // Error!
3:6-3:11: Cannot call `func` with object literal bound to `obj` because property `a` is missing in object type [1] but exists in object literal [2]. [prop-missing]3:6-3:11: Cannot call `func` with object literal bound to `obj` because property `b` is missing in object literal [1] but exists in object type [2]. [prop-missing]4:6-4:14: Cannot call `func` with object literal bound to `obj` because property `a` is missing in object literal [1] but exists in object type [2]. [prop-missing]4:6-4:14: Cannot call `func` with object literal bound to `obj` because property `b` is missing in object type [1] but exists in object literal [2]. [prop-missing]5:6-5:20: Cannot call `func` with object literal bound to `obj` because property `a` is missing in object type [1] but exists in object literal [2]. [prop-missing]5:6-5:20: Cannot call `func` with object literal bound to `obj` because property `b` is missing in object type [1] but exists in object literal [2]. [prop-missing]
It's not possible for an object to have exactly the property a
and no other
properties, and simultaneously exactly the property b
and no other properties.