Skip to content

Commit 6b16203

Browse files
authored
Merge pull request scala#10414 from som-snytt/issue/12792-complement-constant
Fold selection of unary ops
2 parents ebcee62 + 10d16ea commit 6b16203

File tree

6 files changed

+176
-43
lines changed

6 files changed

+176
-43
lines changed

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

Lines changed: 47 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ abstract class ConstantFolder {
2727
val global: Global
2828
import global._
2929

30+
val foldableUnaryOps: Set[Name] = nme.isEncodedUnary ++ List(nme.toChar, nme.toInt, nme.toLong, nme.toFloat, nme.toDouble)
31+
3032
// We can fold side effect free terms and their types
3133
object FoldableTerm {
3234
@inline private def effectless(sym: Symbol): Boolean = sym != null && !sym.isLazy && (sym.isVal || sym.isGetter && sym.accessed.isVal)
@@ -55,13 +57,13 @@ abstract class ConstantFolder {
5557
}
5658

5759
/** If tree is a constant operation, replace with result. */
58-
def apply(tree: Tree, site: Symbol): Tree = {
60+
def apply(tree: Tree, site: Symbol): Tree = if (isPastTyper) tree else
5961
try {
6062
tree match {
61-
case Apply(Select(FoldableTerm(x), op), List(FoldableTerm(y))) => fold(tree, foldBinop(op, x, y), true)
62-
case Apply(Select(ConstantTerm(x), op), List(ConstantTerm(y))) => fold(tree, foldBinop(op, x, y), false)
63-
case Select(FoldableTerm(x), op) => fold(tree, foldUnop(op, x), true)
64-
case Select(ConstantTerm(x), op) => fold(tree, foldUnop(op, x), false)
63+
case Apply(Select(FoldableTerm(x), op), List(FoldableTerm(y))) => fold(tree, foldBinop(op, x, y), foldable = true)
64+
case Apply(Select(ConstantTerm(x), op), List(ConstantTerm(y))) => fold(tree, foldBinop(op, x, y), foldable = false)
65+
case Select(FoldableTerm(x), op) => fold(tree, foldUnop(op, x), foldable = true)
66+
case Select(ConstantTerm(x), op) => fold(tree, foldUnop(op, x), foldable = false)
6567
case _ => tree
6668
}
6769
} catch {
@@ -70,41 +72,59 @@ abstract class ConstantFolder {
7072
runReporting.warning(tree.pos, s"Evaluation of a constant expression results in an arithmetic error: ${e.getMessage}", WarningCategory.LintConstant, site)
7173
tree
7274
}
73-
}
7475

75-
/** If tree is a constant value that can be converted to type `pt`, perform
76-
* the conversion.
76+
/** If tree is a constant value that can be converted to type `pt`, perform the conversion.
7777
*/
7878
def apply(tree: Tree, pt: Type, site: Symbol): Tree = {
7979
val orig = apply(tree, site)
8080
orig.tpe match {
81-
case tp@ConstantType(x) => fold(orig, x convertTo pt, isConstantType(tp))
81+
case tp@ConstantType(x) => fold(orig, x.convertTo(pt), foldable = isConstantType(tp))
8282
case _ => orig
8383
}
8484
}
8585

86+
/** Set the computed constant type.
87+
*/
8688
private def fold(orig: Tree, folded: Constant, foldable: Boolean): Tree =
8789
if ((folded eq null) || folded.tag == UnitTag) orig
88-
else if(foldable) orig setType FoldableConstantType(folded)
90+
else if (foldable) orig setType FoldableConstantType(folded)
8991
else orig setType LiteralType(folded)
9092

91-
private def foldUnop(op: Name, x: Constant): Constant = (op, x.tag) match {
92-
case (nme.UNARY_!, BooleanTag) => Constant(!x.booleanValue)
93-
94-
case (nme.UNARY_~ , IntTag ) => Constant(~x.intValue)
95-
case (nme.UNARY_~ , LongTag ) => Constant(~x.longValue)
96-
97-
case (nme.UNARY_+ , IntTag ) => Constant(+x.intValue)
98-
case (nme.UNARY_+ , LongTag ) => Constant(+x.longValue)
99-
case (nme.UNARY_+ , FloatTag ) => Constant(+x.floatValue)
100-
case (nme.UNARY_+ , DoubleTag ) => Constant(+x.doubleValue)
101-
102-
case (nme.UNARY_- , IntTag ) => Constant(-x.intValue)
103-
case (nme.UNARY_- , LongTag ) => Constant(-x.longValue)
104-
case (nme.UNARY_- , FloatTag ) => Constant(-x.floatValue)
105-
case (nme.UNARY_- , DoubleTag ) => Constant(-x.doubleValue)
106-
107-
case _ => null
93+
private def foldUnop(op: Name, x: Constant): Constant = {
94+
val N = nme
95+
import N._
96+
val value: Any = op match {
97+
case UNARY_! => if (x.tag == BooleanTag) !x.booleanValue else null
98+
case UNARY_~ => x.tag match {
99+
case IntTag => ~x.intValue
100+
case LongTag => ~x.longValue
101+
case _ => null
102+
}
103+
case UNARY_+ => x.tag match {
104+
case IntTag => +x.intValue
105+
case LongTag => +x.longValue
106+
case FloatTag => +x.floatValue
107+
case DoubleTag => +x.doubleValue
108+
case _ => null
109+
}
110+
case UNARY_- => x.tag match {
111+
case IntTag => -x.intValue
112+
case LongTag => -x.longValue
113+
case FloatTag => -x.floatValue
114+
case DoubleTag => -x.doubleValue
115+
case _ => null
116+
}
117+
case _ if x.isNumeric => op match {
118+
case `toChar` => x.charValue
119+
case `toInt` => x.intValue
120+
case `toLong` => x.longValue
121+
case `toFloat` => x.floatValue
122+
case `toDouble` => x.doubleValue
123+
case _ => null
124+
}
125+
case _ => null
126+
}
127+
if (value != null) Constant(value) else null
108128
}
109129

110130
/** These are local helpers to keep foldBinop from overly taxing the

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5453,7 +5453,8 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
54535453
)
54545454
case _ =>
54555455
if (settings.lintUniversalMethods && qualTp.widen.eq(UnitTpe)) checkDubiousUnitSelection(result)
5456-
result
5456+
if (isConstantType(qualTp) && constfold.foldableUnaryOps(name)) constfold(result, context.owner)
5457+
else result
54575458
}
54585459
}
54595460
}
Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
1-
serialversionuid-not-const.scala:1: error: annotation argument needs to be a constant; found: 13L.toLong
2-
@SerialVersionUID(13l.toLong) class C1 extends Serializable
3-
^
41
serialversionuid-not-const.scala:3: error: annotation argument needs to be a constant; found: 13.asInstanceOf[Long]
52
@SerialVersionUID(13.asInstanceOf[Long]) class C3 extends Serializable
63
^
74
serialversionuid-not-const.scala:4: error: annotation argument needs to be a constant; found: Test.bippy
85
@SerialVersionUID(Test.bippy) class C4 extends Serializable
96
^
10-
3 errors
7+
2 errors

test/files/pos/t12792.scala

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
2+
// scalac: -Werror -Xlint
3+
4+
import annotation._
5+
6+
object Foo {
7+
final val w = 1 << (java.lang.Integer.SIZE - 1)
8+
final val x = 1 << (java.lang.Integer.SIZE - 2)
9+
final val y = 1 << (java.lang.Integer.SIZE - 3)
10+
final val z = 1 << (java.lang.Integer.SIZE - 4)
11+
final val c = 0xffffffff & ~w & ~x & ~y & ~z
12+
13+
final val i = +42 // 42.unary_+
14+
final val j = -27 // literal -42
15+
}
16+
17+
class Ann(value: Int) extends ConstantAnnotation
18+
class Byt(value: Byte) extends ConstantAnnotation
19+
20+
class Test {
21+
import Foo._
22+
@Ann(w) def fw = 42
23+
@Ann(x) def fx = 42
24+
@Ann(c) def fc = 42
25+
@Ann(i) def fi = 42
26+
@Ann(j) def fj = 42
27+
@Byt(42) def byteMe = 42
28+
}
29+
30+
class AnnL(value: Long) extends ConstantAnnotation
31+
class AnnD(value: Double) extends ConstantAnnotation
32+
33+
object i17446Types {
34+
35+
final val myInt = 1 << 6
36+
37+
// toLong
38+
final val char2Long: 99L = 'c'.toLong
39+
final val int2Long: 0L = 0.toLong
40+
final val long2Long: 0L = 0L.toLong
41+
final val int2LongPropagated: 64L = myInt.toLong
42+
43+
// toInt
44+
final val char2Int: 99 = 'c'.toInt
45+
final val int2Int: 0 = 0.toInt
46+
final val long2Int: 0 = 0L.toInt
47+
final val long2IntWrapped: -2147483648 = 2147483648L.toInt
48+
final val int2IntPropagated: 64 = myInt.toInt
49+
50+
// toChar
51+
final val char2Char: 'c' = 'c'.toChar
52+
final val int2Char: 'c' = 99.toChar
53+
final val long2Char: 'c' = 99L.toChar
54+
final val int2CharPropagated: '@' = myInt.toChar
55+
56+
// chain everything
57+
final val wow: 1.0 = 1.toChar.toInt.toLong.toFloat.toDouble
58+
}
59+
object i17446 {
60+
61+
final val myInt = 1 << 6
62+
63+
// toLong
64+
final val char2Long = 'c'.toLong
65+
final val int2Long = 0.toLong
66+
final val long2Long = 0L.toLong
67+
final val int2LongPropagated = myInt.toLong
68+
69+
// toInt
70+
final val char2Int = 'c'.toInt
71+
final val int2Int = 0.toInt
72+
final val long2Int = 0L.toInt
73+
final val long2IntWrapped = 2147483648L.toInt
74+
final val int2IntPropagated = myInt.toInt
75+
76+
// toChar
77+
final val char2Char = 'c'.toChar
78+
final val int2Char = 99.toChar
79+
final val long2Char = 99L.toChar
80+
final val int2CharPropagated = myInt.toChar
81+
82+
// chain everything
83+
final val wow = 1.toChar.toInt.toLong.toFloat.toDouble
84+
}
85+
class i17446 {
86+
import i17446._
87+
@Ann(char2Int) def a = 42
88+
@Ann(int2Int) def b = 42
89+
@Ann(long2Int) def c = 42
90+
@Ann(long2IntWrapped) def d = 42
91+
@Ann(int2IntPropagated) def e = 42
92+
@Ann(char2Char) def f = 42
93+
@Ann(int2Char) def g = 42
94+
@Ann(long2Char) def h = 42
95+
@Ann(int2CharPropagated) def i = 42
96+
97+
@AnnL(char2Long) def j = 42
98+
@AnnL(int2Long) def k = 42
99+
@AnnL(long2Long) def l = 42
100+
@AnnL(int2LongPropagated) def m = 42
101+
102+
@AnnD(wow) def n = 42
103+
}

test/files/run/t12062.check

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,6 @@ source-newSource2.scala,line-27 TestShort.this.value()
102102

103103
class TestInt
104104
source-newSource3.scala,line-2 TestInt.super.<init>()
105-
source-newSource3.scala,line-3 1.toInt()
106105
source-newSource3.scala,line-6 java.lang.Integer.toString(TestInt.this.value())
107106
source-newSource3.scala,line-6 TestInt.this.value()
108107
source-newSource3.scala,line-7 java.lang.Integer.hashCode(TestInt.this.value())
@@ -151,7 +150,6 @@ source-newSource3.scala,line-27 TestInt.this.value()
151150

152151
class TestLong
153152
source-newSource4.scala,line-2 TestLong.super.<init>()
154-
source-newSource4.scala,line-3 1.toLong()
155153
source-newSource4.scala,line-6 java.lang.Long.toString(TestLong.this.value())
156154
source-newSource4.scala,line-6 TestLong.this.value()
157155
source-newSource4.scala,line-7 java.lang.Long.hashCode(TestLong.this.value())
@@ -220,7 +218,6 @@ source-newSource6.scala,line-8 TestChar.this.value()
220218

221219
class TestFloat
222220
source-newSource7.scala,line-2 TestFloat.super.<init>()
223-
source-newSource7.scala,line-3 1.toFloat()
224221
source-newSource7.scala,line-6 java.lang.Float.toString(TestFloat.this.value())
225222
source-newSource7.scala,line-6 TestFloat.this.value()
226223
source-newSource7.scala,line-7 java.lang.Float.hashCode(TestFloat.this.value())
@@ -297,7 +294,6 @@ source-newSource7.scala,line-38 TestFloat.this.value()
297294

298295
class TestDouble
299296
source-newSource8.scala,line-2 TestDouble.super.<init>()
300-
source-newSource8.scala,line-3 1.toDouble()
301297
source-newSource8.scala,line-6 java.lang.Double.toString(TestDouble.this.value())
302298
source-newSource8.scala,line-6 TestDouble.this.value()
303299
source-newSource8.scala,line-7 java.lang.Double.hashCode(TestDouble.this.value())

test/junit/scala/tools/nsc/typechecker/ConstantFolderTest.scala

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
package scala.tools.nsc.typechecker
1+
package scala.tools.nsc
2+
package typechecker
23

34
import org.junit.Test
45
import org.junit.Assert.assertTrue
56

67
import scala.tools.testkit.BytecodeTesting
78

8-
99
class ConstantFolderTest extends BytecodeTesting {
1010
import compiler._
1111
import global._
@@ -14,13 +14,10 @@ class ConstantFolderTest extends BytecodeTesting {
1414

1515
def literalValDefAssert(tree: Tree, name: String, constant: Constant): Unit = {
1616
val valDef: ValDef = tree.collect {
17-
case node @ ValDef(_, _, _, _) if node.name.decodedName.toString.trim == name =>
18-
node
17+
case node @ ValDef(_, nm, _, _) if nm.decoded.trim == name => node
1918
}.head
2019

21-
assertTrue {
22-
valDef.collect { case node @ Literal(constant) => node }.nonEmpty
23-
}
20+
assertTrue(s"Expected val $name: $constant", valDef.collect { case node @ Literal(`constant`) => node }.nonEmpty)
2421
}
2522

2623
@Test
@@ -48,6 +45,7 @@ class ConstantFolderTest extends BytecodeTesting {
4845
| final val x5 = true || false
4946
| final val x6 = false || true
5047
| final val x7 = false || false
48+
| final val x8 = !x7
5149
|}
5250
""".stripMargin
5351
val run = compiler.newRun()
@@ -61,5 +59,23 @@ class ConstantFolderTest extends BytecodeTesting {
6159
literalValDefAssert(tree, "x5", Constant(true))
6260
literalValDefAssert(tree, "x6", Constant(true))
6361
literalValDefAssert(tree, "x7", Constant(false))
62+
literalValDefAssert(tree, "x8", Constant(true))
63+
}
64+
65+
@Test def `fold unary ops`: Unit = {
66+
val code =
67+
sm"""|object X {
68+
| final val x0 = 0xff
69+
| final val x1 = ~0xff
70+
| final val x2 = x0 & ~0xf
71+
| final val x3 = x0.toLong
72+
|}"""
73+
val run = compiler.newRun()
74+
run.compileSources(List(BytecodeTesting.makeSourceFile(code, "UnitTestSource.scala")))
75+
val tree = run.units.next().body
76+
literalValDefAssert(tree, "x0", Constant(0xff))
77+
literalValDefAssert(tree, "x1", Constant(0xffffff00))
78+
literalValDefAssert(tree, "x2", Constant(0xf0))
79+
literalValDefAssert(tree, "x3", Constant(0xffL))
6480
}
6581
}

0 commit comments

Comments
 (0)