diff --git a/spec/06-expressions.md b/spec/06-expressions.md index 483dbd176a89..0eac84c2ebbd 100644 --- a/spec/06-expressions.md +++ b/spec/06-expressions.md @@ -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 diff --git a/src/compiler/scala/tools/nsc/typechecker/ConstantFolder.scala b/src/compiler/scala/tools/nsc/typechecker/ConstantFolder.scala index d9e711ab46d7..3bf9c8d8c986 100644 --- a/src/compiler/scala/tools/nsc/typechecker/ConstantFolder.scala +++ b/src/compiler/scala/tools/nsc/typechecker/ConstantFolder.scala @@ -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 } } diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 2c44f33295b2..b0c8a41c8951 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -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") } diff --git a/src/reflect/scala/reflect/internal/Constants.scala b/src/reflect/scala/reflect/internal/Constants.scala index ae50e1ef913f..4b6b13001fab 100644 --- a/src/reflect/scala/reflect/internal/Constants.scala +++ b/src/reflect/scala/reflect/internal/Constants.scala @@ -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 @@ -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 diff --git a/test/files/jvm/duration-tck.scala b/test/files/jvm/duration-tck.scala index 489ef5643acd..f43584995ab9 100644 --- a/test/files/jvm/duration-tck.scala +++ b/test/files/jvm/duration-tck.scala @@ -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 { diff --git a/test/files/neg/deprecated_widening.check b/test/files/neg/deprecated_widening.check index 262de21ada6d..8161bd5f7d50 100644 --- a/test/files/neg/deprecated_widening.check +++ b/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 ^ @@ -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 diff --git a/test/files/neg/deprecated_widening.scala b/test/files/neg/deprecated_widening.scala index 7fdd288a89ff..fade37e00d95 100644 --- a/test/files/neg/deprecated_widening.scala +++ b/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 = { @@ -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 }