Skip to content

Commit 1c9552b

Browse files
authored
Merge pull request #2843 from dotty-staging/fix-homogenize
Fix #2833: Fixes to harmonize
2 parents 8a93f6e + 196769f commit 1c9552b

File tree

7 files changed

+169
-12
lines changed

7 files changed

+169
-12
lines changed

compiler/src/dotty/tools/dotc/typer/Applications.scala

+30-5
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import config.Printers.{typr, unapp, overload}
2929
import TypeApplications._
3030
import language.implicitConversions
3131
import reporting.diagnostic.Message
32+
import Constants.{Constant, IntTag, LongTag}
3233

3334
object Applications {
3435
import tpd._
@@ -441,6 +442,9 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
441442
val harmonizedArgs = harmonizeArgs(typedArgs)
442443
if (harmonizedArgs ne typedArgs) {
443444
ctx.typerState.constraint = origConstraint
445+
// reset constraint, we will re-establish constraint anyway when we
446+
// compare against the seqliteral. The reset is needed
447+
// otherwise pos/harmonize.scala would fail on line 40.
444448
typedArgs = harmonizedArgs
445449
}
446450
typedArgs.foreach(addArg(_, elemFormal))
@@ -1437,15 +1441,35 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
14371441
}
14381442
val clss = numericClasses(ts, Set())
14391443
if (clss.size > 1) {
1444+
def isCompatible(cls: Symbol, sup: TypeRef) =
1445+
defn.isValueSubType(cls.typeRef, sup) &&
1446+
!(cls == defn.LongClass && sup.isRef(defn.FloatClass))
1447+
// exclude Long <: Float from list of allowable widenings
1448+
// TODO: should we do this everywhere we ask for isValueSubType?
1449+
14401450
val lub = defn.ScalaNumericValueTypeList.find(lubTpe =>
1441-
clss.forall(cls => defn.isValueSubType(cls.typeRef, lubTpe))).get
1442-
ts.mapConserve(adapt(_, lub))
1451+
clss.forall(cls => isCompatible(cls, lubTpe))).get
1452+
1453+
def lossOfPrecision(ct: Constant): Boolean =
1454+
ct.tag == IntTag && lub.isRef(defn.FloatClass) &&
1455+
ct.intValue.toFloat.toInt != ct.intValue ||
1456+
ct.tag == LongTag && lub.isRef(defn.DoubleClass) &&
1457+
ct.longValue.toDouble.toLong != ct.longValue
1458+
1459+
val ts1 = ts.mapConserve { t =>
1460+
tpe(t).widenTermRefExpr match {
1461+
case ct: ConstantType if !lossOfPrecision(ct.value) => adapt(t, lub)
1462+
case _ => t
1463+
}
1464+
}
1465+
if (numericClasses(ts1, Set()).size == 1) ts1 else ts
14431466
}
14441467
else ts
14451468
}
14461469

14471470
/** If `trees` all have numeric value types, and they do not have all the same type,
1448-
* pick a common numeric supertype and convert all trees to this type.
1471+
* pick a common numeric supertype and convert all constant trees to this type.
1472+
* If the resulting trees all have the same type, return them instead of the original ones.
14491473
*/
14501474
def harmonize(trees: List[Tree])(implicit ctx: Context): List[Tree] = {
14511475
def adapt(tree: Tree, pt: Type): Tree = tree match {
@@ -1456,9 +1480,10 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
14561480
}
14571481

14581482
/** If all `types` are numeric value types, and they are not all the same type,
1459-
* pick a common numeric supertype and return it instead of every original type.
1483+
* pick a common numeric supertype and widen any constant types in `tpes` to it.
1484+
* If the resulting types are all the same, return them instead of the original ones.
14601485
*/
1461-
def harmonizeTypes(tpes: List[Type])(implicit ctx: Context): List[Type] =
1486+
private def harmonizeTypes(tpes: List[Type])(implicit ctx: Context): List[Type] =
14621487
harmonizeWith(tpes)(identity, (tp, pt) => pt)
14631488
}
14641489

compiler/src/dotty/tools/dotc/typer/Checking.scala

+2-1
Original file line numberDiff line numberDiff line change
@@ -557,7 +557,8 @@ trait Checking {
557557
case tp => tp.widenTermRefExpr match {
558558
case tp: ConstantType if isPureExpr(tree) => // ok
559559
case tp if defn.isFunctionType(tp) && isPureExpr(tree) => // ok
560-
case _ => ctx.error(em"$what must be a constant expression or a function", tree.pos)
560+
case _ =>
561+
if (!ctx.erasedTypes) ctx.error(em"$what must be a constant expression or a function", tree.pos)
561562
}
562563
}
563564

compiler/src/dotty/tools/dotc/typer/Typer.scala

+5-2
Original file line numberDiff line numberDiff line change
@@ -2098,8 +2098,11 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
20982098
case _: RefTree | _: Literal
20992099
if !isVarPattern(tree) &&
21002100
!(tree.tpe <:< pt)(ctx.addMode(Mode.GADTflexible)) =>
2101-
val tp1 :: tp2 :: Nil = harmonizeTypes(pt :: wtp :: Nil)
2102-
checkCanEqual(tp1, tp2, tree.pos)(ctx.retractMode(Mode.Pattern))
2101+
val cmp =
2102+
untpd.Apply(
2103+
untpd.Select(untpd.TypedSplice(tree), nme.EQ),
2104+
untpd.TypedSplice(dummyTreeOfType(pt)))
2105+
typedExpr(cmp, defn.BooleanType)(ctx.retractMode(Mode.Pattern))
21032106
case _ =>
21042107
}
21052108
tree
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
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+

docs/sidebar.yml

+2
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ sidebar:
9595
url: docs/reference/dropped/xml.html
9696
- title: Auto-Application
9797
url: docs/reference/dropped/auto-apply.html
98+
- title: Weak Conformance
99+
url: docs/reference/dropped/weak-conformance.html
98100
- title: Contributing
99101
subsection:
100102
- title: Getting Started

tests/pos/harmonize.scala

+34-4
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,56 @@ object Test {
33
def main(args: Array[String]) = {
44
val x = true
55
val n = 1
6+
inline val nn = 2
67
val y = if (x) 'A' else n
78
val z: Int = y
89

9-
val yy = n match {
10+
val yy1 = n match {
1011
case 1 => 'A'
1112
case 2 => n
1213
case 3 => 1.0
1314
}
14-
val zz: Double = yy
15+
val zz1: AnyVal = yy1 // no widening
16+
17+
val yy2 = n match {
18+
case 1 => 'A'
19+
case 2 => nn
20+
case 3 => 1.0f
21+
}
22+
val zz2: Float = yy2 // widening to Float
23+
24+
val yy3 = n match {
25+
case 1 => 'A'
26+
case 2 => 3L
27+
case 3 => 1.0f
28+
}
29+
val zz3: Double = yy3 // widening to Double
1530

1631
val a = try {
1732
'A'
1833
} catch {
19-
case ex: Exception => n
34+
case ex: Exception => nn
2035
case ex: Error => 3L
2136
}
2237
val b: Long = a
2338

24-
val xs = List(1.0, n, 'c')
39+
val xs = List(1.0, nn, 'c')
2540
val ys: List[Double] = xs
41+
2642
}
2743

44+
inline val b = 33
45+
def f(): Int = b + 1
46+
val a1 = Array(b, 33, 'a')
47+
val b1: Array[Int] = a1
48+
val a2 = Array(b, 33, 'a', f())
49+
val b2: Array[Int] = a2
50+
val a3 = Array(1.0f, 'a', 0)
51+
val b3: Array[Float] = a3
52+
val a4 = Array(1.0f, 1L)
53+
val b4: Array[Double] = a4
54+
val a5 = Array(1.0f, 1L, f())
55+
val b5: Array[AnyVal] = a5
56+
val a6 = Array(1.0f, 1234567890)
57+
val b6: Array[AnyVal] = a6
2858
}

tests/pos/i2833.scala

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
object a {
2+
val x: scala.Any =
3+
if (true)
4+
0L
5+
else if (false)
6+
(0: Int)
7+
else
8+
null
9+
}

0 commit comments

Comments
 (0)