|
| 1 | +--- |
| 2 | +layout: doc-page |
| 3 | +title: "Union Types - More Details" |
| 4 | +--- |
| 5 | + |
| 6 | +## Syntax |
| 7 | + |
| 8 | +Syntactically, unions follow the same rules as intersections, but have a lower precedence, see |
| 9 | +[Intersection Types - More Details](http://lampepfl.github.io/dotty/docs/reference/intersection-types-spec.html). |
| 10 | + |
| 11 | +### Interaction with pattern matching syntax |
| 12 | +`|` is also used in pattern matching to separate pattern alternatives and has |
| 13 | +lower precedence than `:` as used in typed patterns, this means that: |
| 14 | + |
| 15 | +``` scala |
| 16 | +case _: A | B => ... |
| 17 | +``` |
| 18 | +is still equivalent to: |
| 19 | +``` scala |
| 20 | +case (_: A) | B => ... |
| 21 | +``` |
| 22 | +and not to: |
| 23 | +``` scala |
| 24 | +case _: (A | B) => ... |
| 25 | +``` |
| 26 | + |
| 27 | +## Subtyping Rules |
| 28 | + |
| 29 | +- `A` is always a subtype of `A | B` for all `A`, `B`. |
| 30 | +- If `A <: C` and `B <: C` then `A | B <: C` |
| 31 | +- Like `&`, `|` is commutative and associative: |
| 32 | + ```scala |
| 33 | + A | B =:= B | A |
| 34 | + A | (B | C) =:= (A | B) | C |
| 35 | + ``` |
| 36 | +- `&` is distributive over `|`: |
| 37 | + ```scala |
| 38 | + A & (B | C) =:= A & B | A & C |
| 39 | + ``` |
| 40 | + |
| 41 | +From these rules it follows that the _least upper bound_ (lub) of a set of type |
| 42 | +is the union of these types. This replaces the |
| 43 | +[definition of least upper bound in the Scala 2 specification](https://www.scala-lang.org/files/archive/spec/2.12/03-types.html#least-upper-bounds-and-greatest-lower-bounds). |
| 44 | + |
| 45 | +## Motivation |
| 46 | + |
| 47 | +The primary reason for introducing union types in Scala is that they allow us to |
| 48 | +guarantee that for every set of type, we can always form a finite lub. This is |
| 49 | +both useful in practice (infinite lubs in Scala 2 were approximated in an ad-hoc |
| 50 | +way, resulting in imprecise and sometimes incredibly long types) and in theory |
| 51 | +(the type system of Scala 3 is based on the |
| 52 | +[DOT calculus](https://infoscience.epfl.ch/record/227176/files/soundness_oopsla16.pdf), |
| 53 | +which has union types). |
| 54 | + |
| 55 | +Additionally, union types are a useful construct when trying to give types to existing |
| 56 | +dynamically typed APIs, this is why they're [an integral part of TypeScript](https://www.typescriptlang.org/docs/handbook/advanced-types.html#union-types) |
| 57 | +and have even been [partially implemented in Scala.js](https://github.com/scala-js/scala-js/blob/master/library/src/main/scala/scala/scalajs/js/Union.scala). |
| 58 | + |
| 59 | +## Join of a union type |
| 60 | + |
| 61 | +In some situation described below, a union type might need to be widened to |
| 62 | +a non-union type, for this purpose we define the _join_ of a union type `T1 | |
| 63 | +... | Tn` as the smallest intersection type of base class instances of |
| 64 | +`T1`,...,`Tn`. Note that union types might still appear as type arguments in the |
| 65 | +resulting type, this guarantees that the join is always finite. |
| 66 | + |
| 67 | +### Example |
| 68 | + |
| 69 | +Given |
| 70 | + |
| 71 | +```scala |
| 72 | +trait C[+T] |
| 73 | +trait D |
| 74 | +class A extends C[A] with D |
| 75 | +class B extends C[B] with D with E |
| 76 | +``` |
| 77 | + |
| 78 | +The join of `A | B` is `C[A | B] with D` |
| 79 | + |
| 80 | +## Type inference |
| 81 | + |
| 82 | +When inferring the result type of a definition (`val`, `var`, or `def`), if the |
| 83 | +type we are about to infer is a union type, we replace it by its join. |
| 84 | +Similarly, when instantiating a type argument, if the corresponding type |
| 85 | +parameter is not upper-bounded by a union type and the type we are about to |
| 86 | +instantiate is a union type, we replace it by its join. This mirrors the |
| 87 | +treatment of singleton types which are also widened to their underlying type |
| 88 | +unless explicitly specified. and the motivation is the same: inferring types |
| 89 | +which are "too precise" can lead to unintuitive typechecking issues later on. |
| 90 | + |
| 91 | +Note: Since this behavior severely limits the usability of union types, it might |
| 92 | +be changed in the future. For example by not widening unions that have been |
| 93 | +explicitly written down by the user and not inferred, or by not widening a type |
| 94 | +argument when the corresponding type parameter is covariant. See |
| 95 | +[#2330](https://github.com/lampepfl/dotty/pull/2330) and |
| 96 | +[#4867](https://github.com/lampepfl/dotty/issues/4867) for further discussions. |
| 97 | + |
| 98 | +### Example |
| 99 | + |
| 100 | +```scala |
| 101 | +import scala.collection.mutable.ListBuffer |
| 102 | +val x = ListBuffer(Right("foo"), Left(0)) |
| 103 | +val y: ListBuffer[Either[Int, String]] = x |
| 104 | +``` |
| 105 | + |
| 106 | +This code typechecks because the inferred type argument to `ListBuffer` in the |
| 107 | +right-hand side of `x` was `Left[Int, Nothing] | Right[Nothing, String]` which |
| 108 | +was widened to `Either[Int, String]`. If the compiler hadn't done this widening, |
| 109 | +the last line wouldn't typecheck because `ListBuffer` is invariant in its |
| 110 | +argument. |
| 111 | + |
| 112 | + |
| 113 | +## Members |
| 114 | + |
| 115 | +The members of a union type are the members of its join. |
| 116 | + |
| 117 | +### Example |
| 118 | + |
| 119 | +The following code does not typecheck, because `hello` is not a member of |
| 120 | +`AnyRef` which is the join of `A | B`. |
| 121 | + |
| 122 | +```scala |
| 123 | +trait A { def hello: String } |
| 124 | +trait B { def hello: String } |
| 125 | + |
| 126 | +def test(x: A | B) = x.hello // error: value `hello` is not a member of A | B |
| 127 | +``` |
| 128 | + |
| 129 | +## Exhaustivity checking |
| 130 | + |
| 131 | +If the selector of a pattern match is a union type, the match is considered |
| 132 | +exhaustive if all parts of the union are covered. |
| 133 | + |
| 134 | +## Erasure |
| 135 | + |
| 136 | +The erased type for `A | B` is the _erased least upper bound_ of the erased |
| 137 | +types of `A` and `B`. Quoting from the documentation of `TypeErasure#erasedLub`, |
| 138 | +the erased lub is computed as follows: |
| 139 | +- if both argument are arrays of objects, an array of the erased lub of the element types |
| 140 | +- if both arguments are arrays of same primitives, an array of this primitive |
| 141 | +- if one argument is array of primitives and the other is array of objects, Object |
| 142 | +- if one argument is an array, Object |
| 143 | +- otherwise a common superclass or trait S of the argument classes, with the |
| 144 | + following two properties: |
| 145 | + * S is minimal: no other common superclass or trait derives from S |
| 146 | + * S is last : in the linearization of the first argument type `|A|` |
| 147 | + there are no minimal common superclasses or traits that |
| 148 | + come after S. |
| 149 | + The reason to pick last is that we prefer classes over traits that way, |
| 150 | + which leads to more predictable bytecode and (?) faster dynamic dispatch. |
| 151 | + |
| 152 | +## Limitations |
| 153 | + |
| 154 | +In a union type `A | B`, neither `A` nor `B` is allowed to be a singleton type. |
| 155 | +This is an implementation restriction that may be lifted in the future. |
0 commit comments