|
| 1 | +--- |
| 2 | +layout: doc-page |
| 3 | +title: Dropped: Weak Conformance |
| 4 | +--- |
| 5 | + |
| 6 | +In some situations, Scala used a _weak conformance_ relation when |
| 7 | +testing type compatibility or computing the least upper bound of a set |
| 8 | +of types. The principal motivation behind weak conformance was to |
| 9 | +make as expression like this have type `List[Double]`: |
| 10 | + |
| 11 | + List(1.0, math.sqrt(3.0), 0, -3.3) // : List[Double] |
| 12 | + |
| 13 | +It's "obvious" that this should be a `List[Double]`. However, without |
| 14 | +some special provision, the least upper bound of the lists's element |
| 15 | +types `(Double, Double, Int, Double)` would be `AnyVal`, hence the list |
| 16 | +expression would be given type `List[AnyVal]`. |
| 17 | + |
| 18 | +A less obvious example is the following one, which was also typed as a |
| 19 | +`List[Double]`, using the weak conformance relation. |
| 20 | + |
| 21 | + val n: Int = 3 |
| 22 | + val c: Char = 'X' |
| 23 | + val n: Double = math.sqrt(3.0) |
| 24 | + List(n, c, d) // used to be: List[Double], now: List[AnyVal] |
| 25 | + |
| 26 | +Here, it is less clear why the type should be widened to |
| 27 | +`List[Double]`, a `List[AnyVal]` seems to be an equally valid -- and |
| 28 | +more principled -- choice. |
| 29 | + |
| 30 | +To simplify the underlying type theory, Dotty drops the notion of weak |
| 31 | +conformance altogether. Instead, it provides more flexibility when |
| 32 | +assigning a type to a constant expression. The new rule is: |
| 33 | + |
| 34 | + - If a list of expressions `Es` appears as one of |
| 35 | + |
| 36 | + - the elements of a vararg parameter, or |
| 37 | + - the alternatives of an if-then-else or match expression, or |
| 38 | + - the body and catch results of a try expression, |
| 39 | + |
| 40 | + and all expressions have primitive numeric types, but they do not |
| 41 | + all have the same type, then the following is attempted: Every |
| 42 | + constant expression `E` in `Es` is widened to the least primitive |
| 43 | + numeric value type equal to or above the types of all expressions in `Es`, |
| 44 | + if that can be done without a loss of precision. Here |
| 45 | + _above_ and _least_ are interpreted according to the ordering given |
| 46 | + below. |
| 47 | + |
| 48 | + |
| 49 | + Double |
| 50 | + / \ |
| 51 | + Long Float |
| 52 | + \ / |
| 53 | + Int |
| 54 | + / \ |
| 55 | + Short Char |
| 56 | + | |
| 57 | + Byte |
| 58 | + |
| 59 | + A loss of precision occurs for an `Int -> Float` conversion of a constant |
| 60 | + `c` if `c.toFloat.toInt != c`. For a `Long -> Double` conversion it occurs |
| 61 | + if `c.toDouble.toLong != c`. |
| 62 | + |
| 63 | + If these widenings lead to all widened expressions having the same type, |
| 64 | + we use the widened expressions instead of `Es`, otherwise we use `Es` unchanged. |
| 65 | + |
| 66 | +__Examples:__ |
| 67 | + |
| 68 | + inline val b = 33 |
| 69 | + def f(): Int = b + 1 |
| 70 | + List(b, 33, 'a') : List[Int] |
| 71 | + List(b, 33, 'a', f()) : List[Int] |
| 72 | + List(1.0f, 'a', 0) : List[Float] |
| 73 | + List(1.0f, 1L) : List[Double] |
| 74 | + List(1.0f, 1L, f()) : List[AnyVal] |
| 75 | + List(1.0f, 1234567890): List[AnyVal] |
| 76 | + |
| 77 | +The expression on the second-to-last line has type `List[AnyVal]`, |
| 78 | +since widenings only affect constants. Hence, `1.0f` and `1L` are |
| 79 | +widened to `Double`, but `f()` still has type `Int`. The elements |
| 80 | +don't agree on a type after widening, hence the elements are left |
| 81 | +unchanged. |
| 82 | + |
| 83 | +The expression on the last line has type `List[AnyVal]` because |
| 84 | +`1234567890` cannot be converted to a `Float` without a loss of |
| 85 | +precision. |
| 86 | + |
| 87 | + |
0 commit comments