Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deprecate numeric widening of numeric literals which are not representable with Float/Double #8757

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
}