Skip to content

Commit 6d208c5

Browse files
committed
Don't widen constants if that causes a loss of precision
1 parent af81f87 commit 6d208c5

File tree

3 files changed

+51
-18
lines changed

3 files changed

+51
-18
lines changed

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

Lines changed: 10 additions & 1 deletion
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._
@@ -1449,11 +1450,19 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
14491450
!(cls == defn.LongClass && sup.isRef(defn.FloatClass))
14501451
// exclude Long <: Float from list of allowable widenings
14511452
// TODO: should we do this everywhere we ask for isValueSubType?
1453+
14521454
val lub = defn.ScalaNumericValueTypeList.find(lubTpe =>
14531455
clss.forall(cls => isCompatible(cls, lubTpe))).get
1456+
1457+
def lossOfPrecision(ct: Constant): Boolean =
1458+
ct.tag == IntTag && lub.isRef(defn.FloatClass) &&
1459+
ct.intValue.toFloat.toInt != ct.intValue ||
1460+
ct.tag == LongTag && lub.isRef(defn.DoubleClass) &&
1461+
ct.longValue.toDouble.toLong != ct.longValue
1462+
14541463
val ts1 = ts.mapConserve { t =>
14551464
tpe(t).widenTermRefExpr match {
1456-
case ct: ConstantType => adapt(t, lub)
1465+
case ct: ConstantType if !lossOfPrecision(ct.value) => adapt(t, lub)
14571466
case _ => t
14581467
}
14591468
}

docs/docs/reference/dropped/weak-conformance.md

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ assigning a type to a constant expression. The rule is:
4040
and all expressions have primitive numeric types, but they do not
4141
all have the same type, then the following is attempted: Every
4242
constant expression `E` in `Es` is widened to the least primitive
43-
numeric value type above the types of all expressions in `Es`. Here
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
4445
_above_ and _least_ are interpreted according to the ordering given
4546
below.
4647

@@ -55,24 +56,32 @@ assigning a type to a constant expression. The rule is:
5556
|
5657
Byte
5758

58-
If these widenings lead to all expressions `Es` having the same type,
59-
we use the transformed list of expressions instead of `Es`, otherwise
60-
we use `Es` unchanged.
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.
6165

6266
__Examples:__
6367

64-
inline val b: Byte = 3
65-
inline val s: Short = 33
66-
def f(): Int = b + s
67-
List(b, s, 'a') : List[Int]
68-
List(b, s, 'a', f()): List[Int]
69-
List(1.0f, 'a', 0) : List[Float]
70-
List(1.0f, 1L) : List[Double]
71-
List(1.0f, 1L, f()) : List[AnyVal]
72-
73-
The expression on the last line has type `List[AnyVal]`, since widenings
74-
only affect constants. Hence, `1.0f` and `1L` are widened to `Double`,
75-
but `f()` still has type `Int`. The elements don't agree on a type after
76-
widening, hence the expressions are left unchanged.
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.
7786

7887

tests/pos/harmonize.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,21 @@ object Test {
3838

3939
val xs = List(1.0, nn, 'c')
4040
val ys: List[Double] = xs
41+
4142
}
4243

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
4358
}

0 commit comments

Comments
 (0)