Skip to content

Commit c8ba3c4

Browse files
smartersom-snytt
authored andcommitted
Deprecate numeric conversions that lose precision
Int to Float, Long to Float and Long to Double are dangerous conversions and should never be done automatically.
1 parent 46a8156 commit c8ba3c4

File tree

11 files changed

+75
-24
lines changed

11 files changed

+75
-24
lines changed

project/GenerateAnyVals.scala

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,26 @@ trait GenerateAnyValReps {
99

1010
case class Op(op : String, doc : String)
1111

12-
private def companionCoercions(tos: AnyValRep*) = {
13-
tos.toList map (to =>
14-
s"implicit def @javaequiv@2${to.javaEquiv}(x: @name@): ${to.name} = x.to${to.name}"
15-
)
16-
}
12+
private def companionCoercions(deprecated: Boolean, tos: AnyValRep*): List[String] =
13+
tos.toList.flatMap { to =>
14+
val code = s"implicit def @javaequiv@2${to.javaEquiv}(x: @name@): ${to.name} = x.to${to.name}"
15+
if (deprecated)
16+
List(s"""@deprecated("Implicit conversion from @name@ to ${to.name} is dangerous because it loses precision. Write `.to${to.name}` instead.", "2.13.1")""", code)
17+
else
18+
List(code)
19+
}
20+
1721
def coercionComment =
1822
"""/** Language mandated coercions from @name@ to "wider" types. */
1923
import scala.language.implicitConversions"""
2024

2125
def implicitCoercions: List[String] = {
2226
val coercions = this match {
23-
case B => companionCoercions(S, I, L, F, D)
24-
case S | C => companionCoercions(I, L, F, D)
25-
case I => companionCoercions(L, F, D)
26-
case L => companionCoercions(F, D)
27-
case F => companionCoercions(D)
27+
case B => companionCoercions(deprecated = false, S, I, L, F, D)
28+
case S | C => companionCoercions(deprecated = false, I, L, F, D)
29+
case I => companionCoercions(deprecated = true, F) ++ companionCoercions(deprecated = false, L, D)
30+
case L => companionCoercions(deprecated = true, F, D)
31+
case F => companionCoercions(deprecated = false, D)
2832
case _ => Nil
2933
}
3034
if (coercions.isEmpty) Nil

src/compiler/scala/tools/nsc/typechecker/Typers.scala

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1136,8 +1136,15 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
11361136
}
11371137
if (!isThisTypeResult && !explicitlyUnit(tree)) context.warning(tree.pos, "discarded non-Unit value")
11381138
}
1139-
@inline def warnNumericWiden(): Unit =
1140-
if (!isPastTyper && settings.warnNumericWiden) context.warning(tree.pos, "implicit numeric widening")
1139+
@inline def warnNumericWiden(tpSym: Symbol, ptSym: Symbol): Unit =
1140+
if (!isPastTyper) {
1141+
if (tpSym == IntClass && ptSym == FloatClass ||
1142+
tpSym == LongClass && (ptSym == FloatClass || ptSym == DoubleClass))
1143+
context.deprecationWarning(tree.pos, NoSymbol,
1144+
s"Automatic conversion from ${tpSym.name} to ${ptSym.name} is deprecated (since 2.13.1) because it loses precision. " +
1145+
s"Write `.to${ptSym.name}` instead.".stripMargin, "2.13.1")
1146+
else if (settings.warnNumericWiden) context.warning(tree.pos, "implicit numeric widening")
1147+
}
11411148

11421149
// The <: Any requirement inhibits attempts to adapt continuation types to non-continuation types.
11431150
val anyTyped = tree.tpe <:< AnyTpe
@@ -1146,7 +1153,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
11461153
case TypeRef(_, UnitClass, _) if anyTyped => // (12)
11471154
warnValueDiscard() ; tpdPos(gen.mkUnitBlock(tree))
11481155
case TypeRef(_, numValueCls, _) if anyTyped && isNumericValueClass(numValueCls) && isNumericSubType(tree.tpe, pt) => // (10) (11)
1149-
warnNumericWiden() ; tpdPos(Select(tree, s"to${numValueCls.name}"))
1156+
warnNumericWiden(tree.tpe.widen.typeSymbol, numValueCls) ; tpdPos(Select(tree, s"to${numValueCls.name}"))
11501157
case dealiased if dealiased.annotations.nonEmpty && canAdaptAnnotations(tree, this, mode, pt) => // (13)
11511158
tpd(adaptAnnotations(tree, this, mode, pt))
11521159
case _ =>

src/library/scala/Int.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -478,8 +478,9 @@ object Int extends AnyValCompanion {
478478
override def toString = "object scala.Int"
479479
/** Language mandated coercions from Int to "wider" types. */
480480
import scala.language.implicitConversions
481-
implicit def int2long(x: Int): Long = x.toLong
481+
@deprecated("Implicit conversion from Int to Float is dangerous because it loses precision. Write `.toFloat` instead.", "2.13.1")
482482
implicit def int2float(x: Int): Float = x.toFloat
483+
implicit def int2long(x: Int): Long = x.toLong
483484
implicit def int2double(x: Int): Double = x.toDouble
484485
}
485486

src/library/scala/Long.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,7 +475,9 @@ object Long extends AnyValCompanion {
475475
override def toString = "object scala.Long"
476476
/** Language mandated coercions from Long to "wider" types. */
477477
import scala.language.implicitConversions
478+
@deprecated("Implicit conversion from Long to Float is dangerous because it loses precision. Write `.toFloat` instead.", "2.13.1")
478479
implicit def long2float(x: Long): Float = x.toFloat
480+
@deprecated("Implicit conversion from Long to Double is dangerous because it loses precision. Write `.toDouble` instead.", "2.13.1")
479481
implicit def long2double(x: Long): Double = x.toDouble
480482
}
481483

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
deprecated_widening.scala:3: warning: Automatic conversion from Int to Float is deprecated (since 2.13.1) because it loses precision. Write `.toFloat` instead.
2+
val i_f: Float = i // deprecated
3+
^
4+
deprecated_widening.scala:5: warning: Automatic conversion from Long to Float is deprecated (since 2.13.1) because it loses precision. Write `.toFloat` instead.
5+
val l_f: Float = l // deprecated
6+
^
7+
deprecated_widening.scala:6: warning: Automatic conversion from Long to Double is deprecated (since 2.13.1) because it loses precision. Write `.toDouble` instead.
8+
val l_d: Double = l // deprecated
9+
^
10+
deprecated_widening.scala:10: warning: method int2float in object Int is deprecated (since 2.13.1): Implicit conversion from Int to Float is dangerous because it loses precision. Write `.toFloat` instead.
11+
implicitly[Int => Float] // deprecated
12+
^
13+
deprecated_widening.scala:12: warning: method long2float in object Long is deprecated (since 2.13.1): Implicit conversion from Long to Float is dangerous because it loses precision. Write `.toFloat` instead.
14+
implicitly[Long => Float] // deprecated
15+
^
16+
deprecated_widening.scala:13: warning: method long2double in object Long is deprecated (since 2.13.1): Implicit conversion from Long to Double is dangerous because it loses precision. Write `.toDouble` instead.
17+
implicitly[Long => Double] // deprecated
18+
^
19+
error: No warnings can be incurred under -Werror.
20+
6 warnings found
21+
one error found
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-deprecation -Xfatal-warnings
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
object Test {
2+
def foo(i: Int, l: Long): Unit = {
3+
val i_f: Float = i // deprecated
4+
val i_d: Double = i // OK
5+
val l_f: Float = l // deprecated
6+
val l_d: Double = l // deprecated
7+
}
8+
9+
def imp: Unit = {
10+
implicitly[Int => Float] // deprecated
11+
implicitly[Int => Double] // OK
12+
implicitly[Long => Float] // deprecated
13+
implicitly[Long => Double] // deprecated
14+
}
15+
}

test/files/neg/t8450.check

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
t8450.scala:7: warning: implicit numeric widening
2-
def elapsed: Foo = (System.nanoTime - 100L).foo
3-
^
2+
def elapsed: Foo = (System.nanoTime.toInt - 100).foo
3+
^
44
error: No warnings can be incurred under -Werror.
55
1 warning
66
1 error

test/files/neg/t8450.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ trait Foo
44

55
class WarnWidening {
66
implicit class FooDouble(d: Double) { def foo = new Foo {} }
7-
def elapsed: Foo = (System.nanoTime - 100L).foo
7+
def elapsed: Foo = (System.nanoTime.toInt - 100).foo
88
}
99

1010
class NoWarnWidening {
11-
implicit class FooLong(l: Long) { def foo = new Foo {} }
11+
implicit class FooInt(i: Int) { def foo = new Foo {} }
1212
implicit class FooDouble(d: Double) { def foo = new Foo {} }
13-
def elapsed: Foo = (System.nanoTime - 100L).foo
13+
def elapsed: Foo = (System.nanoTime.toInt - 100).foo
1414
}

test/files/run/arrays.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ object Test {
293293
def fcheck(xs: Array[Float ]): Unit = {
294294
check(xs.length == 3, xs.length, 3);
295295
check(xs(0) == f0, xs(0), f0);
296-
check(xs(1) == f1, xs(1), f1: Float); // !!! : Float
296+
check(xs(1) == f1, xs(1), f1.toFloat); // !!! : Float
297297
check(xs(2) == f2, xs(2), f2);
298298
}
299299

@@ -363,7 +363,7 @@ object Test {
363363
val carray: Array[Char ] = Array(c0, c1, c2);
364364
val iarray: Array[Int ] = Array(i0, i1, i2);
365365
val larray: Array[Long ] = Array(l0, l1, l2);
366-
val farray: Array[Float ] = Array(f0, f1, f2);
366+
val farray: Array[Float ] = Array(f0, f1.toFloat, f2);
367367
val darray: Array[Double ] = Array(d0, d1, d2);
368368
val rarray: Array[AnyRef ] = Array(r0, r1, r2, r4, r4, r5);
369369
val oarray: Array[Object ] = Array(o0, o1, o2, o4, o4, o5);

0 commit comments

Comments
 (0)