From e88a7cb3d5c18a450ea5e6d3f652b6a393497b7f Mon Sep 17 00:00:00 2001 From: Fabian Page Date: Wed, 26 Feb 2020 13:28:40 +0100 Subject: [PATCH 1/3] Deprecate numeric widening of Numeric Literals which are not representable with Float/Double --- .../scala/reflect/internal/Constants.scala | 9 ++++++--- test/files/neg/deprecated_widening.check | 20 ++++++++++++++++++- test/files/neg/deprecated_widening.scala | 15 ++++++++++++++ 3 files changed, 40 insertions(+), 4 deletions(-) diff --git a/src/reflect/scala/reflect/internal/Constants.scala b/src/reflect/scala/reflect/internal/Constants.scala index ae50e1ef913f..c95cafb8f9d4 100644 --- a/src/reflect/scala/reflect/internal/Constants.scala +++ b/src/reflect/scala/reflect/internal/Constants.scala @@ -63,7 +63,10 @@ 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 + // Float has a Fraction of 23 Bits + 1 implicit leading Bit so the total precision is 24 Bit (see https://en.wikipedia.org/wiki/Single-precision_floating-point_format) + def isFloatRepresentable: Boolean = ByteTag <= tag && tag <= FloatTag && (tag != IntTag && tag != LongTag || longValue <= (1L << 24) && longValue >= -(1L << 24)) + // Double has a Fraction of 53 Bits + 1 implicit leading Bit => 54 Bit + def isDoubleRepresentable: Boolean = ByteTag <= tag && tag <= DoubleTag && (tag != LongTag || longValue <= (1L << 54) && longValue >= -(1L << 54)) def isNumeric: Boolean = ByteTag <= tag && tag <= DoubleTag def isNonUnitAnyVal = BooleanTag <= tag && tag <= DoubleTag def isSuitableLiteralType = BooleanTag <= tag && tag <= NullTag @@ -218,9 +221,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/neg/deprecated_widening.check b/test/files/neg/deprecated_widening.check index 262de21ada6d..c25c6d3cd612 100644 --- a/test/files/neg/deprecated_widening.check +++ b/test/files/neg/deprecated_widening.check @@ -7,6 +7,24 @@ deprecated_widening.scala:7: warning: Automatic conversion from Long to Float is deprecated_widening.scala:8: warning: Automatic conversion from Long to Double is deprecated (since 2.13.1) because it loses precision. Write `.toDouble` instead. val l_d: Double = l // deprecated ^ +deprecated_widening.scala:23: warning: Automatic conversion from Long to Float is deprecated (since 2.13.1) because it loses precision. Write `.toFloat` instead. + val truncatedPosFloat:Float = 16777217L // deprecated + ^ +deprecated_widening.scala:25: warning: Automatic conversion from Long to Float is deprecated (since 2.13.1) because it loses precision. Write `.toFloat` instead. + val truncatedNegFloat: Float = - 16777217L // deprecated + ^ +deprecated_widening.scala:28: warning: Automatic conversion from Int to Float is deprecated (since 2.13.1) because it loses precision. Write `.toFloat` instead. + val truncatedPosFloatI:Float = 16777217 // deprecated + ^ +deprecated_widening.scala:30: warning: Automatic conversion from Int to Float is deprecated (since 2.13.1) because it loses precision. Write `.toFloat` instead. + val truncatedNegFloatI: Float = - 16777217 // deprecated + ^ +deprecated_widening.scala:33: warning: Automatic conversion from Long to Double is deprecated (since 2.13.1) because it loses precision. Write `.toDouble` instead. + val truncatedPosDouble:Double = 18014398509481985L // deprecated + ^ +deprecated_widening.scala:35: warning: Automatic conversion from Long to Double is deprecated (since 2.13.1) because it loses precision. Write `.toDouble` instead. + val truncatedNegDouble: Double = - 18014398509481985L // deprecated + ^ 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 +35,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 +12 warnings 1 error diff --git a/test/files/neg/deprecated_widening.scala b/test/files/neg/deprecated_widening.scala index 7fdd288a89ff..0da49396e740 100644 --- a/test/files/neg/deprecated_widening.scala +++ b/test/files/neg/deprecated_widening.scala @@ -18,4 +18,19 @@ 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 negFloat: Float = - 16777216L // OK + val truncatedNegFloat: Float = - 16777217L // deprecated + + val posFloatI:Float = 16777216 // OK + val truncatedPosFloatI:Float = 16777217 // deprecated + val negFloatI: Float = - 16777216 // OK + val truncatedNegFloatI: Float = - 16777217 // deprecated + + val posDouble:Double = 18014398509481984L// OK + val truncatedPosDouble:Double = 18014398509481985L // deprecated + val negDouble: Double = - 18014398509481984L // OK + val truncatedNegDouble: Double = - 18014398509481985L // deprecated } From 1b0f108864a7c12bc5b66211201abf6451817d49 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Wed, 4 Mar 2020 13:58:42 +0100 Subject: [PATCH 2/3] Switch to roundtrip-based conversion check Co-authored-by: A. P. Marki --- .../nsc/typechecker/ConstantFolder.scala | 9 ++-- .../scala/tools/nsc/typechecker/Typers.scala | 2 +- .../scala/reflect/internal/Constants.scala | 6 +-- test/files/jvm/duration-tck.scala | 3 +- test/files/neg/deprecated_widening.check | 47 +++++++++++++++---- test/files/neg/deprecated_widening.scala | 22 ++++++++- 6 files changed, 67 insertions(+), 22 deletions(-) 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 c95cafb8f9d4..4b6b13001fab 100644 --- a/src/reflect/scala/reflect/internal/Constants.scala +++ b/src/reflect/scala/reflect/internal/Constants.scala @@ -63,10 +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 - // Float has a Fraction of 23 Bits + 1 implicit leading Bit so the total precision is 24 Bit (see https://en.wikipedia.org/wiki/Single-precision_floating-point_format) - def isFloatRepresentable: Boolean = ByteTag <= tag && tag <= FloatTag && (tag != IntTag && tag != LongTag || longValue <= (1L << 24) && longValue >= -(1L << 24)) - // Double has a Fraction of 53 Bits + 1 implicit leading Bit => 54 Bit - def isDoubleRepresentable: Boolean = ByteTag <= tag && tag <= DoubleTag && (tag != LongTag || longValue <= (1L << 54) && longValue >= -(1L << 54)) + 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 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 c25c6d3cd612..8161bd5f7d50 100644 --- a/test/files/neg/deprecated_widening.check +++ b/test/files/neg/deprecated_widening.check @@ -1,30 +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: Automatic conversion from Long to Float is deprecated (since 2.13.1) because it loses precision. Write `.toFloat` instead. +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:25: warning: Automatic conversion from Long to Float is deprecated (since 2.13.1) because it loses precision. Write `.toFloat` instead. +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:28: warning: Automatic conversion from Int to Float is deprecated (since 2.13.1) because it loses precision. Write `.toFloat` instead. +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:30: warning: Automatic conversion from Int to Float is deprecated (since 2.13.1) because it loses precision. Write `.toFloat` instead. +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:33: warning: Automatic conversion from Long to Double is deprecated (since 2.13.1) because it loses precision. Write `.toDouble` instead. +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:35: warning: Automatic conversion from Long to Double is deprecated (since 2.13.1) because it loses precision. Write `.toDouble` instead. +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 ^ @@ -35,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. -12 warnings +21 warnings 1 error diff --git a/test/files/neg/deprecated_widening.scala b/test/files/neg/deprecated_widening.scala index 0da49396e740..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 = { @@ -21,16 +21,36 @@ object Test { 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 } From da8200111a170ee3d79f4a757db0abbed41b341f Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Thu, 20 Feb 2020 17:39:48 -0800 Subject: [PATCH 3/3] Update spec for deprecated conversions --- spec/06-expressions.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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