Skip to content

Commit

Permalink
Merge pull request #8757 from fabianpage/deprecate_numeric_widening_o…
Browse files Browse the repository at this point in the history
…f_too_big_long_literals_to_float

Deprecate numeric widening of numeric literals which are not representable with Float/Double
  • Loading branch information
lrytz committed Mar 4, 2020
2 parents db6fa85 + da82001 commit 4aad5dc
Show file tree
Hide file tree
Showing 7 changed files with 102 additions and 16 deletions.
7 changes: 6 additions & 1 deletion spec/06-expressions.md
Expand Up @@ -1344,7 +1344,12 @@ If $e$ has a primitive number type which [weakly conforms](03-types.html#weak-co
to the expected type, it is widened to
the expected type using one of the numeric conversion methods
`toShort`, `toChar`, `toInt`, `toLong`,
`toFloat`, `toDouble` defined [here](12-the-scala-standard-library.html#numeric-value-types).
`toFloat`, `toDouble` defined [in the standard library](12-the-scala-standard-library.html#numeric-value-types).

Since conversions from `Int` to `Float` and from `Long` to `Float` or `Double`
may incur a loss of precision, those implicit conversions are deprecated.
The conversion is permitted for literals if the original value can be recovered,
that is, if conversion back to the original type produces the original value.

###### Numeric Literal Narrowing
If the expected type is `Byte`, `Short` or `Char`, and
Expand Down
9 changes: 5 additions & 4 deletions src/compiler/scala/tools/nsc/typechecker/ConstantFolder.scala
Expand Up @@ -27,11 +27,12 @@ abstract class ConstantFolder {

// We can fold side effect free terms and their types
object FoldableTerm {
@inline private def effectless(sym: Symbol): Boolean = sym != null && !sym.isLazy && (sym.isVal || sym.isGetter && sym.accessed.isVal)

def unapply(tree: Tree): Option[Constant] = tree match {
case Literal(x) => Some(x)
case term if term.symbol != null && !term.symbol.isLazy && (term.symbol.isVal || (term.symbol.isGetter && term.symbol.accessed.isVal)) =>
extractConstant(term.tpe)
case _ => None
case Literal(x) => Some(x)
case term if effectless(term.symbol) => extractConstant(term.tpe)
case _ => None
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/compiler/scala/tools/nsc/typechecker/Typers.scala
Expand Up @@ -1150,7 +1150,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
tpSym == LongClass && (ptSym == FloatClass || ptSym == DoubleClass)
)
if (isInharmonic)
context.warning(tree.pos, s"Automatic conversion from ${tpSym.name} to ${ptSym.name} is deprecated (since 2.13.1) because it loses precision. Write `.to${ptSym.name}` instead.")
context.warning(tree.pos, s"Widening conversion from ${tpSym.name} to ${ptSym.name} is deprecated because it loses precision. Write `.to${ptSym.name}` instead.")
else if (settings.warnNumericWiden) context.warning(tree.pos, "implicit numeric widening")
}

Expand Down
7 changes: 4 additions & 3 deletions src/reflect/scala/reflect/internal/Constants.scala
Expand Up @@ -63,7 +63,8 @@ trait Constants extends api.Constants {
def isCharRange: Boolean = isIntRange && Char.MinValue <= intValue && intValue <= Char.MaxValue
def isIntRange: Boolean = ByteTag <= tag && tag <= IntTag
def isLongRange: Boolean = ByteTag <= tag && tag <= LongTag
def isFloatRange: Boolean = ByteTag <= tag && tag <= FloatTag
def isFloatRepresentable: Boolean = ByteTag <= tag && tag <= FloatTag && (tag != IntTag || intValue == intValue.toFloat.toInt) && (tag != LongTag || longValue == longValue.toFloat.toLong)
def isDoubleRepresentable: Boolean = ByteTag <= tag && tag <= DoubleTag && (tag != LongTag || longValue == longValue.toDouble.toLong)
def isNumeric: Boolean = ByteTag <= tag && tag <= DoubleTag
def isNonUnitAnyVal = BooleanTag <= tag && tag <= DoubleTag
def isSuitableLiteralType = BooleanTag <= tag && tag <= NullTag
Expand Down Expand Up @@ -218,9 +219,9 @@ trait Constants extends api.Constants {
Constant(intValue)
else if (target == LongClass && isLongRange)
Constant(longValue)
else if (target == FloatClass && isFloatRange)
else if (target == FloatClass && isFloatRepresentable)
Constant(floatValue)
else if (target == DoubleClass && isNumeric)
else if (target == DoubleClass && isDoubleRepresentable)
Constant(doubleValue)
else
null
Expand Down
3 changes: 1 addition & 2 deletions test/files/jvm/duration-tck.scala
Expand Up @@ -3,10 +3,9 @@
*/

import scala.concurrent.duration._
import scala.reflect._
import scala.tools.testkit.AssertUtil.assertThrows

import scala.language.{ postfixOps }
import scala.language.postfixOps

object Test extends App {

Expand Down
53 changes: 49 additions & 4 deletions test/files/neg/deprecated_widening.check
@@ -1,12 +1,57 @@
deprecated_widening.scala:5: warning: Automatic conversion from Int to Float is deprecated (since 2.13.1) because it loses precision. Write `.toFloat` instead.
deprecated_widening.scala:5: warning: Widening conversion from Int to Float is deprecated because it loses precision. Write `.toFloat` instead.
val i_f: Float = i // deprecated
^
deprecated_widening.scala:7: warning: Automatic conversion from Long to Float is deprecated (since 2.13.1) because it loses precision. Write `.toFloat` instead.
deprecated_widening.scala:7: warning: Widening conversion from Long to Float is deprecated because it loses precision. Write `.toFloat` instead.
val l_f: Float = l // deprecated
^
deprecated_widening.scala:8: warning: Automatic conversion from Long to Double is deprecated (since 2.13.1) because it loses precision. Write `.toDouble` instead.
deprecated_widening.scala:8: warning: Widening conversion from Long to Double is deprecated because it loses precision. Write `.toDouble` instead.
val l_d: Double = l // deprecated
^
deprecated_widening.scala:23: warning: Widening conversion from Long to Float is deprecated because it loses precision. Write `.toFloat` instead.
val truncatedPosFloat:Float = 16777217L // deprecated
^
deprecated_widening.scala:26: warning: Widening conversion from Long to Float is deprecated because it loses precision. Write `.toFloat` instead.
val truncatedNegFloat: Float = - 16777217L // deprecated
^
deprecated_widening.scala:30: warning: Widening conversion from Int to Float is deprecated because it loses precision. Write `.toFloat` instead.
val truncatedPosFloatI:Float = 16777217 // deprecated
^
deprecated_widening.scala:33: warning: Widening conversion from Int to Float is deprecated because it loses precision. Write `.toFloat` instead.
val truncatedNegFloatI: Float = - 16777217 // deprecated
^
deprecated_widening.scala:37: warning: Widening conversion from Long to Double is deprecated because it loses precision. Write `.toDouble` instead.
val truncatedPosDouble:Double = 18014398509481985L // deprecated
^
deprecated_widening.scala:40: warning: Widening conversion from Long to Double is deprecated because it loses precision. Write `.toDouble` instead.
val truncatedNegDouble: Double = - 18014398509481985L // deprecated
^
deprecated_widening.scala:47: warning: Widening conversion from Int to Float is deprecated because it loses precision. Write `.toFloat` instead.
def literals = Set[Float](0x7fffffc0, 0x7ffffffd, 0x7ffffffe, 0x7fffffff)
^
deprecated_widening.scala:47: warning: Widening conversion from Int to Float is deprecated because it loses precision. Write `.toFloat` instead.
def literals = Set[Float](0x7fffffc0, 0x7ffffffd, 0x7ffffffe, 0x7fffffff)
^
deprecated_widening.scala:47: warning: Widening conversion from Int to Float is deprecated because it loses precision. Write `.toFloat` instead.
def literals = Set[Float](0x7fffffc0, 0x7ffffffd, 0x7ffffffe, 0x7fffffff)
^
deprecated_widening.scala:48: warning: Widening conversion from Long to Float is deprecated because it loses precision. Write `.toFloat` instead.
def longingly = Set[Float](0x7fffffc0L, 0x7ffffffdL, 0x7ffffffeL, 0x7fffffffL)
^
deprecated_widening.scala:48: warning: Widening conversion from Long to Float is deprecated because it loses precision. Write `.toFloat` instead.
def longingly = Set[Float](0x7fffffc0L, 0x7ffffffdL, 0x7ffffffeL, 0x7fffffffL)
^
deprecated_widening.scala:48: warning: Widening conversion from Long to Float is deprecated because it loses precision. Write `.toFloat` instead.
def longingly = Set[Float](0x7fffffc0L, 0x7ffffffdL, 0x7ffffffeL, 0x7fffffffL)
^
deprecated_widening.scala:48: warning: Widening conversion from Long to Float is deprecated because it loses precision. Write `.toFloat` instead.
def longingly = Set[Float](0x7fffffc0L, 0x7ffffffdL, 0x7ffffffeL, 0x7fffffffL)
^
deprecated_widening.scala:50: warning: Widening conversion from Int to Float is deprecated because it loses precision. Write `.toFloat` instead.
def `pick one` = Set[Float](0x1000003, 0x1000004, 0x1000005)
^
deprecated_widening.scala:50: warning: Widening conversion from Int to Float is deprecated because it loses precision. Write `.toFloat` instead.
def `pick one` = Set[Float](0x1000003, 0x1000004, 0x1000005)
^
deprecated_widening.scala:12: 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.
implicitly[Int => Float] // deprecated
^
Expand All @@ -17,5 +62,5 @@ deprecated_widening.scala:15: warning: method long2double in object Long is depr
implicitly[Long => Double] // deprecated
^
error: No warnings can be incurred under -Werror.
6 warnings
21 warnings
1 error
37 changes: 36 additions & 1 deletion test/files/neg/deprecated_widening.scala
@@ -1,4 +1,4 @@
// scalac: -deprecation -Werror
// scalac: -Werror -Xlint:deprecation
//
object Test {
def foo(i: Int, l: Long): Unit = {
Expand All @@ -18,4 +18,39 @@ object Test {
// don't leak silent warning from float conversion
val n = 42
def clean = n max 27

val posFloat:Float = 16777216L // OK
val truncatedPosFloat:Float = 16777217L // deprecated
val losslessPosFloat:Float = 16777218L // OK -- lossless
val negFloat: Float = - 16777216L // OK
val truncatedNegFloat: Float = - 16777217L // deprecated
val losslessNegFloat: Float = - 16777218L // OK -- lossless

val posFloatI:Float = 16777216 // OK
val truncatedPosFloatI:Float = 16777217 // deprecated
val losslessPosFloatI:Float = 16777218 // OK -- lossless
val negFloatI: Float = - 16777216 // OK
val truncatedNegFloatI: Float = - 16777217 // deprecated
val losslessNegFloatI: Float = - 16777218 // OK -- lossless

val posDouble:Double = 18014398509481984L// OK
val truncatedPosDouble:Double = 18014398509481985L // deprecated
val losslessPosDouble:Double = 18014398509481988L // OK -- lossless
val negDouble: Double = - 18014398509481984L // OK
val truncatedNegDouble: Double = - 18014398509481985L // deprecated
val losslessNegDouble: Double = - 18014398509481988L // OK -- lossless

// literals don't get a pass -- *especially* literals!

// 0x7ffffffc0 - 0x7fffffff
// Set[Float](2147483584, 2147483645, 2147483646, 2147483647)
def literals = Set[Float](0x7fffffc0, 0x7ffffffd, 0x7ffffffe, 0x7fffffff)
def longingly = Set[Float](0x7fffffc0L, 0x7ffffffdL, 0x7ffffffeL, 0x7fffffffL)

def `pick one` = Set[Float](0x1000003, 0x1000004, 0x1000005)

def `no warn` = 1f + 2147483584
def `no warn either` = 2147483584 + 1f
def f = 1f
def `no warn sowieso` = f + 2147483584
}

0 comments on commit 4aad5dc

Please sign in to comment.