summaryrefslogtreecommitdiff
path: root/test/junit
diff options
context:
space:
mode:
Diffstat (limited to 'test/junit')
-rw-r--r--test/junit/scala/BoxUnboxTest.scala119
-rw-r--r--test/junit/scala/PartialFunctionSerializationTest.scala36
-rw-r--r--test/junit/scala/PredefAutoboxingTest.scala35
-rw-r--r--test/junit/scala/StringContextTest.scala191
-rw-r--r--test/junit/scala/collection/IteratorTest.scala22
-rw-r--r--test/junit/scala/collection/ReusableBuildersTest.scala48
-rw-r--r--test/junit/scala/collection/SeqViewTest.scala16
-rw-r--r--test/junit/scala/collection/SetMapConsistencyTest.scala23
-rw-r--r--test/junit/scala/collection/convert/NullSafetyTest.scala279
-rw-r--r--test/junit/scala/collection/convert/WrapperSerializationTest.scala29
-rw-r--r--test/junit/scala/collection/immutable/SetTest.scala (renamed from test/junit/scala/collection/immutable/SetTests.scala)2
-rw-r--r--test/junit/scala/collection/immutable/StreamTest.scala126
-rw-r--r--test/junit/scala/collection/immutable/StringLikeTest.scala6
-rw-r--r--test/junit/scala/collection/mutable/BitSetTest.scala7
-rw-r--r--test/junit/scala/collection/mutable/OpenHashMapTest.scala2
-rw-r--r--test/junit/scala/collection/mutable/PriorityQueueTest.scala7
-rw-r--r--test/junit/scala/io/SourceTest.scala4
-rw-r--r--test/junit/scala/issues/BytecodeTest.scala478
-rw-r--r--test/junit/scala/issues/BytecodeTests.scala80
-rw-r--r--test/junit/scala/issues/OptimizedBytecodeTest.scala375
-rw-r--r--test/junit/scala/issues/RunTest.scala162
-rw-r--r--test/junit/scala/math/BigDecimalTest.scala5
-rw-r--r--test/junit/scala/math/BigIntTest.scala16
-rw-r--r--test/junit/scala/reflect/ClassTagTest.scala (renamed from test/junit/scala/reflect/ClassTag.scala)0
-rw-r--r--test/junit/scala/reflect/internal/PrintersTest.scala26
-rw-r--r--test/junit/scala/reflect/internal/TreeGenTest.scala51
-rw-r--r--test/junit/scala/reflect/internal/TypesTest.scala28
-rw-r--r--test/junit/scala/runtime/LambdaDeserializerTest.java193
-rw-r--r--test/junit/scala/runtime/ScalaRunTimeTest.scala108
-rw-r--r--test/junit/scala/sys/process/t7350.scala298
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala2
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala80
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/DefaultMethodTest.scala43
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala2
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/IndyLambdaTest.scala74
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/IndySammyTest.scala160
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/StringConcatTest.scala133
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzerTest.scala124
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerTest.scala3
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/AnalyzerTest.scala63
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala4
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala197
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/ClosureOptimizerTest.scala108
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala4
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala4
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala14
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala65
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala6
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala17
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala862
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOpts.scala92
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOptsTest.scala762
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala153
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/SimplifyJumpsTest.scala23
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala64
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/UnusedLocalVariablesTest.scala2
-rw-r--r--test/junit/scala/tools/nsc/classpath/AggregateClassPathTest.scala (renamed from test/junit/scala/tools/nsc/classpath/AggregateFlatClassPathTest.scala)27
-rw-r--r--test/junit/scala/tools/nsc/classpath/PathResolverBaseTest.scala (renamed from test/junit/scala/tools/nsc/classpath/FlatClassPathResolverTest.scala)59
-rw-r--r--test/junit/scala/tools/nsc/settings/ScalaVersionTest.scala53
-rw-r--r--test/junit/scala/tools/nsc/settings/SettingsTest.scala14
-rw-r--r--test/junit/scala/tools/nsc/symtab/FlagsTest.scala8
-rw-r--r--test/junit/scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala31
-rw-r--r--test/junit/scala/tools/nsc/transform/delambdafy/DelambdafyTest.scala4
-rw-r--r--test/junit/scala/tools/nsc/transform/patmat/PatmatBytecodeTest.scala195
-rw-r--r--test/junit/scala/tools/nsc/util/ClassPathImplComparator.scala143
-rw-r--r--test/junit/scala/util/SystemPropertiesTest.scala27
-rw-r--r--test/junit/scala/util/control/ExceptionTest.scala42
67 files changed, 5544 insertions, 892 deletions
diff --git a/test/junit/scala/BoxUnboxTest.scala b/test/junit/scala/BoxUnboxTest.scala
new file mode 100644
index 0000000000..162d805a6b
--- /dev/null
+++ b/test/junit/scala/BoxUnboxTest.scala
@@ -0,0 +1,119 @@
+package scala
+
+import org.junit.Test
+import org.junit.Assert._
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+import scala.tools.testing.AssertUtil._
+
+@RunWith(classOf[JUnit4])
+class BoxUnboxTest {
+ def genericNull[T] = null.asInstanceOf[T] // allowed, see SI-4437, point 2
+
+ @Test
+ def boxUnboxInt(): Unit = {
+ val b = new Integer(1)
+ val u = 1
+
+ assertEquals(1.toInt, u)
+
+ assertEquals(Predef.int2Integer(1), b)
+ assertEquals(1: Integer, b)
+ assertEquals(Int.box(1), b)
+ assertEquals(1.asInstanceOf[Object], b)
+
+ assertThrows[ClassCastException]("".asInstanceOf[Integer])
+
+ assertEquals(Predef.Integer2int(b), u)
+ assertEquals(b: Int, u)
+ assertEquals(Int.unbox(b), u)
+ assertEquals(b.asInstanceOf[Int], u)
+ assertEquals(b.intValue, u)
+ assertEquals(b.toInt, u)
+ intWrapper(b).toInt
+
+ assertThrows[ClassCastException](Int.unbox(""))
+ assertThrows[ClassCastException]("".asInstanceOf[Int])
+
+ // null unboxing in various positions
+
+ val n1 = Int.unbox(null)
+ assertEquals(n1, 0)
+ val n2 = Predef.Integer2int(null)
+ assertEquals(n2, 0)
+ val n3 = (null: Integer): Int
+ assertEquals(n3, 0)
+ val n4 = null.asInstanceOf[Int]
+ assertEquals(n4, 0)
+ val n5 = null.asInstanceOf[Int] == 0
+ assertTrue(n5)
+ val n6 = null.asInstanceOf[Int] == null // SI-9671 -- should be false, but is true
+ assertThrows[AssertionError](assertFalse(n6)) // should not throw
+ val n7 = null.asInstanceOf[Int] != 0
+ assertFalse(n7)
+ val n8 = null.asInstanceOf[Int] != null // SI-9671 -- should be true, but is false
+ assertThrows[AssertionError](assertTrue(n8)) // should not throw
+
+ val mp = new java.util.HashMap[Int, Int]
+ val n9 = mp.get(0)
+ assertEquals(n9, 0)
+ val n10 = mp.get(0) == null // SI-602 -- maybe related to SI-9671 (test above)?
+ assertThrows[AssertionError](assertFalse(n10)) // should not throw
+
+ def f(a: Any) = "" + a
+ val n11 = f(null.asInstanceOf[Int]) // "null", should be "0". probably same cause as SI-602.
+ assertThrows[AssertionError](assertEquals(n11, "0")) // should not throw
+
+ def n12 = genericNull[Int]
+ assertEquals(n12, 0)
+ }
+
+ @Test
+ def numericConversions(): Unit = {
+ val i1 = 1L.asInstanceOf[Int]
+ assertEquals(i1, 1)
+ assertThrows[ClassCastException] {
+ val i2 = (1L: Any).asInstanceOf[Int] // SI-1448, should not throw. see also SI-4437 point 1.
+ assertEquals(i2, 1)
+ }
+ }
+
+ @Test
+ def boxUnboxBoolean(): Unit = {
+ val n1 = Option(null.asInstanceOf[Boolean]) // SI-7397 -- should be Some(false), but is None
+ assertThrows[AssertionError](assertEquals(n1, Some(false))) // should not throw
+ }
+
+ @Test
+ def boxUnboxUnit(): Unit = {
+ // should not use assertEquals in this test: it takes two Object parameters. normally, Unit does
+ // not conform to Object, but for Java-defined methods scalac makes an exception and treats them
+ // as Any. passing a Unit as Any makes the compiler go through another layer of boxing, so it
+ // can hide some bugs (where we actually have a null, but the compiler makes it a ()).
+
+ var v = 0
+ def eff() = { v = 1 }
+ def chk() = { assert(v == 1); v = 0 }
+
+ val b = runtime.BoxedUnit.UNIT
+
+ assert(eff() == b); chk()
+ assert(Unit.box(eff()) == b); chk()
+ assert(().asInstanceOf[Object] == b)
+
+ Unit.unbox({eff(); b}); chk()
+ Unit.unbox({eff(); null}); chk()
+ assertThrows[ClassCastException](Unit.unbox({eff(); ""})); chk()
+
+ val n1 = null.asInstanceOf[Unit] // SI-9066: should be UNIT, but currently null
+ assertThrows[AssertionError](assert(n1 == b)) // should not throw
+
+ val n2 = null.asInstanceOf[Unit] == b // SI-9066: should be true, but currently false
+ assertThrows[AssertionError](assert(n2)) // should not throw
+
+ def f(a: Any) = "" + a
+ val n3 = f(null.asInstanceOf[Unit]) // "null", should be "()". probably same cause as SI-602.
+ assertThrows[AssertionError](assertEquals(n3, "()")) // should not throw
+ }
+}
diff --git a/test/junit/scala/PartialFunctionSerializationTest.scala b/test/junit/scala/PartialFunctionSerializationTest.scala
new file mode 100644
index 0000000000..d525b045cd
--- /dev/null
+++ b/test/junit/scala/PartialFunctionSerializationTest.scala
@@ -0,0 +1,36 @@
+package scala
+
+import org.junit.Test
+import org.junit.Assert._
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(classOf[JUnit4])
+class PartialFunctionSerializationTest {
+ val pf1: PartialFunction[Int, Int] = {
+ case n if n > 0 => 1
+ }
+
+ val pf2: PartialFunction[Int, Int] = {
+ case n if n <= 0 => 2
+ }
+
+
+ private def assertSerializable[A,B](fn: A => B) = {
+ import java.io._
+
+ new ObjectOutputStream(new ByteArrayOutputStream()).writeObject(fn)
+ }
+
+ @Test def canSerializeLiteral= assertSerializable(pf1)
+
+ @Test def canSerializeLifted= assertSerializable(pf1.lift)
+
+ @Test def canSerializeOrElse = assertSerializable(pf1 orElse pf2)
+
+ @Test def canSerializeUnlifted = assertSerializable(Function.unlift((x: Int) => Some(x)))
+
+ @Test def canSerializeAndThen = assertSerializable(pf1.andThen((x: Int) => x))
+
+ @Test def canSerializeEmpty = assertSerializable(PartialFunction.empty)
+}
diff --git a/test/junit/scala/PredefAutoboxingTest.scala b/test/junit/scala/PredefAutoboxingTest.scala
new file mode 100644
index 0000000000..e5d8ded5d4
--- /dev/null
+++ b/test/junit/scala/PredefAutoboxingTest.scala
@@ -0,0 +1,35 @@
+package scala
+
+import org.junit.Test
+import org.junit.Assert._
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+import scala.tools.testing.AssertUtil._
+
+@RunWith(classOf[JUnit4])
+class PredefAutoboxingTest {
+ @Test def unboxNullByte() =
+ assertEquals(Predef.Byte2byte(null), 0.toByte)
+
+ @Test def unboxNullShort() =
+ assertEquals(Predef.Short2short(null), 0.toShort)
+
+ @Test def unboxNullCharacter() =
+ assertEquals(Predef.Character2char(null), 0.toChar)
+
+ @Test def unboxNullInteger() =
+ assertEquals(Predef.Integer2int(null), 0)
+
+ @Test def unboxNullLong() =
+ assertEquals(Predef.Long2long(null), 0L)
+
+ @Test def unboxNullFloat() =
+ assertEquals(Predef.Float2float(null), 0F, 0F)
+
+ @Test def unboxNullDouble() =
+ assertEquals(Predef.Double2double(null), 0D, 0D)
+
+ @Test def unboxNullBoolean() =
+ assertEquals(Predef.Boolean2boolean(null), false)
+}
diff --git a/test/junit/scala/StringContextTest.scala b/test/junit/scala/StringContextTest.scala
index 7e9e775d58..b5af6de7eb 100644
--- a/test/junit/scala/StringContextTest.scala
+++ b/test/junit/scala/StringContextTest.scala
@@ -1,6 +1,10 @@
package scala
+import java.text.DecimalFormat
+
+import language.implicitConversions
+
import org.junit.Test
import org.junit.Assert._
import org.junit.runner.RunWith
@@ -8,10 +12,32 @@ import org.junit.runners.JUnit4
import scala.tools.testing.AssertUtil._
+object StringContextTestUtils {
+ private val decimalSeparator: Char = new DecimalFormat().getDecimalFormatSymbols().getDecimalSeparator()
+ private val numberPattern = """(\d+)\.(\d+.*)""".r
+
+ implicit class StringContextOps(val sc: StringContext) extends AnyVal {
+ // Use this String interpolator to avoid problems with a locale-dependent decimal mark.
+ def locally(numbers: String*): String = {
+ val numbersWithCorrectLocale = numbers.map(applyProperLocale)
+ sc.s(numbersWithCorrectLocale: _*)
+ }
+
+ // Handles cases like locally"3.14" - it's prettier than locally"${"3.14"}".
+ def locally(): String = sc.parts.map(applyProperLocale).mkString
+
+ private def applyProperLocale(number: String): String = {
+ val numberPattern(intPart, fractionalPartAndSuffix) = number
+ s"$intPart$decimalSeparator$fractionalPartAndSuffix"
+ }
+ }
+}
+
@RunWith(classOf[JUnit4])
class StringContextTest {
import StringContext._
+ import StringContextTestUtils.StringContextOps
@Test def noEscape() = {
val s = "string"
@@ -65,23 +91,176 @@ class StringContextTest {
@Test def fIf() = {
val res = f"${if (true) 2.5 else 2.5}%.2f"
- val expected = formatUsingCurrentLocale(2.50)
+ val expected = locally"2.50"
assertEquals(expected, res)
}
@Test def fIfNot() = {
val res = f"${if (false) 2.5 else 3.5}%.2f"
- val expected = formatUsingCurrentLocale(3.50)
+ val expected = locally"3.50"
assertEquals(expected, res)
}
@Test def fHeteroArgs() = {
val res = f"${3.14}%.2f rounds to ${3}%d"
- val expected = formatUsingCurrentLocale(3.14) + " rounds to 3"
+ val expected = locally"${"3.14"} rounds to 3"
assertEquals(expected, res)
}
- // Use this method to avoid problems with a locale-dependent decimal mark.
- // The string interpolation is not used here intentionally as this method is used to test string interpolation.
- private def formatUsingCurrentLocale(number: Double, decimalPlaces: Int = 2) = ("%." + decimalPlaces + "f").format(number)
+ @Test def `f interpolator baseline`(): Unit = {
+
+ implicit def stringToBoolean(s: String): Boolean = java.lang.Boolean.parseBoolean(s)
+ implicit def stringToChar(s: String): Char = s(0)
+ implicit def str2fmt(s: String): java.util.Formattable = new java.util.Formattable {
+ def formatTo(f: java.util.Formatter, g: Int, w: Int, p: Int) = f.format("%s", s)
+ }
+
+ val b_true = true
+ val b_false = false
+
+ val i = 42
+
+ val f_zero = 0.0
+ val f_zero_- = -0.0
+
+ val s = "Scala"
+
+ val fff = new java.util.Formattable {
+ def formatTo(f: java.util.Formatter, g: Int, w: Int, p: Int) = f.format("4")
+ }
+ import java.util.{ Calendar, Locale }
+ val c = Calendar.getInstance(Locale.US)
+ c.set(2012, Calendar.MAY, 26)
+ implicit def strToDate(x: String): Calendar = c
+
+ val ss = List[(String, String)] (
+ // 'b' / 'B' (category: general)
+ // -----------------------------
+ f"${b_false}%b" -> "false",
+ f"${b_true}%b" -> "true",
+
+ f"${null}%b" -> "false",
+ f"${false}%b" -> "false",
+ f"${true}%b" -> "true",
+ f"${true && false}%b" -> "false",
+ f"${new java.lang.Boolean(false)}%b" -> "false",
+ f"${new java.lang.Boolean(true)}%b" -> "true",
+
+ f"${null}%B" -> "FALSE",
+ f"${false}%B" -> "FALSE",
+ f"${true}%B" -> "TRUE",
+ f"${new java.lang.Boolean(false)}%B" -> "FALSE",
+ f"${new java.lang.Boolean(true)}%B" -> "TRUE",
+
+ f"${"true"}%b" -> "true",
+ f"${"false"}%b"-> "false",
+
+ // 'h' | 'H' (category: general)
+ // -----------------------------
+ f"${null}%h" -> "null",
+ f"${f_zero}%h" -> "0",
+ f"${f_zero_-}%h" -> "80000000",
+ f"${s}%h" -> "4c01926",
+
+ f"${null}%H" -> "NULL",
+ f"${s}%H" -> "4C01926",
+
+ // 's' | 'S' (category: general)
+ // -----------------------------
+ f"${null}%s" -> "null",
+ f"${null}%S" -> "NULL",
+ f"${s}%s" -> "Scala",
+ f"${s}%S" -> "SCALA",
+ f"${5}" -> "5",
+ f"${i}" -> "42",
+ f"${'foo}" -> "'foo",
+
+ f"${Thread.State.NEW}" -> "NEW",
+
+ // 'c' | 'C' (category: character)
+ // -------------------------------
+ f"${120:Char}%c" -> "x",
+ f"${120:Byte}%c" -> "x",
+ f"${120:Short}%c" -> "x",
+ f"${120:Int}%c" -> "x",
+ f"${new java.lang.Character('x')}%c" -> "x",
+ f"${new java.lang.Byte(120:Byte)}%c" -> "x",
+ f"${new java.lang.Short(120:Short)}%c" -> "x",
+ f"${new java.lang.Integer(120)}%c" -> "x",
+
+ f"${'x' : java.lang.Character}%c" -> "x",
+ f"${(120:Byte) : java.lang.Byte}%c" -> "x",
+ f"${(120:Short) : java.lang.Short}%c" -> "x",
+ f"${120 : java.lang.Integer}%c" -> "x",
+
+ f"${"Scala"}%c" -> "S",
+
+ // 'd' | 'o' | 'x' | 'X' (category: integral)
+ // ------------------------------------------
+ f"${120:Byte}%d" -> "120",
+ f"${120:Short}%d" -> "120",
+ f"${120:Int}%d" -> "120",
+ f"${120:Long}%d" -> "120",
+ f"${60 * 2}%d" -> "120",
+ f"${new java.lang.Byte(120:Byte)}%d" -> "120",
+ f"${new java.lang.Short(120:Short)}%d" -> "120",
+ f"${new java.lang.Integer(120)}%d" -> "120",
+ f"${new java.lang.Long(120)}%d" -> "120",
+ f"${120 : java.lang.Integer}%d" -> "120",
+ f"${120 : java.lang.Long}%d" -> "120",
+ f"${BigInt(120)}%d" -> "120",
+
+ f"${new java.math.BigInteger("120")}%d" -> "120",
+
+ f"${4}%#10X" -> " 0X4",
+
+ f"She is ${fff}%#s feet tall." -> "She is 4 feet tall.",
+
+ f"Just want to say ${"hello, world"}%#s..." -> "Just want to say hello, world...",
+
+ { implicit val strToShort = (s: String) => java.lang.Short.parseShort(s) ; f"${"120"}%d" } -> "120",
+ { implicit val strToInt = (s: String) => 42 ; f"${"120"}%d" } -> "42",
+
+ // 'e' | 'E' | 'g' | 'G' | 'f' | 'a' | 'A' (category: floating point)
+ // ------------------------------------------------------------------
+ f"${3.4f}%e" -> locally"3.400000e+00",
+ f"${3.4}%e" -> locally"3.400000e+00",
+ f"${3.4f : java.lang.Float}%e" -> locally"3.400000e+00",
+ f"${3.4 : java.lang.Double}%e" -> locally"3.400000e+00",
+
+ f"${BigDecimal(3.4)}%e" -> locally"3.400000e+00",
+
+ f"${new java.math.BigDecimal(3.4)}%e" -> locally"3.400000e+00",
+
+ f"${3}%e" -> locally"3.000000e+00",
+ f"${3L}%e" -> locally"3.000000e+00",
+
+ // 't' | 'T' (category: date/time)
+ // -------------------------------
+ f"${c}%TD" -> "05/26/12",
+ f"${c.getTime}%TD" -> "05/26/12",
+ f"${c.getTime.getTime}%TD" -> "05/26/12",
+ f"""${"1234"}%TD""" -> "05/26/12",
+
+ // literals and arg indexes
+ f"%%" -> "%",
+ f" mind%n------%nmatter" ->
+ """| mind
+ |------
+ |matter""".stripMargin.lines.mkString(compat.Platform.EOL),
+ f"${i}%d %<d ${9}%d" -> "42 42 9",
+ f"${7}%d %<d ${9}%d" -> "7 7 9",
+ f"${7}%d %2$$d ${9}%d" -> "7 9 9",
+
+ f"${null}%d %<B" -> "null FALSE",
+
+ f"${5: Any}" -> "5",
+ f"${5}%s%<d" -> "55",
+ f"${3.14}%s,%<f" -> locally"3.14,${"3.140000"}",
+
+ f"z" -> "z"
+ )
+
+ for ((f, s) <- ss) assertEquals(s, f)
+ }
}
diff --git a/test/junit/scala/collection/IteratorTest.scala b/test/junit/scala/collection/IteratorTest.scala
index 329c85127a..4df29e36c0 100644
--- a/test/junit/scala/collection/IteratorTest.scala
+++ b/test/junit/scala/collection/IteratorTest.scala
@@ -135,6 +135,20 @@ class IteratorTest {
assertEquals(3, List(1, 2, 3, 4, 5).iterator.indexWhere { x: Int => x >= 4 })
assertEquals(-1, List(1, 2, 3, 4, 5).iterator.indexWhere { x: Int => x >= 16 })
}
+ @Test def indexOfFrom(): Unit = {
+ assertEquals(1, List(1, 2, 3, 4, 5).iterator.indexOf(2, 0))
+ assertEquals(1, List(1, 2, 3, 4, 5).iterator.indexOf(2, 1))
+ assertEquals(-1, List(1, 2, 3, 4, 5).iterator.indexOf(2, 2))
+ assertEquals(4, List(1, 2, 3, 2, 1).iterator.indexOf(1, 1))
+ assertEquals(1, List(1, 2, 3, 2, 1).iterator.indexOf(2, 1))
+ }
+ @Test def indexWhereFrom(): Unit = {
+ assertEquals(1, List(1, 2, 3, 4, 5).iterator.indexWhere(_ == 2, 0))
+ assertEquals(1, List(1, 2, 3, 4, 5).iterator.indexWhere(_ == 2, 1))
+ assertEquals(-1, List(1, 2, 3, 4, 5).iterator.indexWhere(_ == 2, 2))
+ assertEquals(4, List(1, 2, 3, 2, 1).iterator.indexWhere(_ < 2, 1))
+ assertEquals(1, List(1, 2, 3, 2, 1).iterator.indexWhere(_ <= 2, 1))
+ }
// iterator-iterate-lazy.scala
// was java.lang.UnsupportedOperationException: tail of empty list
@Test def iterateIsSufficientlyLazy(): Unit = {
@@ -154,6 +168,14 @@ class IteratorTest {
results += (Stream from 1).toIterator.drop(10).toStream.drop(10).toIterator.next()
assertSameElements(List(1,1,21), results)
}
+ // SI-8552
+ @Test def indexOfShouldWorkForTwoParams(): Unit = {
+ assertEquals(1, List(1, 2, 3).iterator.indexOf(2, 0))
+ assertEquals(-1, List(5 -> 0).iterator.indexOf(5, 0))
+ assertEquals(0, List(5 -> 0).iterator.indexOf((5, 0)))
+ assertEquals(-1, List(5 -> 0, 9 -> 2, 0 -> 3).iterator.indexOf(9, 2))
+ assertEquals(1, List(5 -> 0, 9 -> 2, 0 -> 3).iterator.indexOf(9 -> 2))
+ }
// SI-9332
@Test def spanExhaustsLeadingIterator(): Unit = {
def it = Iterator.iterate(0)(_ + 1).take(6)
diff --git a/test/junit/scala/collection/ReusableBuildersTest.scala b/test/junit/scala/collection/ReusableBuildersTest.scala
new file mode 100644
index 0000000000..8dd1a37adf
--- /dev/null
+++ b/test/junit/scala/collection/ReusableBuildersTest.scala
@@ -0,0 +1,48 @@
+package scala.collection
+
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.Test
+
+/* Tests various maps by making sure they all agree on the same answers. */
+@RunWith(classOf[JUnit4])
+class ReusableBuildersTest {
+ // GrowingBuilders are NOT reusable but can clear themselves
+ @Test
+ def test_SI8648() {
+ val b = collection.mutable.HashSet.newBuilder[Int]
+ b += 3
+ b.clear
+ assert(!b.isInstanceOf[collection.mutable.ReusableBuilder[_,_]])
+ assert(b.isInstanceOf[collection.mutable.GrowingBuilder[_,_]])
+ assert(b.result == Set[Int]())
+ }
+
+ // ArrayBuilders ARE reusable, regardless of whether they returned their internal array or not
+ @Test
+ def test_SI9564() {
+ val b = Array.newBuilder[Float]
+ b += 3f
+ val three = b.result
+ b.clear
+ b ++= (1 to 16).map(_.toFloat)
+ val sixteen = b.result
+ b.clear
+ b += 0f
+ val zero = b.result
+ assert(b.isInstanceOf[collection.mutable.ReusableBuilder[_,_]])
+ assert(three.toList == 3 :: Nil)
+ assert(sixteen.toList == (1 to 16))
+ assert(zero.toList == 0 :: Nil)
+ }
+
+ @Test
+ def test_reusability() {
+ val bl = List.newBuilder[String]
+ val bv = Vector.newBuilder[String]
+ val ba = collection.mutable.ArrayBuffer.newBuilder[String]
+ assert(bl.isInstanceOf[collection.mutable.ReusableBuilder[_, _]])
+ assert(bv.isInstanceOf[collection.mutable.ReusableBuilder[_, _]])
+ assert(!ba.isInstanceOf[collection.mutable.ReusableBuilder[_, _]])
+ }
+}
diff --git a/test/junit/scala/collection/SeqViewTest.scala b/test/junit/scala/collection/SeqViewTest.scala
new file mode 100644
index 0000000000..24474fc4b9
--- /dev/null
+++ b/test/junit/scala/collection/SeqViewTest.scala
@@ -0,0 +1,16 @@
+package scala.collection
+
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.Assert._
+import org.junit.Test
+
+@RunWith(classOf[JUnit4])
+class SeqViewTest {
+
+ @Test
+ def test_SI8691() {
+ // Really just testing to make sure ++: doesn't throw an exception
+ assert( Seq(1,2) ++: Seq(3,4).view == Seq(1,2,3,4) )
+ }
+}
diff --git a/test/junit/scala/collection/SetMapConsistencyTest.scala b/test/junit/scala/collection/SetMapConsistencyTest.scala
index 261c11a98b..eb864a8449 100644
--- a/test/junit/scala/collection/SetMapConsistencyTest.scala
+++ b/test/junit/scala/collection/SetMapConsistencyTest.scala
@@ -66,6 +66,8 @@ class SetMapConsistencyTest {
def boxMhm[A] = new BoxMutableMap[A, cm.HashMap[A, Int]](new cm.HashMap[A, Int], "mutable.HashMap")
def boxMohm[A] = new BoxMutableMap[A, cm.OpenHashMap[A, Int]](new cm.OpenHashMap[A, Int], "mutable.OpenHashMap")
+
+ def boxMtm[A: Ordering] = new BoxMutableMap[A, cm.TreeMap[A, Int]](new cm.TreeMap[A, Int], "mutable.TreeMap")
def boxMarm[A <: AnyRef] = new BoxMutableMap[A, cm.AnyRefMap[A, Int]](new cm.AnyRefMap[A, Int](_ => -1), "mutable.AnyRefMap") {
private def arm: cm.AnyRefMap[A, Int] = m.asInstanceOf[cm.AnyRefMap[A, Int]]
@@ -188,7 +190,9 @@ class SetMapConsistencyTest {
def boxMbs = new BoxMutableSet[Int, cm.BitSet](new cm.BitSet, "mutable.BitSet")
def boxMhs[A] = new BoxMutableSet[A, cm.HashSet[A]](new cm.HashSet[A], "mutable.HashSet")
-
+
+ def boxMts[A: Ordering] = new BoxMutableSet[A, cm.TreeSet[A]](new cm.TreeSet[A], "mutable.TreeSet")
+
def boxJavaS[A] = new BoxMutableSet[A, cm.Set[A]]((new java.util.HashSet[A]).asScala, "java.util.HashSet") {
override def adders = 3
override def subbers = 1
@@ -315,7 +319,7 @@ class SetMapConsistencyTest {
@Test
def churnIntMaps() {
val maps = Array[() => MapBox[Int]](
- () => boxMlm[Int], () => boxMhm[Int], () => boxMohm[Int], () => boxJavaM[Int],
+ () => boxMlm[Int], () => boxMhm[Int], () => boxMohm[Int], () => boxMtm[Int], () => boxJavaM[Int],
() => boxIim, () => boxIhm[Int], () => boxIlm[Int], () => boxItm[Int]
)
assert( maps.sliding(2).forall{ ms => churn(ms(0)(), ms(1)(), intKeys, 2000) } )
@@ -325,7 +329,7 @@ class SetMapConsistencyTest {
def churnLongMaps() {
val maps = Array[() => MapBox[Long]](
() => boxMjm, () => boxIjm, () => boxJavaM[Long],
- () => boxMlm[Long], () => boxMhm[Long], () => boxMohm[Long], () => boxIhm[Long], () => boxIlm[Long]
+ () => boxMlm[Long], () => boxMhm[Long], () => boxMtm[Long], () => boxMohm[Long], () => boxIhm[Long], () => boxIlm[Long]
)
assert( maps.sliding(2).forall{ ms => churn(ms(0)(), ms(1)(), longKeys, 10000) } )
}
@@ -352,7 +356,7 @@ class SetMapConsistencyTest {
def churnIntSets() {
val sets = Array[() => MapBox[Int]](
() => boxMhm[Int], () => boxIhm[Int], () => boxJavaS[Int],
- () => boxMbs, () => boxMhs[Int], () => boxIbs, () => boxIhs[Int], () => boxIls[Int], () => boxIts[Int]
+ () => boxMbs, () => boxMhs[Int], () => boxMts[Int], () => boxIbs, () => boxIhs[Int], () => boxIls[Int], () => boxIts[Int]
)
assert( sets.sliding(2).forall{ ms => churn(ms(0)(), ms(1)(), smallKeys, 1000, valuer = _ => 0) } )
}
@@ -529,4 +533,15 @@ class SetMapConsistencyTest {
assert(nit == 4)
assert(nfe == 4)
}
+
+ @Test
+ def test_SI8727() {
+ import scala.tools.testing.AssertUtil._
+ type NSEE = NoSuchElementException
+ val map = Map(0 -> "zero", 1 -> "one")
+ val m = map.filterKeys(i => if (map contains i) true else throw new NSEE)
+ assert{ (m contains 0) && (m get 0).nonEmpty }
+ assertThrows[NSEE]{ m contains 2 }
+ assertThrows[NSEE]{ m get 2 }
+ }
}
diff --git a/test/junit/scala/collection/convert/NullSafetyTest.scala b/test/junit/scala/collection/convert/NullSafetyTest.scala
new file mode 100644
index 0000000000..173568408c
--- /dev/null
+++ b/test/junit/scala/collection/convert/NullSafetyTest.scala
@@ -0,0 +1,279 @@
+package scala.collection.convert
+
+import java.{util => ju, lang => jl}
+import ju.{concurrent => juc}
+
+import org.junit.Test
+import org.junit.experimental.runners.Enclosed
+import org.junit.runner.RunWith
+
+import collection.convert.ImplicitConversions._
+import scala.collection.JavaConverters._
+import scala.collection.{mutable, concurrent}
+
+@RunWith(classOf[Enclosed])
+object NullSafetyTest {
+
+ /*
+ * Pertinent: SI-9113
+ * Tests to insure that wrappers return null instead of wrapping it as a collection
+ */
+
+ class ToScala {
+
+ @Test def testIteratorWrapping(): Unit = {
+ val nullJIterator: ju.Iterator[AnyRef] = null
+ val iterator: Iterator[AnyRef] = nullJIterator
+
+ assert(iterator == null)
+ }
+
+ @Test def testEnumerationWrapping(): Unit = {
+ val nullJEnumeration: ju.Enumeration[AnyRef] = null
+ val enumeration: Iterator[AnyRef] = nullJEnumeration
+
+ assert(enumeration == null)
+ }
+
+ @Test def testIterableWrapping(): Unit = {
+ val nullJIterable: jl.Iterable[AnyRef] = null
+ val iterable: Iterable[AnyRef] = nullJIterable
+
+ assert(iterable == null)
+ }
+
+ @Test def testCollectionWrapping(): Unit = {
+ val nullJCollection: ju.Collection[AnyRef] = null
+ val collection: Iterable[AnyRef] = nullJCollection
+
+ assert(collection == null)
+ }
+
+ @Test def testBufferWrapping(): Unit = {
+ val nullJList: ju.List[AnyRef] = null
+ val buffer: mutable.Buffer[AnyRef] = nullJList
+
+ assert(buffer == null)
+ }
+
+ @Test def testSetWrapping(): Unit = {
+ val nullJSet: ju.Set[AnyRef] = null
+ val set: mutable.Set[AnyRef] = nullJSet
+
+ assert(set == null)
+ }
+
+ @Test def testMapWrapping(): Unit = {
+ val nullJMap: ju.Map[AnyRef, AnyRef] = null
+ val map: mutable.Map[AnyRef, AnyRef] = nullJMap
+
+ assert(map == null)
+ }
+
+ @Test def testConcurrentMapWrapping(): Unit = {
+ val nullJConMap: juc.ConcurrentMap[AnyRef, AnyRef] = null
+ val conMap: concurrent.Map[AnyRef, AnyRef] = nullJConMap
+
+ assert(conMap == null)
+ }
+
+ @Test def testDictionaryWrapping(): Unit = {
+ val nullJDict: ju.Dictionary[AnyRef, AnyRef] = null
+ val dict: mutable.Map[AnyRef, AnyRef] = nullJDict
+
+ assert(dict == null)
+ }
+
+
+ @Test def testPropertyWrapping(): Unit = {
+ val nullJProps: ju.Properties = null
+ val props: mutable.Map[String, String] = nullJProps
+
+ assert(props == null)
+ }
+
+ @Test def testIteratorDecoration(): Unit = {
+ val nullJIterator: ju.Iterator[AnyRef] = null
+
+ assert(nullJIterator.asScala == null)
+ }
+
+ @Test def testEnumerationDecoration(): Unit = {
+ val nullJEnumeration: ju.Enumeration[AnyRef] = null
+
+ assert(nullJEnumeration.asScala == null)
+ }
+
+ @Test def testIterableDecoration(): Unit = {
+ val nullJIterable: jl.Iterable[AnyRef] = null
+
+ assert(nullJIterable.asScala == null)
+ }
+
+ @Test def testCollectionDecoration(): Unit = {
+ val nullJCollection: ju.Collection[AnyRef] = null
+
+ assert(nullJCollection.asScala == null)
+ }
+
+ @Test def testBufferDecoration(): Unit = {
+ val nullJBuffer: ju.List[AnyRef] = null
+
+ assert(nullJBuffer.asScala == null)
+ }
+
+ @Test def testSetDecoration(): Unit = {
+ val nullJSet: ju.Set[AnyRef] = null
+
+ assert(nullJSet.asScala == null)
+ }
+
+ @Test def testMapDecoration(): Unit = {
+ val nullJMap: ju.Map[AnyRef, AnyRef] = null
+
+ assert(nullJMap.asScala == null)
+ }
+
+ @Test def testConcurrentMapDecoration(): Unit = {
+ val nullJConMap: juc.ConcurrentMap[AnyRef, AnyRef] = null
+
+ assert(nullJConMap.asScala == null)
+ }
+
+ @Test def testDictionaryDecoration(): Unit = {
+ val nullJDict: ju.Dictionary[AnyRef, AnyRef] = null
+
+ assert(nullJDict.asScala == null)
+ }
+
+ @Test def testPropertiesDecoration(): Unit = {
+ val nullJProperties: ju.Properties = null
+
+ assert(nullJProperties.asScala == null)
+ }
+ }
+
+ class ToJava {
+
+ @Test def testIteratorWrapping(): Unit = {
+ val nullIterator: Iterator[AnyRef] = null
+ val jIterator: ju.Iterator[AnyRef] = nullIterator
+
+ assert(jIterator == null)
+ }
+
+ @Test def testEnumerationWrapping(): Unit = {
+ val nullEnumeration: Iterator[AnyRef] = null
+ val enumeration: ju.Iterator[AnyRef] = nullEnumeration
+
+ assert(enumeration == null)
+ }
+
+ @Test def testIterableWrapping(): Unit = {
+ val nullIterable: Iterable[AnyRef] = null
+ val iterable: jl.Iterable[AnyRef] = asJavaIterable(nullIterable)
+
+ assert(iterable == null)
+ }
+
+ @Test def testCollectionWrapping(): Unit = {
+ val nullCollection: Iterable[AnyRef] = null
+ val collection: ju.Collection[AnyRef] = nullCollection
+
+ assert(collection == null)
+ }
+
+ @Test def testBufferWrapping(): Unit = {
+ val nullList: mutable.Buffer[AnyRef] = null
+ val buffer: ju.List[AnyRef] = nullList
+
+ assert(buffer == null)
+ }
+
+ @Test def testSetWrapping(): Unit = {
+ val nullSet: mutable.Set[AnyRef] = null
+ val set: ju.Set[AnyRef] = nullSet
+
+ assert(set == null)
+ }
+
+ @Test def testMapWrapping(): Unit = {
+ val nullMap: mutable.Map[AnyRef, AnyRef] = null
+ val map: ju.Map[AnyRef, AnyRef] = nullMap
+
+ assert(map == null)
+ }
+
+ @Test def testConcurrentMapWrapping(): Unit = {
+ val nullConMap: concurrent.Map[AnyRef, AnyRef] = null
+ val conMap: juc.ConcurrentMap[AnyRef, AnyRef] = nullConMap
+
+ assert(conMap == null)
+ }
+
+ @Test def testDictionaryWrapping(): Unit = {
+ val nullDict: mutable.Map[AnyRef, AnyRef] = null
+ val dict: ju.Dictionary[AnyRef, AnyRef] = nullDict
+
+ assert(dict == null)
+ }
+
+ // Implicit conversion to ju.Properties is not available
+
+ @Test def testIteratorDecoration(): Unit = {
+ val nullIterator: Iterator[AnyRef] = null
+
+ assert(nullIterator.asJava == null)
+ }
+
+ @Test def testEnumerationDecoration(): Unit = {
+ val nullEnumeration: Iterator[AnyRef] = null
+
+ assert(nullEnumeration.asJavaEnumeration == null)
+ }
+
+ @Test def testIterableDecoration(): Unit = {
+ val nullIterable: Iterable[AnyRef] = null
+
+ assert(nullIterable.asJava == null)
+ }
+
+ @Test def testCollectionDecoration(): Unit = {
+ val nullCollection: Iterable[AnyRef] = null
+
+ assert(nullCollection.asJavaCollection == null)
+ }
+
+ @Test def testBufferDecoration(): Unit = {
+ val nullBuffer: mutable.Buffer[AnyRef] = null
+
+ assert(nullBuffer.asJava == null)
+ }
+
+ @Test def testSetDecoration(): Unit = {
+ val nullSet: Set[AnyRef] = null
+
+ assert(nullSet.asJava == null)
+ }
+
+ @Test def testMapDecoration(): Unit = {
+ val nullMap: mutable.Map[AnyRef, AnyRef] = null
+
+ assert(nullMap.asJava == null)
+ }
+
+ @Test def testConcurrentMapDecoration(): Unit = {
+ val nullConMap: concurrent.Map[AnyRef, AnyRef] = null
+
+ assert(nullConMap.asJava == null)
+ }
+
+ @Test def testDictionaryDecoration(): Unit = {
+ val nullDict: mutable.Map[AnyRef, AnyRef] = null
+
+ assert(nullDict.asJavaDictionary == null)
+ }
+
+ // Decorator conversion to ju.Properties is not available
+ }
+}
diff --git a/test/junit/scala/collection/convert/WrapperSerializationTest.scala b/test/junit/scala/collection/convert/WrapperSerializationTest.scala
new file mode 100644
index 0000000000..d398be806a
--- /dev/null
+++ b/test/junit/scala/collection/convert/WrapperSerializationTest.scala
@@ -0,0 +1,29 @@
+package scala.collection.convert
+
+import org.junit.Assert._
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(classOf[JUnit4])
+class WrapperSerializationTest {
+ def ser(a: AnyRef) = {
+ val baos = new java.io.ByteArrayOutputStream
+ (new java.io.ObjectOutputStream(baos)).writeObject(a)
+ baos
+ }
+ def des(baos: java.io.ByteArrayOutputStream): AnyRef = {
+ val bais = new java.io.ByteArrayInputStream(baos.toByteArray)
+ (new java.io.ObjectInputStream(bais)).readObject()
+ }
+ def serdes(a: AnyRef): Boolean = a == des(ser(a))
+
+ @Test
+ def test_SI8911() {
+ import scala.collection.JavaConverters._
+ assert( serdes(scala.collection.mutable.ArrayBuffer(1,2).asJava) )
+ assert( serdes(Seq(1,2).asJava) )
+ assert( serdes(Set(1,2).asJava) )
+ assert( serdes(Map(1 -> "one", 2 -> "two").asJava) )
+ }
+}
diff --git a/test/junit/scala/collection/immutable/SetTests.scala b/test/junit/scala/collection/immutable/SetTest.scala
index 28c7864359..4029c98009 100644
--- a/test/junit/scala/collection/immutable/SetTests.scala
+++ b/test/junit/scala/collection/immutable/SetTest.scala
@@ -6,7 +6,7 @@ import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@RunWith(classOf[JUnit4])
-class SetTests {
+class SetTest {
@Test
def test_SI8346_toSet_soundness(): Unit = {
val any2stringadd = "Disabled string conversions so as not to get confused!"
diff --git a/test/junit/scala/collection/immutable/StreamTest.scala b/test/junit/scala/collection/immutable/StreamTest.scala
new file mode 100644
index 0000000000..1b257aabc4
--- /dev/null
+++ b/test/junit/scala/collection/immutable/StreamTest.scala
@@ -0,0 +1,126 @@
+package scala.collection.immutable
+
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.Test
+import org.junit.Assert._
+
+import scala.ref.WeakReference
+import scala.util.Try
+
+@RunWith(classOf[JUnit4])
+class StreamTest {
+
+ @Test
+ def t6727_and_t6440_and_8627(): Unit = {
+ assertTrue(Stream.continually(()).filter(_ => true).take(2) == Seq((), ()))
+ assertTrue(Stream.continually(()).filterNot(_ => false).take(2) == Seq((), ()))
+ assertTrue(Stream(1,2,3,4,5).filter(_ < 4) == Seq(1,2,3))
+ assertTrue(Stream(1,2,3,4,5).filterNot(_ > 4) == Seq(1,2,3,4))
+ assertTrue(Stream.from(1).filter(_ > 4).take(3) == Seq(5,6,7))
+ assertTrue(Stream.from(1).filterNot(_ <= 4).take(3) == Seq(5,6,7))
+ }
+
+ /** Test helper to verify that the given Stream operation allows
+ * GC of the head during processing of the tail.
+ */
+ def assertStreamOpAllowsGC(op: (=> Stream[Int], Int => Unit) => Any, f: Int => Unit): Unit = {
+ val msgSuccessGC = "GC success"
+ val msgFailureGC = "GC failure"
+
+ // A stream of 500 elements at most. We will test that the head can be collected
+ // while processing the tail. After each element we will GC and wait 10 ms, so a
+ // failure to collect will take roughly 5 seconds.
+ val ref = WeakReference( Stream.from(1).take(500) )
+
+ def gcAndThrowIfCollected(n: Int): Unit = {
+ System.gc() // try to GC
+ Thread.sleep(10) // give it 10 ms
+ if (ref.get.isEmpty) throw new RuntimeException(msgSuccessGC) // we're done if head collected
+ f(n)
+ }
+
+ val res = Try { op(ref(), gcAndThrowIfCollected) }.failed // success is indicated by an
+ val msg = res.map(_.getMessage).getOrElse(msgFailureGC) // exception with expected message
+ // failure is indicated by no
+ assertTrue(msg == msgSuccessGC) // exception, or one with different message
+ }
+
+ @Test
+ def foreach_allows_GC() {
+ assertStreamOpAllowsGC(_.foreach(_), _ => ())
+ }
+
+ @Test
+ def filter_all_foreach_allows_GC() {
+ assertStreamOpAllowsGC(_.filter(_ => true).foreach(_), _ => ())
+ }
+
+ @Test // SI-8990
+ def withFilter_after_first_foreach_allows_GC: Unit = {
+ assertStreamOpAllowsGC(_.withFilter(_ > 1).foreach(_), _ => ())
+ }
+
+ @Test // SI-8990
+ def withFilter_after_first_withFilter_foreach_allows_GC: Unit = {
+ assertStreamOpAllowsGC(_.withFilter(_ > 1).withFilter(_ < 100).foreach(_), _ => ())
+ }
+
+ @Test // SI-8990
+ def withFilter_can_retry_after_exception_thrown_in_filter: Unit = {
+ // use mutable state to control an intermittent failure in filtering the Stream
+ var shouldThrow = true
+
+ val wf = Stream.from(1).take(10).withFilter { n =>
+ if (shouldThrow && n == 5) throw new RuntimeException("n == 5") else n > 5
+ }
+
+ assertTrue( Try { wf.map(identity) }.isFailure ) // throws on n == 5
+
+ shouldThrow = false // won't throw next time
+
+ assertTrue( wf.map(identity).length == 5 ) // success instead of NPE
+ }
+
+ /** Test helper to verify that the given Stream operation is properly lazy in the tail */
+ def assertStreamOpLazyInTail(op: (=> Stream[Int]) => Stream[Int], expectedEvaluated: List[Int]): Unit = {
+ // mutable state to record every strict evaluation
+ var evaluated: List[Int] = Nil
+
+ def trackEffectsOnNaturals: Stream[Int] = {
+ def loop(i: Int): Stream[Int] = { evaluated ++= List(i); i #:: loop(i + 1) }
+ loop(1)
+ }
+
+ // call op on a stream which records every strict evaluation
+ val result = op(trackEffectsOnNaturals)
+
+ assertTrue( evaluated == expectedEvaluated )
+ }
+
+ @Test // SI-9134
+ def filter_map_properly_lazy_in_tail: Unit = {
+ assertStreamOpLazyInTail(_.filter(_ % 2 == 0).map(identity), List(1, 2))
+ }
+
+ @Test // SI-9134
+ def withFilter_map_properly_lazy_in_tail: Unit = {
+ assertStreamOpLazyInTail(_.withFilter(_ % 2 == 0).map(identity), List(1, 2))
+ }
+
+ @Test
+ def test_si9379() {
+ class Boom {
+ private var i = -1
+ def inc = {
+ i += 1
+ if (i > 1000) throw new NoSuchElementException("Boom! Too many elements!")
+ i
+ }
+ }
+ val b = new Boom
+ val s = Stream.continually(b.inc)
+ // zipped.toString must allow s to short-circuit evaluation
+ assertTrue((s, s).zipped.toString contains s.toString)
+ }
+}
diff --git a/test/junit/scala/collection/immutable/StringLikeTest.scala b/test/junit/scala/collection/immutable/StringLikeTest.scala
index 3722bdfe4d..50be638b89 100644
--- a/test/junit/scala/collection/immutable/StringLikeTest.scala
+++ b/test/junit/scala/collection/immutable/StringLikeTest.scala
@@ -28,10 +28,16 @@ class StringLikeTest {
@Test
def testSplitEdgeCases: Unit = {
+ val high = 0xD852.toChar
+ val low = 0xDF62.toChar
+ val surrogatepair = List(high, low).mkString
+ val twopairs = surrogatepair + "_" + surrogatepair
+
AssertUtil.assertSameElements("abcd".split('d'), Array("abc")) // not Array("abc", "")
AssertUtil.assertSameElements("abccc".split('c'), Array("ab")) // not Array("ab", "", "", "")
AssertUtil.assertSameElements("xxx".split('x'), Array[String]()) // not Array("", "", "", "")
AssertUtil.assertSameElements("".split('x'), Array("")) // not Array()
AssertUtil.assertSameElements("--ch--omp--".split("-"), Array("", "", "ch", "", "omp")) // All the cases!
+ AssertUtil.assertSameElements(twopairs.split(high), Array(twopairs)) //don't split on characters that are half a surrogate pair
}
}
diff --git a/test/junit/scala/collection/mutable/BitSetTest.scala b/test/junit/scala/collection/mutable/BitSetTest.scala
index d56cc45601..e832194989 100644
--- a/test/junit/scala/collection/mutable/BitSetTest.scala
+++ b/test/junit/scala/collection/mutable/BitSetTest.scala
@@ -28,4 +28,11 @@ class BitSetTest {
littleBitSet &= bigBitSet
assert(littleBitSet.toBitMask.length < bigBitSet.toBitMask.length, "Needlessly extended the size of bitset on &=")
}
+
+ @Test def test_SI8647() {
+ val bs = BitSet()
+ bs.map(_ + 1) // Just needs to compile
+ val xs = bs: SortedSet[Int]
+ xs.map(_ + 1) // Also should compile (did before)
+ }
}
diff --git a/test/junit/scala/collection/mutable/OpenHashMapTest.scala b/test/junit/scala/collection/mutable/OpenHashMapTest.scala
index 1459c14d78..9b5c20e01a 100644
--- a/test/junit/scala/collection/mutable/OpenHashMapTest.scala
+++ b/test/junit/scala/collection/mutable/OpenHashMapTest.scala
@@ -28,7 +28,7 @@ class OpenHashMapTest {
val fieldMirror = mirror.reflect(m).reflectField(termSym)
*/
// Use Java reflection instead for now.
- val field = m.getClass.getDeclaredField("scala$collection$mutable$OpenHashMap$$deleted")
+ val field = m.getClass.getDeclaredField("deleted")
field.setAccessible(true)
m.put(0, 0)
diff --git a/test/junit/scala/collection/mutable/PriorityQueueTest.scala b/test/junit/scala/collection/mutable/PriorityQueueTest.scala
index a14f1bf4c8..faedcf11f0 100644
--- a/test/junit/scala/collection/mutable/PriorityQueueTest.scala
+++ b/test/junit/scala/collection/mutable/PriorityQueueTest.scala
@@ -14,6 +14,12 @@ class PriorityQueueTest {
priorityQueue.enqueue(elements :_*)
@Test
+ def orderingReverseReverse() {
+ val pq = new mutable.PriorityQueue[Nothing]()((_,_)=>42)
+ assert(pq.ord eq pq.reverse.reverse.ord)
+ }
+
+ @Test
def canSerialize() {
val outputStream = new ByteArrayOutputStream()
new ObjectOutputStream(outputStream).writeObject(priorityQueue)
@@ -27,6 +33,7 @@ class PriorityQueueTest {
val objectInputStream = new ObjectInputStream(new ByteArrayInputStream(bytes))
val deserializedPriorityQueue = objectInputStream.readObject().asInstanceOf[PriorityQueue[Int]]
+ //correct sequencing is also tested here:
assert(deserializedPriorityQueue.dequeueAll == elements.sorted.reverse)
}
}
diff --git a/test/junit/scala/io/SourceTest.scala b/test/junit/scala/io/SourceTest.scala
index 3138a4589c..3fe48940a0 100644
--- a/test/junit/scala/io/SourceTest.scala
+++ b/test/junit/scala/io/SourceTest.scala
@@ -28,6 +28,10 @@ class SourceTest {
@Test def canIterateLines() = {
assertEquals(sampler.lines.size, (Source fromString sampler).getLines.size)
}
+ @Test def loadFromResource() = {
+ val res = Source.fromResource("rootdoc.txt")
+ assertTrue("No classpath resource found", res.getLines().size > 5)
+ }
@Test def canCustomizeReporting() = {
class CapitalReporting(is: InputStream) extends BufferedSource(is) {
override def report(pos: Int, msg: String, out: PrintStream): Unit = {
diff --git a/test/junit/scala/issues/BytecodeTest.scala b/test/junit/scala/issues/BytecodeTest.scala
new file mode 100644
index 0000000000..cf5c7f9ec3
--- /dev/null
+++ b/test/junit/scala/issues/BytecodeTest.scala
@@ -0,0 +1,478 @@
+package scala.issues
+
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.Test
+
+import scala.tools.asm.Opcodes._
+import scala.tools.nsc.backend.jvm.AsmUtils
+import scala.tools.nsc.backend.jvm.CodeGenTools._
+import org.junit.Assert._
+
+import scala.collection.JavaConverters._
+import scala.tools.asm.Opcodes
+import scala.tools.asm.tree.ClassNode
+import scala.tools.partest.ASMConverters._
+import scala.tools.testing.ClearAfterClass
+
+object BytecodeTest extends ClearAfterClass.Clearable {
+ var compiler = newCompiler()
+ def clear(): Unit = { compiler = null }
+}
+
+@RunWith(classOf[JUnit4])
+class BytecodeTest extends ClearAfterClass {
+ ClearAfterClass.stateToClear = BytecodeTest
+ val compiler = BytecodeTest.compiler
+
+ @Test
+ def t8731(): Unit = {
+ val code =
+ """class C {
+ | def f(x: Int) = (x: @annotation.switch) match {
+ | case 1 => 0
+ | case 2 => 1
+ | case 3 => 2
+ | }
+ | final val K = 10
+ | def g(x: Int) = (x: @annotation.switch) match {
+ | case K => 0
+ | case 1 => 10
+ | case 2 => 20
+ | }
+ |}
+ """.stripMargin
+
+ val List(c) = compileClasses(compiler)(code)
+
+ assertTrue(getSingleMethod(c, "f").instructions.count(_.isInstanceOf[TableSwitch]) == 1)
+ assertTrue(getSingleMethod(c, "g").instructions.count(_.isInstanceOf[LookupSwitch]) == 1)
+ }
+
+ @Test
+ def t8926(): Unit = {
+ import scala.reflect.internal.util.BatchSourceFile
+
+ // this test cannot be implemented using partest because of its mixed-mode compilation strategy:
+ // partest first compiles all files with scalac, then the java files, and then again the scala
+ // using the output classpath. this shadows the bug SI-8926.
+
+ val annotA =
+ """import java.lang.annotation.Retention;
+ |import java.lang.annotation.RetentionPolicy;
+ |@Retention(RetentionPolicy.RUNTIME)
+ |public @interface AnnotA { }
+ """.stripMargin
+ val annotB = "public @interface AnnotB { }"
+
+ val scalaSrc =
+ """@AnnotA class A
+ |@AnnotB class B
+ """.stripMargin
+
+ val run = new compiler.Run()
+ run.compileSources(List(new BatchSourceFile("AnnotA.java", annotA), new BatchSourceFile("AnnotB.java", annotB), new BatchSourceFile("Test.scala", scalaSrc)))
+ val outDir = compiler.settings.outputDirs.getSingleOutput.get
+ val outfiles = (for (f <- outDir.iterator if !f.isDirectory) yield (f.name, f.toByteArray)).toList
+
+ def check(classfile: String, annotName: String) = {
+ val f = (outfiles collect { case (`classfile`, bytes) => AsmUtils.readClass(bytes) }).head
+ val descs = f.visibleAnnotations.asScala.map(_.desc).toList
+ assertTrue(descs.toString, descs exists (_ contains annotName))
+ }
+
+ check("A.class", "AnnotA")
+
+ // known issue SI-8928: the visibility of AnnotB should be CLASS, but annotation classes without
+ // a @Retention annotation are currently emitted as RUNTIME.
+ check("B.class", "AnnotB")
+ }
+
+ @Test
+ def t6288bJumpPosition(): Unit = {
+ val code =
+ """object Case3 { // 01
+ | def unapply(z: Any): Option[Int] = Some(-1) // 02
+ | def main(args: Array[String]) { // 03
+ | ("": Any) match { // 04
+ | case x : String => // 05
+ | println("case 0") // 06 println and jump at 6
+ | case _ => // 07
+ | println("default") // 08 println and jump at 8
+ | } // 09
+ | println("done") // 10
+ | }
+ |}
+ """.stripMargin
+ val List(mirror, module) = compileClasses(compiler)(code)
+
+ val unapplyLineNumbers = getSingleMethod(module, "unapply").instructions.filter(_.isInstanceOf[LineNumber])
+ assert(unapplyLineNumbers == List(LineNumber(2, Label(0))), unapplyLineNumbers)
+
+ val expected = List(
+ LineNumber(4, Label(0)),
+ LineNumber(5, Label(5)),
+ Jump(IFEQ, Label(20)),
+
+ LineNumber(6, Label(11)),
+ Invoke(INVOKEVIRTUAL, "scala/Predef$", "println", "(Ljava/lang/Object;)V", false),
+ Jump(GOTO, Label(33)),
+
+ LineNumber(5, Label(20)),
+ Jump(GOTO, Label(24)),
+
+ LineNumber(8, Label(24)),
+ Invoke(INVOKEVIRTUAL, "scala/Predef$", "println", "(Ljava/lang/Object;)V", false),
+ Jump(GOTO, Label(33)),
+
+ LineNumber(10, Label(33)),
+ Invoke(INVOKEVIRTUAL, "scala/Predef$", "println", "(Ljava/lang/Object;)V", false)
+ )
+
+ val mainIns = getSingleMethod(module, "main").instructions filter {
+ case _: LineNumber | _: Invoke | _: Jump => true
+ case _ => false
+ }
+ assertSameCode(mainIns, expected)
+ }
+
+ @Test
+ def bytecodeForBranches(): Unit = {
+ val code =
+ """class C {
+ | def t1(b: Boolean) = if (b) 1 else 2
+ | def t2(x: Int) = if (x == 393) 1 else 2
+ | def t3(a: Array[String], b: AnyRef) = a != b && b == a
+ | def t4(a: AnyRef) = a == null || null != a
+ | def t5(a: AnyRef) = (a eq null) || (null ne a)
+ | def t6(a: Int, b: Boolean) = if ((a == 10) && b || a != 1) 1 else 2
+ | def t7(a: AnyRef, b: AnyRef) = a == b
+ | def t8(a: AnyRef) = Nil == a || "" != a
+ |}
+ """.stripMargin
+
+ val List(c) = compileClasses(compiler)(code)
+
+ // t1: no unnecessary GOTOs
+ assertSameCode(getSingleMethod(c, "t1"), List(
+ VarOp(ILOAD, 1), Jump(IFEQ, Label(6)),
+ Op(ICONST_1), Jump(GOTO, Label(9)),
+ Label(6), Op(ICONST_2),
+ Label(9), Op(IRETURN)))
+
+ // t2: no unnecessary GOTOs
+ assertSameCode(getSingleMethod(c, "t2"), List(
+ VarOp(ILOAD, 1), IntOp(SIPUSH, 393), Jump(IF_ICMPNE, Label(7)),
+ Op(ICONST_1), Jump(GOTO, Label(10)),
+ Label(7), Op(ICONST_2),
+ Label(10), Op(IRETURN)))
+
+ // t3: Array == is translated to reference equality, AnyRef == to null checks and equals
+ assertSameCode(getSingleMethod(c, "t3"), List(
+ // Array ==
+ VarOp(ALOAD, 1), VarOp(ALOAD, 2), Jump(IF_ACMPEQ, Label(23)),
+ // AnyRef ==
+ VarOp(ALOAD, 2), VarOp(ALOAD, 1), VarOp(ASTORE, 3), Op(DUP), Jump(IFNONNULL, Label(14)),
+ Op(POP), VarOp(ALOAD, 3), Jump(IFNULL, Label(19)), Jump(GOTO, Label(23)),
+ Label(14), VarOp(ALOAD, 3), Invoke(INVOKEVIRTUAL, "java/lang/Object", "equals", "(Ljava/lang/Object;)Z", false), Jump(IFEQ, Label(23)),
+ Label(19), Op(ICONST_1), Jump(GOTO, Label(26)),
+ Label(23), Op(ICONST_0),
+ Label(26), Op(IRETURN)))
+
+ val t4t5 = List(
+ VarOp(ALOAD, 1), Jump(IFNULL, Label(6)),
+ VarOp(ALOAD, 1), Jump(IFNULL, Label(10)),
+ Label(6), Op(ICONST_1), Jump(GOTO, Label(13)),
+ Label(10), Op(ICONST_0),
+ Label(13), Op(IRETURN))
+
+ // t4: one side is known null, so just a null check on the other
+ assertSameCode(getSingleMethod(c, "t4"), t4t5)
+
+ // t5: one side known null, so just a null check on the other
+ assertSameCode(getSingleMethod(c, "t5"), t4t5)
+
+ // t6: no unnecessary GOTOs
+ assertSameCode(getSingleMethod(c, "t6"), List(
+ VarOp(ILOAD, 1), IntOp(BIPUSH, 10), Jump(IF_ICMPNE, Label(7)),
+ VarOp(ILOAD, 2), Jump(IFNE, Label(12)),
+ Label(7), VarOp(ILOAD, 1), Op(ICONST_1), Jump(IF_ICMPEQ, Label(16)),
+ Label(12), Op(ICONST_1), Jump(GOTO, Label(19)),
+ Label(16), Op(ICONST_2),
+ Label(19), Op(IRETURN)))
+
+ // t7: universal equality
+ assertInvoke(getSingleMethod(c, "t7"), "scala/runtime/BoxesRunTime", "equals")
+
+ // t8: no null checks invoking equals on modules and constants
+ assertSameCode(getSingleMethod(c, "t8"), List(
+ Field(GETSTATIC, "scala/collection/immutable/Nil$", "MODULE$", "Lscala/collection/immutable/Nil$;"), VarOp(ALOAD, 1), Invoke(INVOKEVIRTUAL, "java/lang/Object", "equals", "(Ljava/lang/Object;)Z", false), Jump(IFNE, Label(10)),
+ Ldc(LDC, ""), VarOp(ALOAD, 1), Invoke(INVOKEVIRTUAL, "java/lang/Object", "equals", "(Ljava/lang/Object;)Z", false), Jump(IFNE, Label(14)),
+ Label(10), Op(ICONST_1), Jump(GOTO, Label(17)),
+ Label(14), Op(ICONST_0),
+ Label(17), Op(IRETURN)))
+ }
+
+ object forwarderTestUtils {
+ def findMethods(cls: ClassNode, name: String): List[Method] = cls.methods.iterator.asScala.find(_.name == name).map(convertMethod).toList
+
+ import language.implicitConversions
+ implicit def s2c(s: Symbol)(implicit classes: Map[String, ClassNode]): ClassNode = classes(s.name)
+
+ def checkForwarder(c: ClassNode, target: String) = {
+ val List(f) = findMethods(c, "f")
+ assertSameCode(f, List(VarOp(ALOAD, 0), Invoke(INVOKESPECIAL, target, "f", "()I", false), Op(IRETURN)))
+ }
+ }
+
+ @Test
+ def traitMethodForwarders(): Unit = {
+ import forwarderTestUtils._
+ val code =
+ """trait T1 { def f = 1 }
+ |trait T2 extends T1 { override def f = 2 }
+ |trait T3 { self: T1 => override def f = 3 }
+ |
+ |abstract class A1 { def f: Int }
+ |class A2 { def f: Int = 4 }
+ |
+ |trait T4 extends A1 { def f = 5 }
+ |trait T5 extends A2 { override def f = 6 }
+ |
+ |trait T6 { def f: Int }
+ |trait T7 extends T6 { abstract override def f = super.f + 1 }
+ |
+ |trait T8 { override def clone() = super.clone() }
+ |
+ |class A3 extends T1 { override def f = 7 }
+ |
+ |class C1 extends T1
+ |class C2 extends T2
+ |class C3 extends T1 with T2
+ |class C4 extends T2 with T1
+ |class C5 extends T1 with T3
+ |
+ |// traits extending a class that defines f
+ |class C6 extends T4
+ |class C7 extends T5
+ |class C8 extends A1 with T4
+ |class C9 extends A2 with T5
+ |
+ |// T6: abstract f in trait
+ |class C10 extends T6 with T1
+ |class C11 extends T6 with T2
+ |abstract class C12 extends A1 with T6
+ |class C13 extends A2 with T6
+ |class C14 extends T4 with T6
+ |class C15 extends T5 with T6
+ |
+ |// superclass overrides a trait method
+ |class C16 extends A3
+ |class C17 extends A3 with T1
+ |
+ |// abstract override
+ |class C18 extends T6 { def f = 22 }
+ |class C19 extends C18 with T7
+ |
+ |class C20 extends T8
+ """.stripMargin
+
+ implicit val classes = compileClasses(compiler)(code).map(c => (c.name, c)).toMap
+
+ val noForwarder = List('C1, 'C2, 'C3, 'C4, 'C10, 'C11, 'C12, 'C13, 'C16, 'C17)
+ for (c <- noForwarder) assertEquals(findMethods(c, "f"), Nil)
+
+ checkForwarder('C5, "T3")
+ checkForwarder('C6, "T4")
+ checkForwarder('C7, "T5")
+ checkForwarder('C8, "T4")
+ checkForwarder('C9, "T5")
+ checkForwarder('C14, "T4")
+ checkForwarder('C15, "T5")
+ assertSameSummary(getSingleMethod('C18, "f"), List(BIPUSH, IRETURN))
+ checkForwarder('C19, "T7")
+ assertSameCode(getSingleMethod('C19, "T7$$super$f"), List(VarOp(ALOAD, 0), Invoke(INVOKESPECIAL, "C18", "f", "()I", false), Op(IRETURN)))
+ assertInvoke(getSingleMethod('C20, "clone"), "T8", "clone") // mixin forwarder
+ }
+
+ @Test
+ def noTraitMethodForwardersForOverloads(): Unit = {
+ import forwarderTestUtils._
+ val code =
+ """trait T1 { def f(x: Int) = 0 }
+ |trait T2 { def f(x: String) = 1 }
+ |class C extends T1 with T2
+ """.stripMargin
+ val List(c, t1, t2) = compileClasses(compiler)(code)
+ assertEquals(findMethods(c, "f"), Nil)
+ }
+
+ @Test
+ def traitMethodForwardersForJavaDefaultMethods(): Unit = {
+ import forwarderTestUtils._
+ val j1 = ("interface J1 { int f(); }", "J1.java")
+ val j2 = ("interface J2 { default int f() { return 1; } }", "J2.java")
+ val j3 = ("interface J3 extends J1 { default int f() { return 2; } }", "J3.java")
+ val j4 = ("interface J4 extends J2 { default int f() { return 3; } }", "J4.java")
+ val code =
+ """trait T1 extends J2 { override def f = 4 }
+ |trait T2 { self: J2 => override def f = 5 }
+ |
+ |class K1 extends J2
+ |class K2 extends J1 with J2
+ |class K3 extends J2 with J1
+ |
+ |class K4 extends J3
+ |class K5 extends J3 with J1
+ |class K6 extends J1 with J3
+ |
+ |class K7 extends J4
+ |class K8 extends J4 with J2
+ |class K9 extends J2 with J4
+ |
+ |class K10 extends T1 with J2
+ |class K11 extends J2 with T1
+ |
+ |class K12 extends J2 with T2
+ """.stripMargin
+ implicit val classes = compileClasses(compiler)(code, List(j1, j2, j3, j4)).map(c => (c.name, c)).toMap
+
+ val noForwarder = List('K1, 'K2, 'K3, 'K4, 'K5, 'K6, 'K7, 'K8, 'K9, 'K10, 'K11)
+ for (c <- noForwarder) assertEquals(findMethods(c, "f"), Nil)
+
+ checkForwarder('K12, "T2")
+ }
+
+ @Test
+ def invocationReceivers(): Unit = {
+ val List(c1, c2, t, u) = compileClasses(compiler)(invocationReceiversTestCode.definitions("Object"))
+ // mixin forwarder in C1
+ assertSameCode(getSingleMethod(c1, "clone"), List(VarOp(ALOAD, 0), Invoke(INVOKESPECIAL, "T", "clone", "()Ljava/lang/Object;", false), Op(ARETURN)))
+ assertInvoke(getSingleMethod(c1, "f1"), "T", "clone")
+ assertInvoke(getSingleMethod(c1, "f2"), "T", "clone")
+ assertInvoke(getSingleMethod(c1, "f3"), "C1", "clone")
+ assertInvoke(getSingleMethod(c2, "f1"), "T", "clone")
+ assertInvoke(getSingleMethod(c2, "f2"), "T", "clone")
+ assertInvoke(getSingleMethod(c2, "f3"), "C1", "clone")
+
+ val List(c1b, c2b, tb, ub) = compileClasses(compiler)(invocationReceiversTestCode.definitions("String"))
+ def ms(c: ClassNode, n: String) = c.methods.asScala.toList.filter(_.name == n)
+ assert(ms(tb, "clone").length == 1)
+ assert(ms(ub, "clone").isEmpty)
+ val List(c1Clone) = ms(c1b, "clone")
+ assertEquals(c1Clone.desc, "()Ljava/lang/Object;")
+ assert((c1Clone.access | Opcodes.ACC_BRIDGE) != 0)
+ assertSameCode(convertMethod(c1Clone), List(VarOp(ALOAD, 0), Invoke(INVOKEVIRTUAL, "C1", "clone", "()Ljava/lang/String;", false), Op(ARETURN)))
+
+ def iv(m: Method) = getSingleMethod(c1b, "f1").instructions.collect({case i: Invoke => i})
+ assertSameCode(iv(getSingleMethod(c1b, "f1")), List(Invoke(INVOKEINTERFACE, "T", "clone", "()Ljava/lang/String;", true)))
+ assertSameCode(iv(getSingleMethod(c1b, "f2")), List(Invoke(INVOKEINTERFACE, "T", "clone", "()Ljava/lang/String;", true)))
+ // invokeinterface T.clone in C1 is OK here because it is not an override of Object.clone (different siganture)
+ assertSameCode(iv(getSingleMethod(c1b, "f3")), List(Invoke(INVOKEINTERFACE, "T", "clone", "()Ljava/lang/String;", true)))
+ }
+
+ @Test
+ def invocationReceiversProtected(): Unit = {
+ // http://lrytz.github.io/scala-aladdin-bugtracker/displayItem.do%3Fid=455.html / 9954eaf
+ // also https://issues.scala-lang.org/browse/SI-1430 / 0bea2ab (same but with interfaces)
+ val aC =
+ """package a;
+ |/*package private*/ abstract class A {
+ | public int f() { return 1; }
+ | public int t;
+ |}
+ """.stripMargin
+ val bC =
+ """package a;
+ |public class B extends A { }
+ """.stripMargin
+ val iC =
+ """package a;
+ |/*package private*/ interface I { int f(); }
+ """.stripMargin
+ val jC =
+ """package a;
+ |public interface J extends I { }
+ """.stripMargin
+ val cC =
+ """package b
+ |class C {
+ | def f1(b: a.B) = b.f
+ | def f2(b: a.B) = { b.t = b.t + 1 }
+ | def f3(j: a.J) = j.f
+ |}
+ """.stripMargin
+ val List(c) = compileClasses(compiler)(cC, javaCode = List((aC, "A.java"), (bC, "B.java"), (iC, "I.java"), (jC, "J.java")))
+ assertInvoke(getSingleMethod(c, "f1"), "a/B", "f") // receiver needs to be B (A is not accessible in class C, package b)
+ println(getSingleMethod(c, "f2").instructions.stringLines)
+ assertInvoke(getSingleMethod(c, "f3"), "a/J", "f") // receiver needs to be J
+ }
+
+ @Test
+ def specialInvocationReceivers(): Unit = {
+ val code =
+ """class C {
+ | def f1(a: Array[String]) = a.clone()
+ | def f2(a: Array[Int]) = a.hashCode()
+ | def f3(n: Nothing) = n.hashCode()
+ | def f4(n: Null) = n.toString()
+ |
+ |}
+ """.stripMargin
+ val List(c) = compileClasses(compiler)(code)
+ assertInvoke(getSingleMethod(c, "f1"), "[Ljava/lang/String;", "clone") // array descriptor as receiver
+ assertInvoke(getSingleMethod(c, "f2"), "java/lang/Object", "hashCode") // object receiver
+ assertInvoke(getSingleMethod(c, "f3"), "java/lang/Object", "hashCode")
+ assertInvoke(getSingleMethod(c, "f4"), "java/lang/Object", "toString")
+ }
+}
+
+object invocationReceiversTestCode {
+ // if cloneType is more specific than Object (e.g., String), a bridge method is generated.
+ def definitions(cloneType: String) =
+ s"""trait T { override def clone(): $cloneType = "hi" }
+ |trait U extends T
+ |class C1 extends U with Cloneable {
+ | // The comments below are true when $cloneType is Object.
+ | // C1 gets a forwarder for clone that invokes T.clone. this is needed because JVM method
+ | // resolution always prefers class members, so it would resolve to Object.clone, even if
+ | // C1 is a subtype of the interface T which has an overriding default method for clone.
+ |
+ | // invokeinterface T.clone
+ | def f1 = (this: T).clone()
+ |
+ | // cannot invokeinterface U.clone (NoSuchMethodError). Object.clone would work here, but
+ | // not in the example in C2 (illegal access to protected). T.clone works in all cases and
+ | // resolves correctly.
+ | def f2 = (this: U).clone()
+ |
+ | // invokevirtual C1.clone()
+ | def f3 = (this: C1).clone()
+ |}
+ |
+ |class C2 {
+ | def f1(t: T) = t.clone() // invokeinterface T.clone
+ | def f2(t: U) = t.clone() // invokeinterface T.clone -- Object.clone would be illegal (protected, explained in C1)
+ | def f3(t: C1) = t.clone() // invokevirtual C1.clone -- Object.clone would be illegal
+ |}
+ """.stripMargin
+
+ val runCode =
+ """
+ |val r = new StringBuffer()
+ |val c1 = new C1
+ |r.append(c1.f1)
+ |r.append(c1.f2)
+ |r.append(c1.f3)
+ |val t = new T { }
+ |val u = new U { }
+ |val c2 = new C2
+ |r.append(c2.f1(t))
+ |r.append(c2.f1(u))
+ |r.append(c2.f1(c1))
+ |r.append(c2.f2(u))
+ |r.append(c2.f2(c1))
+ |r.append(c2.f3(c1))
+ |r.toString
+ """.stripMargin
+}
diff --git a/test/junit/scala/issues/BytecodeTests.scala b/test/junit/scala/issues/BytecodeTests.scala
deleted file mode 100644
index d4ed063a03..0000000000
--- a/test/junit/scala/issues/BytecodeTests.scala
+++ /dev/null
@@ -1,80 +0,0 @@
-package scala.issues
-
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.junit.Test
-import scala.tools.asm.Opcodes
-import scala.tools.nsc.backend.jvm.AsmUtils
-import scala.tools.nsc.backend.jvm.CodeGenTools._
-import org.junit.Assert._
-import scala.collection.JavaConverters._
-import scala.tools.partest.ASMConverters._
-
-@RunWith(classOf[JUnit4])
-class BytecodeTests {
- val compiler = newCompiler()
-
- @Test
- def t8731(): Unit = {
- val code =
- """class C {
- | def f(x: Int) = (x: @annotation.switch) match {
- | case 1 => 0
- | case 2 => 1
- | case 3 => 2
- | }
- | final val K = 10
- | def g(x: Int) = (x: @annotation.switch) match {
- | case K => 0
- | case 1 => 10
- | case 2 => 20
- | }
- |}
- """.stripMargin
-
- val List(c) = compileClasses(compiler)(code)
-
- assertTrue(getSingleMethod(c, "f").instructions.count(_.isInstanceOf[TableSwitch]) == 1)
- assertTrue(getSingleMethod(c, "g").instructions.count(_.isInstanceOf[LookupSwitch]) == 1)
- }
-
- @Test
- def t8926(): Unit = {
- import scala.reflect.internal.util.BatchSourceFile
-
- // this test cannot be implemented using partest because of its mixed-mode compilation strategy:
- // partest first compiles all files with scalac, then the java files, and then again the scala
- // using the output classpath. this shadows the bug SI-8926.
-
- val annotA =
- """import java.lang.annotation.Retention;
- |import java.lang.annotation.RetentionPolicy;
- |@Retention(RetentionPolicy.RUNTIME)
- |public @interface AnnotA { }
- """.stripMargin
- val annotB = "public @interface AnnotB { }"
-
- val scalaSrc =
- """@AnnotA class A
- |@AnnotB class B
- """.stripMargin
-
- val compiler = newCompiler()
- val run = new compiler.Run()
- run.compileSources(List(new BatchSourceFile("AnnotA.java", annotA), new BatchSourceFile("AnnotB.java", annotB), new BatchSourceFile("Test.scala", scalaSrc)))
- val outDir = compiler.settings.outputDirs.getSingleOutput.get
- val outfiles = (for (f <- outDir.iterator if !f.isDirectory) yield (f.name, f.toByteArray)).toList
-
- def check(classfile: String, annotName: String) = {
- val f = (outfiles collect { case (`classfile`, bytes) => AsmUtils.readClass(bytes) }).head
- val descs = f.visibleAnnotations.asScala.map(_.desc).toList
- assertTrue(descs.toString, descs exists (_ contains annotName))
- }
-
- check("A.class", "AnnotA")
-
- // known issue SI-8928: the visibility of AnnotB should be CLASS, but annotation classes without
- // a @Retention annotation are currently emitted as RUNTIME.
- check("B.class", "AnnotB")
- }
-}
diff --git a/test/junit/scala/issues/OptimizedBytecodeTest.scala b/test/junit/scala/issues/OptimizedBytecodeTest.scala
new file mode 100644
index 0000000000..1555e8945a
--- /dev/null
+++ b/test/junit/scala/issues/OptimizedBytecodeTest.scala
@@ -0,0 +1,375 @@
+package scala.issues
+
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.Test
+import scala.tools.asm.Opcodes._
+import org.junit.Assert._
+
+import scala.tools.nsc.backend.jvm.{AsmUtils, CodeGenTools}
+
+import CodeGenTools._
+import scala.tools.partest.ASMConverters
+import ASMConverters._
+import AsmUtils._
+
+import scala.tools.testing.ClearAfterClass
+
+object OptimizedBytecodeTest extends ClearAfterClass.Clearable {
+ val args = "-Yopt:l:classpath -Yopt-warnings"
+ var compiler = newCompiler(extraArgs = args)
+ def clear(): Unit = { compiler = null }
+}
+
+@RunWith(classOf[JUnit4])
+class OptimizedBytecodeTest extends ClearAfterClass {
+ ClearAfterClass.stateToClear = OptimizedBytecodeTest
+
+ val compiler = OptimizedBytecodeTest.compiler
+
+ @Test
+ def t2171(): Unit = {
+ val code =
+ """class C {
+ | final def m(msg: => String) = try 0 catch { case ex: Throwable => println(msg) }
+ | def t(): Unit = while (true) m("...")
+ |}
+ """.stripMargin
+ val List(c) = compileClasses(compiler)(code)
+ assertSameCode(getSingleMethod(c, "t"), List(Label(0), Jump(GOTO, Label(0))))
+ }
+
+ @Test
+ def t3430(): Unit = {
+ val code =
+ """class C {
+ | final def m(f: String => Boolean) = f("a")
+ | def t(): Boolean =
+ | m { s1 =>
+ | m { s2 =>
+ | while (true) { }
+ | true
+ | }
+ | }
+ |}
+ """.stripMargin
+ val List(c) = compileClasses(compiler)(code)
+
+ assertSameSummary(getSingleMethod(c, "t"), List(
+ LDC, ASTORE, ALOAD /*0*/, ALOAD /*1*/, "C$$$anonfun$1", IRETURN))
+ assertSameSummary(getSingleMethod(c, "C$$$anonfun$1"), List(LDC, "C$$$anonfun$2", IRETURN))
+ assertSameSummary(getSingleMethod(c, "C$$$anonfun$2"), List(-1 /*A*/, GOTO /*A*/))
+ }
+
+ @Test
+ def t3252(): Unit = {
+ val code =
+ """class C {
+ | def t(x: Boolean): Thread = {
+ | g {
+ | x match {
+ | case false => Tat.h { }
+ | }
+ | }
+ | }
+ |
+ | private def g[T](block: => T) = ???
+ |}
+ |object Tat {
+ | def h(block: => Unit): Nothing = ???
+ |}
+ """.stripMargin
+ val List(c, t, tMod) = compileClasses(compiler)(code, allowMessage = _.msg.contains("not be exhaustive"))
+ assertSameSummary(getSingleMethod(c, "t"), List(GETSTATIC, "$qmark$qmark$qmark", ATHROW))
+ }
+
+ @Test
+ def t6157(): Unit = {
+ val code =
+ """class C {
+ | def t = println(ErrorHandler.defaultIfIOException("String")("String"))
+ |}
+ |object ErrorHandler {
+ | import java.io.IOException
+ | @inline
+ | def defaultIfIOException[T](default: => T)(closure: => T): T = try closure catch {
+ | case e: IOException => default
+ | }
+ |}
+ """.stripMargin
+
+ val msg =
+ """ErrorHandler$::defaultIfIOException(Lscala/Function0;Lscala/Function0;)Ljava/lang/Object; is annotated @inline but could not be inlined:
+ |The operand stack at the callsite in C::t()V contains more values than the
+ |arguments expected by the callee ErrorHandler$::defaultIfIOException(Lscala/Function0;Lscala/Function0;)Ljava/lang/Object;. These values would be discarded
+ |when entering an exception handler declared in the inlined method.""".stripMargin
+
+ compileClasses(compiler)(code, allowMessage = _.msg == msg)
+ }
+
+ @Test
+ def t6547(): Unit = { // "pos" test -- check that it compiles
+ val code =
+ """trait ConfigurableDefault[@specialized V] {
+ | def fillArray(arr: Array[V], v: V) = (arr: Any) match {
+ | case x: Array[Int] => null
+ | case x: Array[Long] => v.asInstanceOf[Long]
+ | }
+ |}
+ """.stripMargin
+ compileClasses(compiler)(code)
+ }
+
+ @Test
+ def t8062(): Unit = {
+ val c1 =
+ """package warmup
+ |object Warmup { def filter[A](p: Any => Boolean): Any = filter[Any](p) }
+ """.stripMargin
+ val c2 = "class C { def t = warmup.Warmup.filter[Any](x => false) }"
+ val List(c, _, _) = compileClassesSeparately(List(c1, c2), extraArgs = OptimizedBytecodeTest.args)
+ assertInvoke(getSingleMethod(c, "t"), "warmup/Warmup$", "filter")
+ }
+
+ @Test
+ def t8306(): Unit = { // "pos" test
+ val code =
+ """class C {
+ | def foo: Int = 123
+ | lazy val extension: Int = foo match {
+ | case idx if idx != -1 => 15
+ | case _ => 17
+ | }
+ |}
+ """.stripMargin
+ compileClasses(compiler)(code)
+ }
+
+ @Test
+ def t8359(): Unit = { // "pos" test
+ // This is a minimization of code that crashed the compiler during bootstrapping
+ // in the first iteration of https://github.com/scala/scala/pull/4373, the PR
+ // that adjusted the order of free and declared params in LambdaLift.
+
+ // Was:
+ // java.lang.AssertionError: assertion failed:
+ // Record Record(<$anon: Function1>,Map(value a$1 -> Deref(LocalVar(value b)))) does not contain a field value b$1
+ // at scala.tools.nsc.Global.assert(Global.scala:262)
+ // at scala.tools.nsc.backend.icode.analysis.CopyPropagation$copyLattice$State.getFieldNonRecordValue(CopyPropagation.scala:113)
+ // at scala.tools.nsc.backend.icode.analysis.CopyPropagation$copyLattice$State.getFieldNonRecordValue(CopyPropagation.scala:122)
+ // at scala.tools.nsc.backend.opt.ClosureElimination$ClosureElim$$anonfun$analyzeMethod$1$$anonfun$apply$2.replaceFieldAccess$1(ClosureElimination.scala:124)
+ val code =
+ """package test
+ |class Typer {
+ | def bar(a: Boolean, b: Boolean): Unit = {
+ | @inline
+ | def baz(): Unit = {
+ | ((_: Any) => (Typer.this, a, b)).apply("")
+ | }
+ | ((_: Any) => baz()).apply("")
+ | }
+ |}
+ """.stripMargin
+ compileClasses(compiler)(code)
+ }
+
+ @Test
+ def t9123(): Unit = { // "pos" test
+ val code =
+ """trait Setting {
+ | type T
+ | def value: T
+ |}
+ |object Test {
+ | def test(x: Some[Setting]) = x match {
+ | case Some(dep) => Some(dep.value) map (_ => true)
+ | }
+ |}
+ """.stripMargin
+ compileClasses(compiler)(code)
+ }
+
+ @Test
+ def traitForceInfo(): Unit = {
+ // This did NOT crash unless it's in the interactive package.
+ // error: java.lang.AssertionError: assertion failed: trait Contexts.NoContext$ linkedModule: <none>List()
+ // at scala.Predef$.assert(Predef.scala:160)
+ // at scala.tools.nsc.symtab.classfile.ClassfileParser$innerClasses$.innerSymbol$1(ClassfileParser.scala:1211)
+ // at scala.tools.nsc.symtab.classfile.ClassfileParser$innerClasses$.classSymbol(ClassfileParser.scala:1223)
+ // at scala.tools.nsc.symtab.classfile.ClassfileParser.classNameToSymbol(ClassfileParser.scala:489)
+ // at scala.tools.nsc.symtab.classfile.ClassfileParser.sig2type$1(ClassfileParser.scala:757)
+ // at scala.tools.nsc.symtab.classfile.ClassfileParser.sig2type$1(ClassfileParser.scala:789)
+ val code =
+ """package scala.tools.nsc
+ |package interactive
+ |
+ |trait MyContextTrees {
+ | val self: Global
+ | val NoContext = self.analyzer.NoContext
+ |}
+ """.stripMargin
+ compileClasses(compiler)(code)
+ }
+
+ @Test
+ def t9160(): Unit = {
+ val code =
+ """class C {
+ | def getInt: Int = 0
+ | def t(trees: Object): Int = {
+ | trees match {
+ | case Some(elems) =>
+ | case tree => getInt
+ | }
+ | 55
+ | }
+ |}
+ """.stripMargin
+ val List(c) = compileClasses(compiler)(code)
+ assertSameSummary(getSingleMethod(c, "t"), List(
+ ALOAD /*1*/, INSTANCEOF /*Some*/, IFNE /*A*/,
+ ALOAD /*0*/, "getInt", POP,
+ -1 /*A*/, BIPUSH, IRETURN))
+ }
+
+ @Test
+ def t8796(): Unit = {
+ val code =
+ """final class C {
+ | def pr(): Unit = ()
+ | def t(index: Int): Unit = index match {
+ | case 0 => pr()
+ | case 1 => pr()
+ | case _ => t(index - 2)
+ | }
+ |}
+ """.stripMargin
+ val List(c) = compileClasses(compiler)(code)
+ assertSameSummary(getSingleMethod(c, "t"), List(
+ -1 /*A*/, ILOAD /*1*/, TABLESWITCH,
+ -1, ALOAD, "pr", RETURN,
+ -1, ALOAD, "pr", RETURN,
+ -1, ILOAD, ICONST_2, ISUB, ISTORE, GOTO /*A*/))
+ }
+
+ @Test
+ def t8524(): Unit = {
+ val c1 =
+ """package library
+ |object Library {
+ | @inline def pleaseInlineMe() = 1
+ | object Nested { @inline def pleaseInlineMe() = 2 }
+ |}
+ """.stripMargin
+
+ val c2 =
+ """class C {
+ | def t = library.Library.pleaseInlineMe() + library.Library.Nested.pleaseInlineMe()
+ |}
+ """.stripMargin
+
+ val cls = compileClassesSeparately(List(c1, c2), extraArgs = OptimizedBytecodeTest.args)
+ val c = cls.find(_.name == "C").get
+ assertSameSummary(getSingleMethod(c, "t"), List(
+ GETSTATIC, IFNONNULL, ACONST_NULL, ATHROW, // module load and null checks not yet eliminated
+ -1, ICONST_1, GETSTATIC, IFNONNULL, ACONST_NULL, ATHROW,
+ -1, ICONST_2, IADD, IRETURN))
+ }
+
+ @Test
+ def privateInline(): Unit = {
+ val code =
+ """final class C {
+ | private var x1 = false
+ | var x2 = false
+ |
+ | @inline private def wrapper1[T](body: => T): T = {
+ | val saved = x1
+ | x1 = true
+ | try body
+ | finally x1 = saved
+ | }
+ |
+ | @inline private def wrapper2[T](body: => T): T = {
+ | val saved = x2
+ | x2 = true
+ | try body
+ | finally x2 = saved
+ | }
+ | // inlined
+ | def f1a() = wrapper1(5)
+ | // not inlined: even after inlining `identity`, the Predef module is already on the stack for the
+ | // subsequent null check (the receiver of an inlined method, in this case Predef, is checked for
+ | // nullness, to ensure an NPE is thrown)
+ | def f1b() = identity(wrapper1(5))
+ |
+ | def f2a() = wrapper2(5) // inlined
+ | def f2b() = identity(wrapper2(5)) // not inlined
+ |}
+ """.stripMargin
+ val List(c) = compileClasses(compiler)(code, allowMessage = _.msg.contains("exception handler declared in the inlined method"))
+ assertInvoke(getSingleMethod(c, "f1a"), "C", "C$$$anonfun$1")
+ assertInvoke(getSingleMethod(c, "f1b"), "C", "wrapper1")
+ assertInvoke(getSingleMethod(c, "f2a"), "C", "C$$$anonfun$3")
+ assertInvoke(getSingleMethod(c, "f2b"), "C", "wrapper2")
+ }
+
+ @Test
+ def t7060(): Unit = {
+ val code =
+ """class C {
+ | @inline final def mbarray_apply_minibox(array: Any, tag: Byte): Long =
+ | if (tag == 0) array.asInstanceOf[Array[Long]](0)
+ | else array.asInstanceOf[Array[Byte]](0).toLong
+ |
+ | def t = mbarray_apply_minibox(null, 0)
+ |}
+ """.stripMargin
+ val List(c) = compileClasses(compiler)(code)
+ assertNoInvoke(getSingleMethod(c, "t"))
+ }
+
+ @Test
+ def t8315(): Unit = {
+ val code =
+ """class C {
+ | def t(as: Listt): Unit = {
+ | map(as, (_: Any) => return)
+ | }
+ | final def map(x: Listt, f: Any => Any): Any = {
+ | if (x eq Nill) "" else f("")
+ | }
+ |}
+ |object Nill extends Listt
+ |class Listt
+ """.stripMargin
+ val List(c, nil, nilMod, listt) = compileClasses(compiler)(code)
+ assertInvoke(getSingleMethod(c, "t"), "C", "C$$$anonfun$1")
+ }
+
+ @Test
+ def t8315b(): Unit = {
+ val code =
+ """class C {
+ | def crash: Unit = {
+ | val key = ""
+ | try map(new F(key))
+ | catch { case _: Throwable => }
+ | }
+ | final def map(f: F): Any = f.apply("")
+ |}
+ |final class F(key: String) {
+ | final def apply(a: Any): Any = throw new RuntimeException(key)
+ |}
+ """.stripMargin
+ val List(c, f) = compileClasses(compiler)(code)
+ assertInvoke(getSingleMethod(c, "crash"), "C", "map")
+ }
+
+ @Test
+ def optimiseEnablesNewOpt(): Unit = {
+ val code = """class C { def t = (1 to 10) foreach println }"""
+ val List(c) = readAsmClasses(compile(newCompiler(extraArgs = "-optimise -deprecation"))(code, allowMessage = _.msg.contains("is deprecated")))
+ assertInvoke(getSingleMethod(c, "t"), "C", "C$$$anonfun$1") // range-foreach inlined from classpath
+ }
+}
diff --git a/test/junit/scala/issues/RunTest.scala b/test/junit/scala/issues/RunTest.scala
new file mode 100644
index 0000000000..0605947e63
--- /dev/null
+++ b/test/junit/scala/issues/RunTest.scala
@@ -0,0 +1,162 @@
+package scala.issues
+
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.{AfterClass, BeforeClass, Test}
+import org.junit.Assert._
+
+import scala.reflect.runtime._
+import scala.tools.reflect.ToolBox
+import scala.tools.testing.ClearAfterClass
+
+object RunTest extends ClearAfterClass.Clearable {
+ var toolBox = universe.runtimeMirror(getClass.getClassLoader).mkToolBox()
+ override def clear(): Unit = { toolBox = null }
+
+ // definitions for individual tests
+
+ class VC(val x: Any) extends AnyVal
+}
+
+@RunWith(classOf[JUnit4])
+class RunTest extends ClearAfterClass {
+ ClearAfterClass.stateToClear = RunTest
+
+ def run[T](code: String): T = {
+ val tb = RunTest.toolBox
+ tb.eval(tb.parse(code)).asInstanceOf[T]
+ }
+
+ @Test
+ def classOfValueClassAlias(): Unit = {
+ val code =
+ """import scala.issues.RunTest.VC
+ |type aVC = VC
+ |type aInt = Int
+ |type aInteger = Integer
+ |classOf[VC] == classOf[aVC] &&
+ | classOf[aInt] == classOf[Int] &&
+ | classOf[aInteger] == classOf[Integer] &&
+ | classOf[aInt] != classOf[aInteger]
+ """.stripMargin
+ assertTrue(run[Boolean](code))
+ }
+
+ @Test
+ def classOfFinalVal(): Unit = {
+ val code =
+ """class C {
+ | final val a1 = classOf[Int]
+ | final val b1 = classOf[List[_]]
+ | final val c1 = classOf[List[String]]
+ | final val d1 = classOf[Array[Int]]
+ | final val e1 = classOf[Array[List[_]]]
+ | final val f1 = classOf[Array[_]]
+ |
+ | val a2 = classOf[Int]
+ | val b2 = classOf[List[_]]
+ | val c2 = classOf[List[String]]
+ | val d2 = classOf[Array[Int]]
+ | val e2 = classOf[Array[List[_]]]
+ | val f2 = classOf[Array[_]]
+ |
+ | val listC = Class.forName("scala.collection.immutable.List")
+ |
+ | val compare = List(
+ | (a1, a2, Integer.TYPE),
+ | (b1, b2, listC),
+ | (c1, c2, listC),
+ | (d1, d2, Array(1).getClass),
+ | (e1, e2, Array(List()).getClass),
+ | (f1, f2, new Object().getClass))
+ |}
+ |(new C).compare
+ """.stripMargin
+ type K = Class[_]
+ val cs = run[List[(K, K, K)]](code)
+ for ((x, y, z) <- cs) {
+ assertEquals(x, y)
+ assertEquals(x, z)
+ }
+ }
+
+ @Test
+ def t9702(): Unit = {
+ val code =
+ """import javax.annotation.Resource
+ |import scala.issues.RunTest.VC
+ |class C {
+ | type aList[K] = List[K]
+ | type aVC = VC
+ | type aInt = Int
+ | type aInteger = Integer
+ | @Resource(`type` = classOf[List[Int]]) def a = 0
+ | @Resource(`type` = classOf[List[_]]) def b = 0
+ | @Resource(`type` = classOf[aList[_]]) def c = 0
+ | @Resource(`type` = classOf[Int]) def d = 0
+ | @Resource(`type` = classOf[aInt]) def e = 0
+ | @Resource(`type` = classOf[Integer]) def f = 0
+ | @Resource(`type` = classOf[aInteger]) def g = 0
+ | @Resource(`type` = classOf[VC]) def h = 0
+ | @Resource(`type` = classOf[aVC]) def i = 0
+ | @Resource(`type` = classOf[Array[Int]]) def j = 0
+ | @Resource(`type` = classOf[Array[List[_]]]) def k = 0
+ |}
+ |val c = classOf[C]
+ |def typeArg(meth: String) = c.getDeclaredMethod(meth).getDeclaredAnnotation(classOf[Resource]).`type`
+ |('a' to 'k').toList.map(_.toString).map(typeArg)
+ """.stripMargin
+
+ val l = Class.forName("scala.collection.immutable.List")
+ val i = Integer.TYPE
+ val ig = new Integer(1).getClass
+ val v = new RunTest.VC(1).getClass
+ val ai = Array(1).getClass
+ val al = Array(List()).getClass
+
+ // sanity checks
+ assertEquals(i, classOf[Int])
+ assertNotEquals(i, ig)
+
+ assertEquals(run[List[Class[_]]](code),
+ List(l, l, l, i, i, ig, ig, v, v, ai, al))
+ }
+
+ @Test
+ def annotationInfoNotErased(): Unit = {
+ val code =
+ """import javax.annotation.Resource
+ |import scala.annotation.meta.getter
+ |class C {
+ | type Rg = Resource @getter
+ | @(Resource @getter)(`type` = classOf[Int]) def a = 0
+ | @Rg(`type` = classOf[Int]) def b = 0
+ |}
+ |val c = classOf[C]
+ |def typeArg(meth: String) = c.getDeclaredMethod(meth).getDeclaredAnnotation(classOf[Resource]).`type`
+ |List("a", "b") map typeArg
+ |""".stripMargin
+
+ val i = Integer.TYPE
+ assertEquals(run[List[Class[_]]](code), List(i, i))
+ }
+
+ @Test
+ def invocationReceivers(): Unit = {
+ import invocationReceiversTestCode._
+ assertEquals(run[String](definitions("Object") + runCode), "hi" * 9)
+ assertEquals(run[String](definitions("String") + runCode), "hi" * 9) // bridge method for clone generated
+ }
+
+ @Test
+ def classOfUnitConstant(): Unit = {
+ val code =
+ """abstract class A { def f: Class[_] }
+ |class C extends A { final val f = classOf[Unit] }
+ |val c = new C
+ |(c.f, (c: A).f)
+ """.stripMargin
+ val u = Void.TYPE
+ assertEquals(run[(Class[_], Class[_])](code), (u, u))
+ }
+}
diff --git a/test/junit/scala/math/BigDecimalTest.scala b/test/junit/scala/math/BigDecimalTest.scala
index a9e2481f37..5de02cbe0c 100644
--- a/test/junit/scala/math/BigDecimalTest.scala
+++ b/test/junit/scala/math/BigDecimalTest.scala
@@ -260,4 +260,9 @@ class BigDecimalTest {
testPrecision()
testRounded()
}
+
+ @Test
+ def testIsComparable() {
+ assert(BigDecimal(0.1).isInstanceOf[java.lang.Comparable[_]])
+ }
}
diff --git a/test/junit/scala/math/BigIntTest.scala b/test/junit/scala/math/BigIntTest.scala
new file mode 100644
index 0000000000..5a5694a775
--- /dev/null
+++ b/test/junit/scala/math/BigIntTest.scala
@@ -0,0 +1,16 @@
+package scala.math
+
+import java.math.{BigInteger => BI, MathContext => MC}
+
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(classOf[JUnit4])
+class BigIntTest {
+
+ @Test
+ def testIsComparable() {
+ assert(BigInt(1).isInstanceOf[java.lang.Comparable[_]])
+ }
+}
diff --git a/test/junit/scala/reflect/ClassTag.scala b/test/junit/scala/reflect/ClassTagTest.scala
index 49022dccda..49022dccda 100644
--- a/test/junit/scala/reflect/ClassTag.scala
+++ b/test/junit/scala/reflect/ClassTagTest.scala
diff --git a/test/junit/scala/reflect/internal/PrintersTest.scala b/test/junit/scala/reflect/internal/PrintersTest.scala
index 9bfe6eecb8..2305e7ea50 100644
--- a/test/junit/scala/reflect/internal/PrintersTest.scala
+++ b/test/junit/scala/reflect/internal/PrintersTest.scala
@@ -8,14 +8,6 @@ import scala.reflect.runtime.{currentMirror=>cm}
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
-@RunWith(classOf[JUnit4])
-class PrintersTest extends BasePrintTests
- with ClassPrintTests
- with TraitPrintTests
- with ValAndDefPrintTests
- with QuasiTreesPrintTests
- with PackagePrintTests
-
object PrinterHelper {
val toolbox = cm.mkToolBox()
@@ -73,7 +65,8 @@ object PrinterHelper {
import PrinterHelper._
-trait BasePrintTests {
+@RunWith(classOf[JUnit4])
+class BasePrintTest {
@Test def testIdent = assertTreeCode(Ident("*"))("*")
@Test def testConstant1 = assertTreeCode(Literal(Constant("*")))("\"*\"")
@@ -348,7 +341,8 @@ trait BasePrintTests {
@Test def testImport4 = assertPrintedCode("import scala.collection._")
}
-trait ClassPrintTests {
+@RunWith(classOf[JUnit4])
+class ClassPrintTest {
@Test def testClass = assertPrintedCode("class *")
@Test def testClassWithBody = assertPrintedCode(sm"""
@@ -833,7 +827,8 @@ trait ClassPrintTests {
|}""")
}
-trait TraitPrintTests {
+@RunWith(classOf[JUnit4])
+class TraitPrintTest {
@Test def testTrait = assertPrintedCode("trait *")
@Test def testTraitWithBody = assertPrintedCode(sm"""
@@ -953,7 +948,8 @@ trait TraitPrintTests {
|}""")
}
-trait ValAndDefPrintTests {
+@RunWith(classOf[JUnit4])
+class ValAndDefPrintTest {
@Test def testVal1 = assertPrintedCode("val a: scala.Unit = ()")
@Test def testVal2 = assertPrintedCode("val * : scala.Unit = ()")
@@ -1093,7 +1089,8 @@ trait ValAndDefPrintTests {
|}""", wrapCode = true)
}
-trait PackagePrintTests {
+@RunWith(classOf[JUnit4])
+class PackagePrintTest {
@Test def testPackage1 = assertPrintedCode(sm"""
|package foo.bar {
|
@@ -1131,7 +1128,8 @@ trait PackagePrintTests {
|}""", checkTypedTree = false)
}
-trait QuasiTreesPrintTests {
+@RunWith(classOf[JUnit4])
+class QuasiTreesPrintTest {
@Test def testQuasiIdent = assertTreeCode(q"*")("*")
@Test def testQuasiVal = assertTreeCode(q"val * : Unit = null")("val * : Unit = null")
diff --git a/test/junit/scala/reflect/internal/TreeGenTest.scala b/test/junit/scala/reflect/internal/TreeGenTest.scala
new file mode 100644
index 0000000000..db1ea5cf6a
--- /dev/null
+++ b/test/junit/scala/reflect/internal/TreeGenTest.scala
@@ -0,0 +1,51 @@
+package scala.reflect.internal
+
+import org.junit.Assert._
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+import scala.runtime.ScalaRunTime
+import scala.tools.nsc.symtab.SymbolTableForUnitTesting
+
+@RunWith(classOf[JUnit4])
+class TreeGenTest {
+ object symbolTable extends SymbolTableForUnitTesting
+
+ import symbolTable._
+
+ @Test
+ def attributedRefToTopLevelMemberNotPrefixedByThis_t9473_a(): Unit = {
+ val SomeClass = symbolOf[Some[_]]
+ val ref = gen.mkAttributedRef(SomeClass)
+ assertEquals("scala.Some", ref.toString) // was scala.this.Some
+ ref match {
+ case sel @ Select(pre @ Ident(preName), name) =>
+ assertEquals(TermName("scala"), preName)
+ assertEquals(TypeName("Some"), name)
+ assertEquals(SomeClass, sel.symbol)
+ case _ => fail(showRaw(ref))
+ }
+ }
+
+ @Test
+ def attributedRefToTopLevelMemberNotPrefixedByThis_t9473_b(): Unit = {
+ val ScalaRuntimeModule = symbolOf[ScalaRunTime.type].sourceModule
+ val ref = gen.mkAttributedRef(ScalaRuntimeModule)
+ assertEquals("scala.runtime.ScalaRunTime", ref.toString)
+ ref match {
+ case sel @ Select(Select(Ident(TermName("scala")), TermName("runtime")), TermName("ScalaRunTime")) =>
+ case _ => fail(showRaw(ref))
+ }
+ }
+ @Test
+ def attributedRefToTopLevelMemberNotPrefixedByThis_t9473_c(): Unit = {
+ val DummyImplicitClass = symbolOf[Predef.DummyImplicit]
+ val ref = gen.mkAttributedRef(DummyImplicitClass)
+ assertEquals("scala.Predef.DummyImplicit", ref.toString)
+// ref match {
+// case sel @ Select(Select(Ident(TermName("scala")), TermName("runtime")), TermName("ScalaRunTime")) =>
+// case _ => fail(showRaw(ref))
+// }
+ }
+}
diff --git a/test/junit/scala/reflect/internal/TypesTest.scala b/test/junit/scala/reflect/internal/TypesTest.scala
index 95194ef0a4..05a77cfb47 100644
--- a/test/junit/scala/reflect/internal/TypesTest.scala
+++ b/test/junit/scala/reflect/internal/TypesTest.scala
@@ -1,9 +1,10 @@
package scala.reflect.internal
import org.junit.Assert._
-import org.junit.Test
+import org.junit.{Assert, Test}
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
+import scala.collection.mutable
import scala.tools.nsc.symtab.SymbolTableForUnitTesting
@RunWith(classOf[JUnit4])
@@ -32,4 +33,29 @@ class TypesTest {
val uniquelyNarrowed2 = refinedType(boolWithString1narrow2 :: Nil, NoSymbol)
assert(uniquelyNarrowed1 =:= uniquelyNarrowed2)
}
+
+ @Test
+ def testTransitivityWithModuleTypeRef(): Unit = {
+ import rootMirror.EmptyPackageClass
+ val (module, moduleClass) = EmptyPackageClass.newModuleAndClassSymbol(TermName("O"), NoPosition, 0L)
+ val minfo = ClassInfoType(List(ObjectTpe), newScope, moduleClass)
+ module.moduleClass setInfo minfo
+ module setInfo module.moduleClass.tpe
+ val tp1 = TypeRef(ThisType(EmptyPackageClass), moduleClass, Nil)
+ val tp2 = SingleType(ThisType(EmptyPackageClass), module)
+ val tp3 = ThisType(moduleClass)
+ val tps = List(tp1, tp2, tp3)
+ val results = mutable.Buffer[String]()
+ tps.permutations.foreach {
+ case ts @ List(a, b, c) =>
+ def tsShownRaw = ts.map(t => showRaw(t)).mkString(", ")
+ if (a <:< b && b <:< c && !(a <:< c)) results += s"<:< intransitive: $tsShownRaw"
+ if (a =:= b && b =:= c && !(a =:= c)) results += s"=:= intransitive: $tsShownRaw"
+ }
+ results.toList match {
+ case Nil => // okay
+ case xs =>
+ Assert.fail(xs.mkString("\n"))
+ }
+ }
}
diff --git a/test/junit/scala/runtime/LambdaDeserializerTest.java b/test/junit/scala/runtime/LambdaDeserializerTest.java
new file mode 100644
index 0000000000..069eb4aab6
--- /dev/null
+++ b/test/junit/scala/runtime/LambdaDeserializerTest.java
@@ -0,0 +1,193 @@
+package scala.runtime;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.Serializable;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.SerializedLambda;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.HashMap;
+
+public final class LambdaDeserializerTest {
+ private LambdaHost lambdaHost = new LambdaHost();
+
+ @Test
+ public void serializationPrivate() {
+ F1<Boolean, String> f1 = lambdaHost.lambdaBackedByPrivateImplMethod();
+ Assert.assertEquals(f1.apply(true), reconstitute(f1).apply(true));
+ }
+
+ @Test
+ public void serializationStatic() {
+ F1<Boolean, String> f1 = lambdaHost.lambdaBackedByStaticImplMethod();
+ Assert.assertEquals(f1.apply(true), reconstitute(f1).apply(true));
+ }
+
+ @Test
+ public void serializationVirtualMethodReference() {
+ F1<Boolean, String> f1 = lambdaHost.lambdaBackedByVirtualMethodReference();
+ Assert.assertEquals(f1.apply(true), reconstitute(f1).apply(true));
+ }
+
+ @Test
+ public void serializationInterfaceMethodReference() {
+ F1<I, Object> f1 = lambdaHost.lambdaBackedByInterfaceMethodReference();
+ I i = new I() {
+ };
+ Assert.assertEquals(f1.apply(i), reconstitute(f1).apply(i));
+ }
+
+ @Test
+ public void serializationStaticMethodReference() {
+ F1<Boolean, String> f1 = lambdaHost.lambdaBackedByStaticMethodReference();
+ Assert.assertEquals(f1.apply(true), reconstitute(f1).apply(true));
+ }
+
+ @Test
+ public void serializationNewInvokeSpecial() {
+ F0<Object> f1 = lambdaHost.lambdaBackedByConstructorCall();
+ Assert.assertEquals(f1.apply(), reconstitute(f1).apply());
+ }
+
+ @Test
+ public void uncached() {
+ F0<Object> f1 = lambdaHost.lambdaBackedByConstructorCall();
+ F0<Object> reconstituted1 = reconstitute(f1);
+ F0<Object> reconstituted2 = reconstitute(f1);
+ Assert.assertNotEquals(reconstituted1.getClass(), reconstituted2.getClass());
+ }
+
+ @Test
+ public void cached() {
+ HashMap<String, MethodHandle> cache = new HashMap<>();
+ F0<Object> f1 = lambdaHost.lambdaBackedByConstructorCall();
+ F0<Object> reconstituted1 = reconstitute(f1, cache);
+ F0<Object> reconstituted2 = reconstitute(f1, cache);
+ Assert.assertEquals(reconstituted1.getClass(), reconstituted2.getClass());
+ }
+
+ @Test
+ public void cachedStatic() {
+ HashMap<String, MethodHandle> cache = new HashMap<>();
+ F1<Boolean, String> f1 = lambdaHost.lambdaBackedByStaticImplMethod();
+ // Check that deserialization of a static lambda always returns the
+ // same instance.
+ Assert.assertSame(reconstitute(f1, cache), reconstitute(f1, cache));
+
+ // (as is the case with regular invocation.)
+ Assert.assertSame(f1, lambdaHost.lambdaBackedByStaticImplMethod());
+ }
+
+ @Test
+ public void implMethodNameChanged() {
+ F1<Boolean, String> f1 = lambdaHost.lambdaBackedByStaticImplMethod();
+ SerializedLambda sl = writeReplace(f1);
+ checkIllegalAccess(copySerializedLambda(sl, sl.getImplMethodName() + "___", sl.getImplMethodSignature()));
+ }
+
+ @Test
+ public void implMethodSignatureChanged() {
+ F1<Boolean, String> f1 = lambdaHost.lambdaBackedByStaticImplMethod();
+ SerializedLambda sl = writeReplace(f1);
+ checkIllegalAccess(copySerializedLambda(sl, sl.getImplMethodName(), sl.getImplMethodSignature().replace("Boolean", "Integer")));
+ }
+
+ private void checkIllegalAccess(SerializedLambda serialized) {
+ try {
+ LambdaDeserializer.deserializeLambda(MethodHandles.lookup(), null, serialized);
+ throw new AssertionError();
+ } catch (IllegalArgumentException iae) {
+ if (!iae.getMessage().contains("Illegal lambda deserialization")) {
+ Assert.fail("Unexpected message: " + iae.getMessage());
+ }
+ }
+ }
+
+ private SerializedLambda copySerializedLambda(SerializedLambda sl, String implMethodName, String implMethodSignature) {
+ Object[] captures = new Object[sl.getCapturedArgCount()];
+ for (int i = 0; i < captures.length; i++) {
+ captures[i] = sl.getCapturedArg(i);
+ }
+ return new SerializedLambda(loadClass(sl.getCapturingClass()), sl.getFunctionalInterfaceClass(), sl.getFunctionalInterfaceMethodName(),
+ sl.getFunctionalInterfaceMethodSignature(), sl.getImplMethodKind(), sl.getImplClass(), implMethodName, implMethodSignature,
+ sl.getInstantiatedMethodType(), captures);
+ }
+
+ private Class<?> loadClass(String className) {
+ try {
+ return Class.forName(className.replace('/', '.'));
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ private <A, B> A reconstitute(A f1) {
+ return reconstitute(f1, null);
+ }
+
+ @SuppressWarnings("unchecked")
+ private <A, B> A reconstitute(A f1, java.util.HashMap<String, MethodHandle> cache) {
+ try {
+ return (A) LambdaDeserializer.deserializeLambda(LambdaHost.lookup(), cache, writeReplace(f1));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private <A> SerializedLambda writeReplace(A f1) {
+ try {
+ Method writeReplace = f1.getClass().getDeclaredMethod("writeReplace");
+ writeReplace.setAccessible(true);
+ return (SerializedLambda) writeReplace.invoke(f1);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
+
+
+interface F1<A, B> extends Serializable {
+ B apply(A a);
+}
+
+interface F0<A> extends Serializable {
+ A apply();
+}
+
+class LambdaHost {
+ public F1<Boolean, String> lambdaBackedByPrivateImplMethod() {
+ int local = 42;
+ return (b) -> Arrays.asList(local, b ? "true" : "false", LambdaHost.this).toString();
+ }
+
+ @SuppressWarnings("Convert2MethodRef")
+ public F1<Boolean, String> lambdaBackedByStaticImplMethod() {
+ return (b) -> String.valueOf(b);
+ }
+
+ public F1<Boolean, String> lambdaBackedByStaticMethodReference() {
+ return String::valueOf;
+ }
+
+ public F1<Boolean, String> lambdaBackedByVirtualMethodReference() {
+ return Object::toString;
+ }
+
+ public F1<I, Object> lambdaBackedByInterfaceMethodReference() {
+ return I::i;
+ }
+
+ public F0<Object> lambdaBackedByConstructorCall() {
+ return String::new;
+ }
+
+ public static MethodHandles.Lookup lookup() {
+ return MethodHandles.lookup();
+ }
+}
+
+interface I {
+ default String i() { return "i"; };
+}
diff --git a/test/junit/scala/runtime/ScalaRunTimeTest.scala b/test/junit/scala/runtime/ScalaRunTimeTest.scala
index 9da197c71a..5bfb12610e 100644
--- a/test/junit/scala/runtime/ScalaRunTimeTest.scala
+++ b/test/junit/scala/runtime/ScalaRunTimeTest.scala
@@ -5,66 +5,66 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
-/** Tests for the private class DefaultPromise */
+/** Tests for the runtime object ScalaRunTime */
@RunWith(classOf[JUnit4])
class ScalaRunTimeTest {
@Test
- def testIsTuple() {
- import ScalaRunTime.isTuple
- def check(v: Any) = {
- assertTrue(v.toString, isTuple(v))
+ def testStingOf() {
+ import ScalaRunTime.stringOf
+ import scala.collection._
+ import parallel.ParIterable
+
+ assertEquals("null", stringOf(null))
+ assertEquals( "\"\"", stringOf(""))
+
+ assertEquals("abc", stringOf("abc"))
+ assertEquals("\" abc\"", stringOf(" abc"))
+ assertEquals("\"abc \"", stringOf("abc "))
+
+ assertEquals("""Array()""", stringOf(Array.empty[AnyRef]))
+ assertEquals("""Array()""", stringOf(Array.empty[Int]))
+ assertEquals("""Array(1, 2, 3)""", stringOf(Array(1, 2, 3)))
+ assertEquals("""Array(a, "", " c", null)""", stringOf(Array("a", "", " c", null)))
+ assertEquals("""Array(Array("", 1, Array(5)), Array(1))""",
+ stringOf(Array(Array("", 1, Array(5)), Array(1))))
+
+ val map = Map(1->"", 2->"a", 3->" a", 4->null)
+ assertEquals(s"""${map.stringPrefix}(1 -> "", 2 -> a, 3 -> " a", 4 -> null)""", stringOf(map))
+ assertEquals(s"""${map.stringPrefix}(1 -> "", 2 -> a)""", stringOf(map, 2))
+
+ val iterable = Iterable("a", "", " c", null)
+ assertEquals(s"""${iterable.stringPrefix}(a, "", " c", null)""", stringOf(iterable))
+ assertEquals(s"""${iterable.stringPrefix}(a, "")""", stringOf(iterable, 2))
+
+ val parIterable = ParIterable("a", "", " c", null)
+ assertEquals(s"""${parIterable.stringPrefix}(a, "", " c", null)""", stringOf(parIterable))
+ assertEquals(s"""${parIterable.stringPrefix}(a, "")""", stringOf(parIterable, 2))
+
+ val traversable = new Traversable[Int] {
+ def foreach[U](f: Int => U): Unit = (0 to 3).foreach(f)
}
+ assertEquals(s"${traversable.stringPrefix}(0, 1, 2, 3)", stringOf(traversable))
+ assertEquals(s"${traversable.stringPrefix}(0, 1)", stringOf(traversable, 2))
- val s = ""
- check(Tuple1(s))
- check((s, s))
- check((s, s, s))
- check((s, s, s, s))
- check((s, s, s, s, s))
- check((s, s, s, s, s, s))
- check((s, s, s, s, s, s, s))
- check((s, s, s, s, s, s, s, s))
- check((s, s, s, s, s, s, s, s, s))
- check((s, s, s, s, s, s, s, s, s, s))
- check((s, s, s, s, s, s, s, s, s, s, s))
- check((s, s, s, s, s, s, s, s, s, s, s, s))
- check((s, s, s, s, s, s, s, s, s, s, s, s, s))
- check((s, s, s, s, s, s, s, s, s, s, s, s, s, s))
- check((s, s, s, s, s, s, s, s, s, s, s, s, s, s, s))
- check((s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s))
- check((s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s))
- check((s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s))
- check((s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s))
- check((s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s))
- check((s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s))
- check((s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s))
+ val tuple1 = Tuple1(0)
+ assertEquals("(0,)", stringOf(tuple1))
+ assertEquals("(0,)", stringOf(tuple1, 0))
+ assertEquals("(Array(0),)", stringOf(Tuple1(Array(0))))
- // some specialized variants will have mangled classnames
- check(Tuple1(0))
- check((0, 0))
- check((0, 0, 0))
- check((0, 0, 0, 0))
- check((0, 0, 0, 0, 0))
- check((0, 0, 0, 0, 0, 0))
- check((0, 0, 0, 0, 0, 0, 0))
- check((0, 0, 0, 0, 0, 0, 0, 0))
- check((0, 0, 0, 0, 0, 0, 0, 0, 0))
- check((0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
- check((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
- check((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
- check((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
- check((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
- check((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
- check((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
- check((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
- check((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
- check((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
- check((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
- check((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
- check((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
+ val tuple2 = Tuple2(0, 1)
+ assertEquals("(0,1)", stringOf(tuple2))
+ assertEquals("(0,1)", stringOf(tuple2, 0))
+ assertEquals("(Array(0),1)", stringOf((Array(0), 1)))
- case class C()
- val c = new C()
- assertFalse(c.toString, isTuple(c))
+ val tuple3 = Tuple3(0, 1, 2)
+ assertEquals("(0,1,2)", stringOf(tuple3))
+ assertEquals("(0,1,2)", stringOf(tuple3, 0))
+ assertEquals("(Array(0),1,2)", stringOf((Array(0), 1, 2)))
+
+ val x = new Object {
+ override def toString(): String = "this is the stringOf string"
+ }
+ assertEquals(stringOf(x), "this is the stringOf string")
+ assertEquals(stringOf(x, 2), "this is the stringOf string")
}
}
diff --git a/test/junit/scala/sys/process/t7350.scala b/test/junit/scala/sys/process/t7350.scala
new file mode 100644
index 0000000000..9fdcac8ccc
--- /dev/null
+++ b/test/junit/scala/sys/process/t7350.scala
@@ -0,0 +1,298 @@
+package scala.sys.process
+
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.Test
+import java.io.{InputStream, OutputStream, PipedInputStream, PipedOutputStream, ByteArrayInputStream,
+ ByteArrayOutputStream, IOException, Closeable}
+import java.lang.reflect.InvocationTargetException
+import scala.concurrent.{Await, Future}
+import scala.concurrent.duration.{Duration, SECONDS}
+import scala.concurrent.ExecutionContext.Implicits.global
+import scala.util.control.Exception.ignoring
+
+// Each test normally ends in a moment, but for failure cases, waits until one second.
+
+@RunWith(classOf[JUnit4])
+class PipedProcessTest {
+ class ProcessMock(error: Boolean) extends Process {
+ var destroyCount = 0
+ def isAlive() = false
+ def exitValue(): Int = {
+ if (error) {
+ throw new InterruptedException()
+ }
+ 0
+ }
+ def destroy(): Unit = { destroyCount += 1 }
+ }
+
+ class ProcessBuilderMock(process: Process, error: Boolean) extends ProcessBuilder.AbstractBuilder {
+ override def run(io: ProcessIO): Process = {
+ if (error) {
+ throw new IOException()
+ }
+ process
+ }
+ }
+
+ class PipeSinkMock extends Process.PipeSink("PipeSinkMock") {
+ var releaseCount = 0
+ override val pipe = null
+ override val sink = null
+ override def run(): Unit = {}
+ override def connectOut(out: OutputStream): Unit = {}
+ override def connectIn(pipeOut: PipedOutputStream): Unit = {}
+ override def release(): Unit = { releaseCount += 1 }
+ }
+
+ class PipeSourceMock extends Process.PipeSource("PipeSourceMock") {
+ var releaseCount = 0
+ override val pipe = null
+ override val source = null
+ override def run(): Unit = {}
+ override def connectIn(in: InputStream): Unit = {}
+ override def connectOut(sink: Process.PipeSink): Unit = {}
+ override def release(): Unit = { releaseCount += 1 }
+ }
+
+ class PipedProcesses(a: ProcessBuilder, b: ProcessBuilder, defaultIO: ProcessIO, toError: Boolean)
+ extends Process.PipedProcesses(a, b, defaultIO, toError) {
+ def callRunAndExitValue(source: Process.PipeSource, sink: Process.PipeSink) = {
+ val m = classOf[Process.PipedProcesses].getDeclaredMethod("runAndExitValue", classOf[Process.PipeSource], classOf[Process.PipeSink])
+ m.setAccessible(true)
+ try m.invoke(this, source, sink).asInstanceOf[Option[Int]]
+ catch {
+ case err: InvocationTargetException => throw err.getTargetException
+ }
+ }
+ }
+
+ // PipedProcesses need not to release resources when it normally end
+ @Test
+ def normallyEnd() {
+ val io = BasicIO(false, ProcessLogger(_ => ()))
+ val source = new PipeSourceMock
+ val sink = new PipeSinkMock
+ val a = new ProcessMock(error = false)
+ val b = new ProcessMock(error = false)
+ val p = new PipedProcesses(new ProcessBuilderMock(a, error = false), new ProcessBuilderMock(b, error = false), io, false)
+ val f = Future {
+ p.callRunAndExitValue(source, sink)
+ }
+ Await.result(f, Duration(1, SECONDS))
+ assert(source.releaseCount == 0)
+ assert(sink.releaseCount == 0)
+ assert(a.destroyCount == 0)
+ assert(b.destroyCount == 0)
+ }
+
+ // PipedProcesses must release resources when b.run() failed
+ @Test
+ def bFailed() {
+ val io = BasicIO(false, ProcessLogger(_ => ()))
+ val source = new PipeSourceMock
+ val sink = new PipeSinkMock
+ val a = new ProcessMock(error = false)
+ val b = new ProcessMock(error = false)
+ val p = new PipedProcesses(new ProcessBuilderMock(a, error = false), new ProcessBuilderMock(b, error = true), io, false)
+ val f = Future {
+ ignoring(classOf[IOException]) {
+ p.callRunAndExitValue(source, sink)
+ }
+ }
+ Await.result(f, Duration(1, SECONDS))
+ assert(source.releaseCount == 1)
+ assert(sink.releaseCount == 1)
+ assert(a.destroyCount == 0)
+ assert(b.destroyCount == 0)
+ }
+
+ // PipedProcesses must release resources when a.run() failed
+ @Test
+ def aFailed() {
+ val io = BasicIO(false, ProcessLogger(_ => ()))
+ val source = new PipeSourceMock
+ val sink = new PipeSinkMock
+ val a = new ProcessMock(error = false)
+ val b = new ProcessMock(error = false)
+ val p = new PipedProcesses(new ProcessBuilderMock(a, error = true), new ProcessBuilderMock(b, error = false), io, false)
+ val f = Future {
+ ignoring(classOf[IOException]) {
+ p.callRunAndExitValue(source, sink)
+ }
+ }
+ Await.result(f, Duration(1, SECONDS))
+ assert(source.releaseCount == 1)
+ assert(sink.releaseCount == 1)
+ assert(a.destroyCount == 0)
+ assert(b.destroyCount == 1)
+ }
+
+ // PipedProcesses must release resources when interrupted during waiting for first.exitValue()
+ @Test
+ def firstInterrupted() {
+ val io = BasicIO(false, ProcessLogger(_ => ()))
+ val source = new PipeSourceMock
+ val sink = new PipeSinkMock
+ val a = new ProcessMock(error = true)
+ val b = new ProcessMock(error = false)
+ val p = new PipedProcesses(new ProcessBuilderMock(a, error = false), new ProcessBuilderMock(b, error = false), io, false)
+ val f = Future {
+ p.callRunAndExitValue(source, sink)
+ }
+ Await.result(f, Duration(1, SECONDS))
+ assert(source.releaseCount == 1)
+ assert(sink.releaseCount == 1)
+ assert(a.destroyCount == 1)
+ assert(b.destroyCount == 1)
+ }
+
+ // PipedProcesses must release resources when interrupted during waiting for second.exitValue()
+ @Test
+ def secondInterrupted() {
+ val io = BasicIO(false, ProcessLogger(_ => ()))
+ val source = new PipeSourceMock
+ val sink = new PipeSinkMock
+ val a = new ProcessMock(error = false)
+ val b = new ProcessMock(error = true)
+ val p = new PipedProcesses(new ProcessBuilderMock(a, error = false), new ProcessBuilderMock(b, error = false), io, false)
+ val f = Future {
+ p.callRunAndExitValue(source, sink)
+ }
+ Await.result(f, Duration(1, SECONDS))
+ assert(source.releaseCount == 1)
+ assert(sink.releaseCount == 1)
+ assert(a.destroyCount == 1)
+ assert(b.destroyCount == 1)
+ }
+}
+
+@RunWith(classOf[JUnit4])
+class PipeSourceSinkTest {
+ def throwsIOException(f: => Unit) = {
+ try { f; false }
+ catch { case _: IOException => true }
+ }
+
+ class PipeSink extends Process.PipeSink("TestPipeSink") {
+ def ensureRunloopStarted() = {
+ while (sink.size() > 0) {
+ Thread.sleep(1)
+ }
+ }
+ def isReleased = {
+ val field = classOf[Process.PipeSink].getDeclaredField("pipe")
+ field.setAccessible(true)
+ val pipe = field.get(this).asInstanceOf[PipedInputStream]
+ !this.isAlive && throwsIOException { pipe.read() }
+ }
+ }
+
+ class PipeSource extends Process.PipeSource("TestPipeSource") {
+ def ensureRunloopStarted() = {
+ while (source.size() > 0) {
+ Thread.sleep(1)
+ }
+ }
+ def isReleased = {
+ val field = classOf[Process.PipeSource].getDeclaredField("pipe")
+ field.setAccessible(true)
+ val pipe = field.get(this).asInstanceOf[PipedOutputStream]
+ !this.isAlive && throwsIOException { pipe.write(1) }
+ }
+ }
+
+ trait CloseChecking extends Closeable {
+ var closed = false
+ override def close() = closed = true
+ }
+ class DebugOutputStream extends ByteArrayOutputStream with CloseChecking
+ class DebugInputStream(s: String) extends ByteArrayInputStream(s.getBytes()) with CloseChecking
+ class DebugInfinityInputStream extends InputStream with CloseChecking {
+ def read() = 1
+ }
+
+ def sourceSink() = {
+ val source = new PipeSource
+ val sink = new PipeSink
+ source connectOut sink
+ source.start()
+ sink.start()
+ (source, sink)
+ }
+
+ // PipeSource and PipeSink must release resources when it normally end
+ @Test
+ def normallyEnd() {
+ val in = new DebugInputStream("aaa")
+ val (source, sink) = sourceSink()
+ val out = new DebugOutputStream
+ source connectIn in
+ sink connectOut out
+ val f = Future {
+ source.join()
+ sink.join()
+ }
+ Await.result(f, Duration(1, SECONDS))
+ assert(in.closed == true)
+ assert(out.closed == true)
+ assert(source.isReleased == true)
+ assert(sink.isReleased == true)
+ }
+
+ // PipeSource and PipeSink must release resources when interrupted during waiting for source.take()
+ @Test
+ def sourceInterrupted() {
+ val (source, sink) = sourceSink()
+ val out = new DebugOutputStream
+ sink connectOut out
+ val f = Future {
+ sink.ensureRunloopStarted()
+ source.release()
+ sink.release()
+ }
+ Await.result(f, Duration(1, SECONDS))
+ assert(out.closed == true)
+ assert(source.isReleased == true)
+ assert(sink.isReleased == true)
+ }
+
+ // PipeSource and PipeSink must release resources when interrupted during waiting for sink.take()
+ @Test
+ def sinkInterrupted() {
+ val in = new DebugInputStream("aaa")
+ val (source, sink) = sourceSink()
+ source connectIn in
+ val f = Future {
+ source.ensureRunloopStarted()
+ source.release()
+ sink.release()
+ }
+ Await.result(f, Duration(1, SECONDS))
+ assert(in.closed == true)
+ assert(source.isReleased == true)
+ assert(sink.isReleased == true)
+ }
+
+ // PipeSource and PipeSink must release resources when interrupted during copy streams"
+ @Test
+ def runloopInterrupted() {
+ val in = new DebugInfinityInputStream
+ val (source, sink) = sourceSink()
+ val out = new DebugOutputStream
+ source connectIn in
+ sink connectOut out
+ val f = Future {
+ source.ensureRunloopStarted()
+ sink.ensureRunloopStarted()
+ source.release()
+ sink.release()
+ }
+ Await.result(f, Duration(1, SECONDS))
+ assert(in.closed == true)
+ assert(out.closed == true)
+ assert(source.isReleased == true)
+ assert(sink.isReleased == true)
+ }
+}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala b/test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala
index 6ada0e20fb..8b8e2b36de 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala
@@ -12,7 +12,7 @@ import scala.tools.testing.ClearAfterClass
object BTypesTest extends ClearAfterClass.Clearable {
var compiler = {
- val comp = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:none")
+ val comp = newCompiler(extraArgs = "-Yopt:l:none")
new comp.Run() // initializes some of the compiler
comp.exitingDelambdafy(comp.scalaPrimitives.init()) // needed: it's only done when running the backend, and we don't actually run the compiler
comp.exitingDelambdafy(comp.genBCode.bTypes.initializeCoreBTypes())
diff --git a/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala b/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala
index ee9580c1c3..389e5b2ead 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala
@@ -48,11 +48,13 @@ object CodeGenTools {
resetOutput(compiler)
compiler
}
-
+
def newCompilerWithoutVirtualOutdir(defaultArgs: String = "-usejavacp", extraArgs: String = ""): Global = {
- val settings = new Settings()
+ def showError(s: String) = throw new Exception(s)
+ val settings = new Settings(showError)
val args = (CommandLineParser tokenize defaultArgs) ++ (CommandLineParser tokenize extraArgs)
- settings.processArguments(args, processAll = true)
+ val (_, nonSettingsArgs) = settings.processArguments(args, processAll = true)
+ if (nonSettingsArgs.nonEmpty) showError("invalid compiler flags: " + nonSettingsArgs.mkString(" "))
new Global(settings, new StoreReporter)
}
@@ -93,6 +95,23 @@ object CodeGenTools {
getGeneratedClassfiles(compiler.settings.outputDirs.getSingleOutput.get)
}
+ def compileTransformed(compiler: Global)(scalaCode: String, javaCode: List[(String, String)] = Nil, beforeBackend: compiler.Tree => compiler.Tree): List[(String, Array[Byte])] = {
+ compiler.settings.stopBefore.value = "jvm" :: Nil
+ val run = newRun(compiler)
+ import compiler._
+ val scalaUnit = newCompilationUnit(scalaCode, "unitTestSource.scala")
+ val javaUnits = javaCode.map(p => newCompilationUnit(p._1, p._2))
+ val units = scalaUnit :: javaUnits
+ run.compileUnits(units, run.parserPhase)
+ compiler.settings.stopBefore.value = Nil
+ scalaUnit.body = beforeBackend(scalaUnit.body)
+ checkReport(compiler, _ => false)
+ val run1 = newRun(compiler)
+ run1.compileUnits(units, run1.phaseNamed("jvm"))
+ checkReport(compiler, _ => false)
+ getGeneratedClassfiles(compiler.settings.outputDirs.getSingleOutput.get)
+ }
+
/**
* Compile multiple Scala files separately into a single output directory.
*
@@ -145,13 +164,60 @@ object CodeGenTools {
convertMethod(m)
}
+ def assertSameCode(method: Method, expected: List[Instruction]): Unit = assertSameCode(method.instructions.dropNonOp, expected)
def assertSameCode(actual: List[Instruction], expected: List[Instruction]): Unit = {
- assertTrue(s"\nExpected: $expected\nActual : $actual", actual === expected)
+ assert(actual === expected, s"\nExpected: $expected\nActual : $actual")
+ }
+
+ def assertSameSummary(method: Method, expected: List[Any]): Unit = assertSameSummary(method.instructions, expected)
+ def assertSameSummary(actual: List[Instruction], expected: List[Any]): Unit = {
+ def expectedString = expected.map({
+ case s: String => s""""$s""""
+ case i: Int => opcodeToString(i, i)
+ }).mkString("List(", ", ", ")")
+ assert(actual.summary == expected, s"\nFound : ${actual.summaryText}\nExpected: $expectedString")
+ }
+
+ def assertNoInvoke(m: Method): Unit = assertNoInvoke(m.instructions)
+ def assertNoInvoke(ins: List[Instruction]): Unit = {
+ assert(!ins.exists(_.isInstanceOf[Invoke]), ins.stringLines)
+ }
+
+ def assertInvoke(m: Method, receiver: String, method: String): Unit = assertInvoke(m.instructions, receiver, method)
+ def assertInvoke(l: List[Instruction], receiver: String, method: String): Unit = {
+ assert(l.exists {
+ case Invoke(_, `receiver`, `method`, _, _) => true
+ case _ => false
+ }, l.stringLines)
+ }
+
+ def assertDoesNotInvoke(m: Method, method: String): Unit = assertDoesNotInvoke(m.instructions, method)
+ def assertDoesNotInvoke(l: List[Instruction], method: String): Unit = {
+ assert(!l.exists {
+ case i: Invoke => i.name == method
+ case _ => false
+ }, l.stringLines)
+ }
+
+ def assertInvokedMethods(m: Method, expected: List[String]): Unit = assertInvokedMethods(m.instructions, expected)
+ def assertInvokedMethods(l: List[Instruction], expected: List[String]): Unit = {
+ def quote(l: List[String]) = l.map(s => s""""$s"""").mkString("List(", ", ", ")")
+ val actual = l collect { case i: Invoke => i.owner + "." + i.name }
+ assert(actual == expected, s"\nFound : ${quote(actual)}\nExpected: ${quote(expected)}")
+ }
+
+ def assertNoIndy(m: Method): Unit = assertNoIndy(m.instructions)
+ def assertNoIndy(l: List[Instruction]) = {
+ val indy = l collect { case i: InvokeDynamic => i }
+ assert(indy.isEmpty, indy)
}
def getSingleMethod(classNode: ClassNode, name: String): Method =
convertMethod(classNode.methods.asScala.toList.find(_.name == name).get)
+ def findAsmMethods(c: ClassNode, p: String => Boolean) = c.methods.iterator.asScala.filter(m => p(m.name)).toList.sortBy(_.name)
+ def findAsmMethod(c: ClassNode, name: String) = findAsmMethods(c, _ == name).head
+
/**
* Instructions that match `query` when textified.
* If `query` starts with a `+`, the next instruction is returned.
@@ -159,7 +225,7 @@ object CodeGenTools {
def findInstr(method: MethodNode, query: String): List[AbstractInsnNode] = {
val useNext = query(0) == '+'
val instrPart = if (useNext) query.drop(1) else query
- val insns = method.instructions.iterator.asScala.find(i => textify(i) contains instrPart).toList
+ val insns = method.instructions.iterator.asScala.filter(i => textify(i) contains instrPart).toList
if (useNext) insns.map(_.getNext) else insns
}
@@ -175,4 +241,8 @@ object CodeGenTools {
implicit class MortalInstruction(val ins: Instruction) extends AnyVal {
def dead: (Instruction, Boolean) = (ins, false)
}
+
+ implicit class listStringLines[T](val l: List[T]) extends AnyVal {
+ def stringLines = l.mkString("\n")
+ }
}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/DefaultMethodTest.scala b/test/junit/scala/tools/nsc/backend/jvm/DefaultMethodTest.scala
new file mode 100644
index 0000000000..2ce9d21331
--- /dev/null
+++ b/test/junit/scala/tools/nsc/backend/jvm/DefaultMethodTest.scala
@@ -0,0 +1,43 @@
+package scala.tools.nsc.backend.jvm
+
+import org.junit.Assert._
+import org.junit.Test
+
+import scala.collection.JavaConverters
+import scala.tools.asm.Opcodes
+import scala.tools.asm.tree.ClassNode
+import scala.tools.nsc.backend.jvm.CodeGenTools._
+import JavaConverters._
+import scala.tools.testing.ClearAfterClass
+
+object DefaultMethodTest extends ClearAfterClass.Clearable {
+ var compiler = newCompiler()
+ def clear(): Unit = { compiler = null }
+}
+
+class DefaultMethodTest extends ClearAfterClass {
+ ClearAfterClass.stateToClear = DefaultMethodTest
+ val compiler = DefaultMethodTest.compiler
+
+ @Test
+ def defaultMethodsViaGenBCode(): Unit = {
+ import compiler._
+ val code = "package pack { trait T { def foo: Int }}"
+ object makeFooDefaultMethod extends Transformer {
+ val Foo = TermName("foo")
+ /** Transforms a single tree. */
+ override def transform(tree: compiler.Tree): compiler.Tree = tree match {
+ case dd @ DefDef(_, Foo, _, _, _, _) =>
+ dd.symbol.setFlag(reflect.internal.Flags.JAVA_DEFAULTMETHOD)
+ copyDefDef(dd)(rhs = Literal(Constant(1)).setType(definitions.IntTpe))
+ case _ => super.transform(tree)
+ }
+ }
+ val asmClasses: List[ClassNode] = readAsmClasses(compileTransformed(compiler)(code, Nil, makeFooDefaultMethod.transform(_)))
+ val foo = asmClasses.head.methods.iterator.asScala.toList.last
+ assertTrue("default method should not be abstract", (foo.access & Opcodes.ACC_ABSTRACT) == 0)
+ assertTrue("default method body emitted", foo.instructions.size() > 0)
+ }
+
+
+}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala b/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala
index 240d3523f1..0cdc6ead10 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala
@@ -10,7 +10,7 @@ import scala.tools.partest.ASMConverters._
import scala.tools.testing.ClearAfterClass
object DirectCompileTest extends ClearAfterClass.Clearable {
- var compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:method")
+ var compiler = newCompiler(extraArgs = "-Yopt:l:method")
def clear(): Unit = { compiler = null }
}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/IndyLambdaTest.scala b/test/junit/scala/tools/nsc/backend/jvm/IndyLambdaTest.scala
new file mode 100644
index 0000000000..d29f6b0a13
--- /dev/null
+++ b/test/junit/scala/tools/nsc/backend/jvm/IndyLambdaTest.scala
@@ -0,0 +1,74 @@
+package scala.tools.nsc.backend.jvm
+
+import org.junit.Assert._
+import org.junit.{Assert, Test}
+
+import scala.tools.asm.{Handle, Opcodes}
+import scala.tools.asm.tree.InvokeDynamicInsnNode
+import scala.tools.nsc.backend.jvm.AsmUtils._
+import scala.tools.nsc.backend.jvm.CodeGenTools._
+import scala.tools.testing.ClearAfterClass
+import scala.collection.JavaConverters._
+
+object IndyLambdaTest extends ClearAfterClass.Clearable {
+ var compiler = newCompiler()
+
+ def clear(): Unit = {
+ compiler = null
+ }
+}
+
+class IndyLambdaTest extends ClearAfterClass {
+ ClearAfterClass.stateToClear = IndyLambdaTest
+ val compiler = IndyLambdaTest.compiler
+
+ @Test def boxingBridgeMethodUsedSelectively(): Unit = {
+ def implMethodDescriptorFor(code: String): String = {
+ val method = compileMethods(compiler)(s"""def f = $code """).find(_.name == "f").get
+ val x = method.instructions.iterator.asScala.toList
+ x.flatMap {
+ case insn : InvokeDynamicInsnNode => insn.bsmArgs.collect { case h : Handle => h.getDesc }
+ case _ => Nil
+ }.head
+ }
+
+ val obj = "Ljava/lang/Object;"
+ val str = "Ljava/lang/String;"
+
+ // unspecialized functions that have a primitive in parameter or return position
+ // give rise to a "boxing bridge" method (which has the suffix `$adapted`).
+ // This is because Scala's unboxing of null values gives zero, whereas Java's throw a NPE.
+
+ // 1. Here we show that we are calling the boxing bridge (the lambda bodies here are compiled into
+ // methods of `(I)Ljava/lang/Object;` / `(I)Ljava/lang/Object;` respectively.)
+ assertEquals(s"($obj)$obj", implMethodDescriptorFor("(x: Int) => new Object"))
+ assertEquals(s"($obj)$obj", implMethodDescriptorFor("(x: Object) => 0"))
+
+ // 2a. We don't need such adaptations for parameters or return values with types that differ
+ // from Object due to other generic substitution, LambdaMetafactory will downcast the arguments.
+ assertEquals(s"($str)$str", implMethodDescriptorFor("(x: String) => x"))
+
+ // 2b. Testing 2a. in combination with 1.
+ assertEquals(s"($obj)$str", implMethodDescriptorFor("(x: Int) => \"\""))
+ assertEquals(s"($str)$obj", implMethodDescriptorFor("(x: String) => 0"))
+
+ // 3. Specialized functions, don't need any of this as they implement a method like `apply$mcII$sp`,
+ // and the (un)boxing is handled in the base class in code emitted by scalac.
+ assertEquals("(I)I", implMethodDescriptorFor("(x: Int) => x"))
+
+ // non-builtin sams are like specialized functions
+ compileClasses(compiler)("class VC(private val i: Int) extends AnyVal; trait FunVC { def apply(a: VC): VC }")
+ assertEquals("(I)I", implMethodDescriptorFor("((x: VC) => x): FunVC"))
+
+ compileClasses(compiler)("trait Fun1[T, U] { def apply(a: T): U }")
+ assertEquals(s"($obj)$str", implMethodDescriptorFor("(x => x.toString): Fun1[Int, String]"))
+ assertEquals(s"($obj)$obj", implMethodDescriptorFor("(x => println(x)): Fun1[Int, Unit]"))
+ assertEquals(s"($obj)$str", implMethodDescriptorFor("((x: VC) => \"\") : Fun1[VC, String]"))
+ assertEquals(s"($str)$obj", implMethodDescriptorFor("((x: String) => new VC(0)) : Fun1[String, VC]"))
+
+ compileClasses(compiler)("trait Coll[A, Repr] extends Any")
+ compileClasses(compiler)("final class ofInt(val repr: Array[Int]) extends AnyVal with Coll[Int, Array[Int]]")
+
+ assertEquals(s"([I)$obj", implMethodDescriptorFor("((xs: Array[Int]) => new ofInt(xs)): Array[Int] => Coll[Int, Array[Int]]"))
+ }
+}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/IndySammyTest.scala b/test/junit/scala/tools/nsc/backend/jvm/IndySammyTest.scala
new file mode 100644
index 0000000000..b9e45a7dc9
--- /dev/null
+++ b/test/junit/scala/tools/nsc/backend/jvm/IndySammyTest.scala
@@ -0,0 +1,160 @@
+package scala.tools.nsc
+package backend.jvm
+
+import org.junit.Assert.assertEquals
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.Test
+
+import scala.tools.asm.Opcodes._
+import scala.tools.asm.tree._
+import scala.tools.nsc.reporters.StoreReporter
+import CodeGenTools._
+import scala.tools.partest.ASMConverters
+import ASMConverters._
+
+import scala.tools.testing.ClearAfterClass
+
+object IndySammyTest extends ClearAfterClass.Clearable {
+ var _compiler = newCompiler()
+
+ def compile(scalaCode: String, javaCode: List[(String, String)] = Nil, allowMessage: StoreReporter#Info => Boolean = _ => false): List[ClassNode] =
+ compileClasses(_compiler)(scalaCode, javaCode, allowMessage)
+
+ def clear(): Unit = { _compiler = null }
+}
+
+@RunWith(classOf[JUnit4])
+class IndySammyTest extends ClearAfterClass {
+ ClearAfterClass.stateToClear = IndySammyTest
+ import IndySammyTest._
+
+ val compiler = _compiler
+
+ def funClassName(from: String, to: String) = s"Fun$from$to"
+ def classPrologue(from: String, to: String) =
+ "class VC(private val i: Int) extends AnyVal\n" +
+ s"trait ${funClassName(from, to)} { def apply(a: $from): $to}"
+
+ def lamDef(from: String, to: String, body: String => String) =
+ s"""def lam = (x => ${body("x")}): ${funClassName(from, to)}"""
+
+ def appDef(arg: String) = s"""def app = lam($arg)"""
+
+ /* Create a lambda of type "$from => $to" (with body "$body(x)" if "x" is the argument name),
+ * and apply it to `arg`.
+ *
+ * Check:
+ * - the signature of the apply method
+ * - the instructions in the lambda's body (anonfun method)
+ * - the instructions used to create the argument for the application
+ * (and the return corresponding to the lambda's result type)
+ */
+ def test(from: String, to: String, arg: String, body: String => String = x => x)
+ (expectedSig: String, lamBody: List[Instruction], appArgs: List[Instruction], ret: Instruction)
+ (allowMessage: StoreReporter#Info => Boolean = _ => false) = {
+ val cls = compile(s"${classPrologue(from, to)}")
+ val methodNodes = compileMethods(compiler)(lamDef(from, to, body) +";"+ appDef(arg), allowMessage)
+
+ val applySig = cls.head.methods.get(0).desc
+ val anonfun = methodNodes.find(_.name contains "$anonfun$").map(convertMethod).get
+ val lamInsn = methodNodes.find(_.name == "lam").map(instructionsFromMethod).get.dropNonOp
+ val applyInvoke = methodNodes.find(_.name == "app").map(convertMethod).get
+
+ assertEquals(expectedSig, applySig)
+ assert(lamInsn.length == 2 && lamInsn.head.isInstanceOf[InvokeDynamic], lamInsn)
+ assertSameCode(anonfun, lamBody)
+ assertSameCode(applyInvoke, List(
+ VarOp(ALOAD, 0),
+ Invoke(INVOKEVIRTUAL, "C", "lam", s"()L${funClassName(from, to)};", false)) ++ appArgs ++ List(
+ Invoke(INVOKEINTERFACE, funClassName(from, to), "apply", applySig, true), ret)
+ )
+ }
+
+// def testSpecial(lam: String, lamTp: String, arg: String)(allowMessage: StoreReporter#Info => Boolean = _ => false) = {
+// val cls = compile("trait Special[@specialized A] { def apply(a: A): A}" )
+// val methodNodes = compileMethods(compiler)(s"def lam : $lamTp = $lam" +";"+ appDef(arg), allowMessage)
+//
+// val anonfun = methodNodes.filter(_.name contains "$anonfun$").map(convertMethod)
+// val lamInsn = methodNodes.find(_.name == "lam").map(instructionsFromMethod).get.dropNonOp
+// val applyInvoke = methodNodes.find(_.name == "app").map(convertMethod).get
+//
+// assert(lamInsn.length == 2 && lamInsn.head.isInstanceOf[InvokeDynamic], lamInsn)
+// assertSameCode(anonfun, lamBody)
+// assertSameCode(applyInvoke, List(
+// VarOp(ALOAD, 0),
+// Invoke(INVOKEVIRTUAL, "C", "lam", s"()L${funClassName(from, to)};", false)) ++ appArgs ++ List(
+// Invoke(INVOKEINTERFACE, funClassName(from, to), "apply", applySig, true), ret)
+// )
+// }
+
+ // x => x : VC => VC applied to VC(1)
+ @Test
+ def testVC_VC_VC =
+ test("VC", "VC", "new VC(1)")("(I)I",
+ List(VarOp(ILOAD, 0), Op(IRETURN)),
+ List(Op(ICONST_1)),
+ Op(IRETURN))()
+
+ // x => new VC(x) : Int => VC applied to 1
+ @Test
+ def testInt_VC_1 =
+ test("Int", "VC", "1", x => s"new VC($x)")("(I)I",
+ List(VarOp(ILOAD, 0), Op(IRETURN)),
+ List(Op(ICONST_1)),
+ Op(IRETURN))()
+
+ // x => x : VC => Int applied to VC(1)
+ @Test
+ def testVC_Int_VC =
+ test("VC", "Int", "new VC(1)", x => "1")("(I)I",
+ List(Op(ICONST_1), Op(IRETURN)),
+ List(Op(ICONST_1)),
+ Op(IRETURN))()
+
+ // x => new VC(1) : VC => Any applied to VC(1)
+ @Test
+ def testVC_Any_VC =
+ test("VC", "Any", "new VC(1)", x => s"new VC(1)")("(I)Ljava/lang/Object;",
+ List(TypeOp(NEW, "VC"), Op(DUP), Op(ICONST_1), Invoke(INVOKESPECIAL, "VC", "<init>", "(I)V", false), Op(ARETURN)),
+ List(Op(ICONST_1)),
+ Op(ARETURN))()
+
+
+ // x => x : VC => Unit applied to VC(1)
+ @Test
+ def testVC_Unit_VC =
+ test("VC", "Unit", "new VC(1)")("(I)V",
+ List(VarOp(ILOAD, 0), Op(POP), Op(RETURN)),
+ List(Op(ICONST_1)),
+ Op(RETURN))(allowMessage = _.msg.contains("pure expression"))
+
+ // x => new VC(x.asInstanceOf[Int]) : Any => VC applied to 1
+ //
+ // Scala:
+ // def lam = (x => new VC(x.asInstanceOf[Int])): FunAny_VC
+ // def app = lam(1)
+ // Java:
+ // FunAny_VC lam() { return x -> BoxesRunTime.unboxToInt((Object)x); }
+ // int app() { lam().apply(BoxesRunTime.boxToInteger((int)1));
+ @Test
+ def testAny_VC_1 =
+ test("Any", "VC", "1", x => s"new VC($x.asInstanceOf[Int])")("(Ljava/lang/Object;)I",
+ List(VarOp(ALOAD, 0), Invoke(INVOKESTATIC, "scala/runtime/BoxesRunTime", "unboxToInt", "(Ljava/lang/Object;)I", false), Op(IRETURN)),
+ List(Op(ICONST_1), Invoke(INVOKESTATIC, "scala/runtime/BoxesRunTime", "boxToInteger", "(I)Ljava/lang/Integer;", false)),
+ Op(IRETURN))()
+
+ // TODO
+ // x => x : Special[Int] applied to 1
+// @Test
+// def testSpecial_Int_1 =
+// testSpecial("x => x", "Special[Int]", "1")()
+
+
+ // Tests ThisReferringMethodsTraverser
+ @Test
+ def testStaticIfNoThisReference: Unit = {
+ val methodNodes = compileMethods(compiler)("def foo = () => () => () => 42")
+ methodNodes.forall(m => !m.name.contains("anonfun") || (m.access & ACC_STATIC) == ACC_STATIC)
+ }
+}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/StringConcatTest.scala b/test/junit/scala/tools/nsc/backend/jvm/StringConcatTest.scala
new file mode 100644
index 0000000000..2a9b8f7198
--- /dev/null
+++ b/test/junit/scala/tools/nsc/backend/jvm/StringConcatTest.scala
@@ -0,0 +1,133 @@
+package scala.tools.nsc
+package backend.jvm
+
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.Test
+import scala.tools.asm.Opcodes._
+import org.junit.Assert._
+
+import scala.tools.testing.AssertUtil._
+
+import CodeGenTools._
+import scala.tools.partest.ASMConverters
+import ASMConverters._
+import scala.tools.testing.ClearAfterClass
+
+object StringConcatTest extends ClearAfterClass.Clearable {
+ var compiler = newCompiler()
+ def clear(): Unit = { compiler = null }
+}
+
+@RunWith(classOf[JUnit4])
+class StringConcatTest extends ClearAfterClass {
+ ClearAfterClass.stateToClear = StringConcatTest
+ val compiler = StringConcatTest.compiler
+
+ @Test
+ def appendOverloadNoBoxing(): Unit = {
+ val code =
+ """class C {
+ | def t1(
+ | v: Unit,
+ | z: Boolean,
+ | c: Char,
+ | b: Byte,
+ | s: Short,
+ | i: Int,
+ | l: Long,
+ | f: Float,
+ | d: Double,
+ | str: String,
+ | sbuf: java.lang.StringBuffer,
+ | chsq: java.lang.CharSequence,
+ | chrs: Array[Char]) = str + this + v + z + c + b + s + i + f + l + d + sbuf + chsq + chrs
+ |
+ | // similar, but starting off with any2stringadd
+ | def t2(
+ | v: Unit,
+ | z: Boolean,
+ | c: Char,
+ | b: Byte,
+ | s: Short,
+ | i: Int,
+ | l: Long,
+ | f: Float,
+ | d: Double,
+ | str: String,
+ | sbuf: java.lang.StringBuffer,
+ | chsq: java.lang.CharSequence,
+ | chrs: Array[Char]) = this + str + v + z + c + b + s + i + f + l + d + sbuf + chsq + chrs
+ |}
+ """.stripMargin
+ val List(c) = compileClasses(compiler)(code)
+
+ def invokeNameDesc(m: String): List[String] = getSingleMethod(c, m).instructions collect {
+ case Invoke(_, _, name, desc, _) => name + desc
+ }
+ assertEquals(invokeNameDesc("t1"), List(
+ "<init>()V",
+ "append(Ljava/lang/String;)Ljava/lang/StringBuilder;",
+ "append(Ljava/lang/Object;)Ljava/lang/StringBuilder;",
+ "append(Ljava/lang/Object;)Ljava/lang/StringBuilder;",
+ "append(Z)Ljava/lang/StringBuilder;",
+ "append(C)Ljava/lang/StringBuilder;",
+ "append(I)Ljava/lang/StringBuilder;",
+ "append(I)Ljava/lang/StringBuilder;",
+ "append(I)Ljava/lang/StringBuilder;",
+ "append(F)Ljava/lang/StringBuilder;",
+ "append(J)Ljava/lang/StringBuilder;",
+ "append(D)Ljava/lang/StringBuilder;",
+ "append(Ljava/lang/StringBuffer;)Ljava/lang/StringBuilder;",
+ "append(Ljava/lang/CharSequence;)Ljava/lang/StringBuilder;",
+ "append(Ljava/lang/Object;)Ljava/lang/StringBuilder;", // test that we're not using the [C overload
+ "toString()Ljava/lang/String;"))
+
+ assertEquals(invokeNameDesc("t2"), List(
+ "<init>()V",
+ "any2stringadd(Ljava/lang/Object;)Ljava/lang/Object;",
+ "$plus$extension(Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/String;",
+ "append(Ljava/lang/String;)Ljava/lang/StringBuilder;",
+ "append(Ljava/lang/Object;)Ljava/lang/StringBuilder;",
+ "append(Z)Ljava/lang/StringBuilder;",
+ "append(C)Ljava/lang/StringBuilder;",
+ "append(I)Ljava/lang/StringBuilder;",
+ "append(I)Ljava/lang/StringBuilder;",
+ "append(I)Ljava/lang/StringBuilder;",
+ "append(F)Ljava/lang/StringBuilder;",
+ "append(J)Ljava/lang/StringBuilder;",
+ "append(D)Ljava/lang/StringBuilder;",
+ "append(Ljava/lang/StringBuffer;)Ljava/lang/StringBuilder;",
+ "append(Ljava/lang/CharSequence;)Ljava/lang/StringBuilder;",
+ "append(Ljava/lang/Object;)Ljava/lang/StringBuilder;", // test that we're not using the [C overload
+ "toString()Ljava/lang/String;"))
+ }
+
+ @Test
+ def concatPrimitiveCorrectness(): Unit = {
+ val obj: Object = new { override def toString = "TTT" }
+ def t(
+ v: Unit,
+ z: Boolean,
+ c: Char,
+ b: Byte,
+ s: Short,
+ i: Int,
+ l: Long,
+ f: Float,
+ d: Double,
+ str: String,
+ sbuf: java.lang.StringBuffer,
+ chsq: java.lang.CharSequence,
+ chrs: Array[Char]) = {
+ val s1 = str + obj + v + z + c + b + s + i + f + l + d + sbuf + chsq + chrs
+ val s2 = obj + str + v + z + c + b + s + i + f + l + d + sbuf + chsq + chrs
+ s1 + "//" + s2
+ }
+ def sbuf = { val r = new java.lang.StringBuffer(); r.append("sbuf"); r }
+ def chsq: java.lang.CharSequence = "chsq"
+ val s = t((), true, 'd', 3: Byte, 12: Short, 3, -32l, 12.3f, -4.2d, "me", sbuf, chsq, Array('a', 'b'))
+ val r = s.replaceAll("""\[C@\w+""", "<ARRAY>")
+ assertEquals(r, "meTTT()trued312312.3-32-4.2sbufchsq<ARRAY>//TTTme()trued312312.3-32-4.2sbufchsq<ARRAY>")
+ }
+}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzerTest.scala b/test/junit/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzerTest.scala
index 94e776aadb..571d84c872 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzerTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzerTest.scala
@@ -17,10 +17,10 @@ import scala.tools.testing.ClearAfterClass
import scala.tools.nsc.backend.jvm.opt.BytecodeUtils._
import AsmUtils._
-import scala.collection.convert.decorateAsScala._
+import scala.collection.JavaConverters._
object NullnessAnalyzerTest extends ClearAfterClass.Clearable {
- var noOptCompiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:none")
+ var noOptCompiler = newCompiler(extraArgs = "-Yopt:l:none")
def clear(): Unit = {
noOptCompiler = null
@@ -31,25 +31,22 @@ object NullnessAnalyzerTest extends ClearAfterClass.Clearable {
class NullnessAnalyzerTest extends ClearAfterClass {
ClearAfterClass.stateToClear = NullnessAnalyzerTest
val noOptCompiler = NullnessAnalyzerTest.noOptCompiler
+ import noOptCompiler.genBCode.bTypes.backendUtils._
- def newNullnessAnalyzer(methodNode: MethodNode, classInternalName: InternalName = "C"): NullnessAnalyzer = {
- val nullnessAnalyzer = new NullnessAnalyzer
- nullnessAnalyzer.analyze(classInternalName, methodNode)
- nullnessAnalyzer
- }
+ def newNullnessAnalyzer(methodNode: MethodNode, classInternalName: InternalName = "C") = new AsmAnalyzer(methodNode, classInternalName, new NullnessAnalyzer(noOptCompiler.genBCode.bTypes))
- def testNullness(analyzer: NullnessAnalyzer, method: MethodNode, query: String, index: Int, nullness: Nullness): Unit = {
+ def testNullness(analyzer: AsmAnalyzer[NullnessValue], method: MethodNode, query: String, index: Int, nullness: NullnessValue): Unit = {
for (i <- findInstr(method, query)) {
- val r = analyzer.frameAt(i, method).getValue(index).nullness
+ val r = analyzer.frameAt(i).getValue(index)
assertTrue(s"Expected: $nullness, found: $r. At instr ${textify(i)}", nullness == r)
}
}
// debug / helper for writing tests
- def showAllNullnessFrames(analyzer: NullnessAnalyzer, method: MethodNode): String = {
+ def showAllNullnessFrames(analyzer: AsmAnalyzer[NullnessValue], method: MethodNode): String = {
val instrLength = method.instructions.iterator.asScala.map(textify(_).length).max
val lines = for (i <- method.instructions.iterator.asScala) yield {
- val f = analyzer.frameAt(i, method)
+ val f = analyzer.frameAt(i)
val frameString = {
if (f == null) "null"
else (0 until (f.getLocals + f.getStackSize)).iterator
@@ -71,12 +68,13 @@ class NullnessAnalyzerTest extends ClearAfterClass {
// So in the frame for `ALOAD 0`, the stack is still empty.
val res =
- """ L0: 0: NotNull
- | LINENUMBER 1 L0: 0: NotNull
- | ALOAD 0: 0: NotNull
- |INVOKEVIRTUAL java/lang/Object.toString ()Ljava/lang/String;: 0: NotNull, 1: NotNull
- | ARETURN: 0: NotNull, 1: Unknown1
- | L0: null""".stripMargin
+ """ L0: 0: NotNull
+ | LINENUMBER 1 L0: 0: NotNull
+ | ALOAD 0: 0: NotNull
+ |INVOKEVIRTUAL C.toString ()Ljava/lang/String;: 0: NotNull, 1: NotNull
+ | ARETURN: 0: NotNull, 1: Unknown1
+ | L0: null""".stripMargin
+// println(showAllNullnessFrames(newNullnessAnalyzer(m), m))
assertEquals(showAllNullnessFrames(newNullnessAnalyzer(m), m), res)
}
@@ -84,15 +82,15 @@ class NullnessAnalyzerTest extends ClearAfterClass {
def thisNonNull(): Unit = {
val List(m) = compileMethods(noOptCompiler)("def f = this.toString")
val a = newNullnessAnalyzer(m)
- testNullness(a, m, "ALOAD 0", 0, NotNull)
+ testNullness(a, m, "ALOAD 0", 0, NotNullValue)
}
@Test
def instanceMethodCall(): Unit = {
val List(m) = compileMethods(noOptCompiler)("def f(a: String) = a.trim")
val a = newNullnessAnalyzer(m)
- testNullness(a, m, "INVOKEVIRTUAL java/lang/String.trim", 1, Unknown)
- testNullness(a, m, "ARETURN", 1, NotNull)
+ testNullness(a, m, "INVOKEVIRTUAL java/lang/String.trim", 1, UnknownValue1)
+ testNullness(a, m, "ARETURN", 1, NotNullValue)
}
@Test
@@ -110,13 +108,13 @@ class NullnessAnalyzerTest extends ClearAfterClass {
// ARETURN: 0: NotNull, 1: NotNull, 2: Unknown
for ((insn, index, nullness) <- List(
- ("+NEW", 2, Unknown), // new value at slot 2 on the stack
- ("+DUP", 3, Unknown),
- ("+INVOKESPECIAL java/lang/Object", 2, NotNull), // after calling the initializer on 3, the value at 2 becomes NotNull
- ("ASTORE 1", 1, Unknown), // before the ASTORE 1, nullness of the value in local 1 is Unknown
- ("+ASTORE 1", 1, NotNull), // after storing the value at 2 in local 1, the local 1 is NotNull
- ("+ALOAD 1", 2, NotNull), // loading the value 1 puts a NotNull value on the stack (at 2)
- ("+INVOKEVIRTUAL java/lang/Object.toString", 2, Unknown) // nullness of value returned by `toString` is Unknown
+ ("+NEW", 2, UnknownValue1), // new value at slot 2 on the stack
+ ("+DUP", 3, UnknownValue1),
+ ("+INVOKESPECIAL java/lang/Object", 2, NotNullValue), // after calling the initializer on 3, the value at 2 becomes NotNull
+ ("ASTORE 1", 1, UnknownValue1), // before the ASTORE 1, nullness of the value in local 1 is Unknown
+ ("+ASTORE 1", 1, NotNullValue), // after storing the value at 2 in local 1, the local 1 is NotNull
+ ("+ALOAD 1", 2, NotNullValue), // loading the value 1 puts a NotNull value on the stack (at 2)
+ ("+INVOKEVIRTUAL java/lang/Object.toString", 2, UnknownValue1) // nullness of value returned by `toString` is Unknown
)) testNullness(a, m, insn, index, nullness)
}
@@ -125,9 +123,9 @@ class NullnessAnalyzerTest extends ClearAfterClass {
val List(m) = compileMethods(noOptCompiler)("def f = { var a: Object = null; a }")
val a = newNullnessAnalyzer(m)
for ((insn, index, nullness) <- List(
- ("+ACONST_NULL", 2, Null),
- ("+ASTORE 1", 1, Null),
- ("+ALOAD 1", 2, Null)
+ ("+ACONST_NULL", 2, NullValue),
+ ("+ASTORE 1", 1, NullValue),
+ ("+ALOAD 1", 2, NullValue)
)) testNullness(a, m, insn, index, nullness)
}
@@ -135,15 +133,33 @@ class NullnessAnalyzerTest extends ClearAfterClass {
def stringLiteralsNotNull(): Unit = {
val List(m) = compileMethods(noOptCompiler)("""def f = { val a = "hi"; a.trim }""")
val a = newNullnessAnalyzer(m)
- testNullness(a, m, "+ASTORE 1", 1, NotNull)
+ testNullness(a, m, "+ASTORE 1", 1, NotNullValue)
}
@Test
def newArraynotNull() {
val List(m) = compileMethods(noOptCompiler)("def f = { val a = new Array[Int](2); a(0) }")
val a = newNullnessAnalyzer(m)
- testNullness(a, m, "+NEWARRAY T_INT", 2, NotNull) // new array on stack
- testNullness(a, m, "+ASTORE 1", 1, NotNull) // local var (a)
+ testNullness(a, m, "+NEWARRAY T_INT", 2, NotNullValue) // new array on stack
+ testNullness(a, m, "+ASTORE 1", 1, NotNullValue) // local var (a)
+ }
+
+ @Test
+ def mergeNullNotNull(): Unit = {
+ val code =
+ """def f(o: Object) = {
+ | var a: Object = o
+ | var c: Object = null
+ | if ("".trim eq "-") {
+ | c = o
+ | }
+ | a.toString
+ |}
+ """.stripMargin
+ val List(m) = compileMethods(noOptCompiler)(code)
+ val a = newNullnessAnalyzer(m)
+ val toSt = "+INVOKEVIRTUAL java/lang/Object.toString"
+ testNullness(a, m, toSt, 3, UnknownValue1)
}
@Test
@@ -173,22 +189,22 @@ class NullnessAnalyzerTest extends ClearAfterClass {
val toSt = "INVOKEVIRTUAL java/lang/Object.toString"
val end = s"+$toSt"
for ((insn, index, nullness) <- List(
- (trim, 0, NotNull), // this
- (trim, 1, Unknown), // parameter o
- (trim, 2, Unknown), // a
- (trim, 3, Null), // b
- (trim, 4, Null), // c
- (trim, 5, Unknown), // d
-
- (toSt, 2, Unknown), // a, still the same
- (toSt, 3, Unknown), // b, was re-assinged in both branches to Unknown
- (toSt, 4, Unknown), // c, was re-assigned in one branch to Unknown
- (toSt, 5, Null), // d, was assigned to null in both branches
-
- (end, 2, NotNull), // a, NotNull (alias of b)
- (end, 3, NotNull), // b, receiver of toString
- (end, 4, Unknown), // c, no change (not an alias of b)
- (end, 5, Null) // d, no change
+ (trim, 0, NotNullValue), // this
+ (trim, 1, UnknownValue1), // parameter o
+ (trim, 2, UnknownValue1), // a
+ (trim, 3, NullValue), // b
+ (trim, 4, NullValue), // c
+ (trim, 5, UnknownValue1), // d
+
+ (toSt, 2, UnknownValue1), // a, still the same
+ (toSt, 3, UnknownValue1), // b, was re-assinged in both branches to Unknown
+ (toSt, 4, UnknownValue1), // c, was re-assigned in one branch to Unknown
+ (toSt, 5, NullValue), // d, was assigned to null in both branches
+
+ (end, 2, NotNullValue), // a, NotNull (alias of b)
+ (end, 3, NotNullValue), // b, receiver of toString
+ (end, 4, UnknownValue1), // c, no change (not an alias of b)
+ (end, 5, NullValue) // d, no change
)) testNullness(a, m, insn, index, nullness)
}
@@ -210,11 +226,11 @@ class NullnessAnalyzerTest extends ClearAfterClass {
val trim = "INVOKEVIRTUAL java/lang/String.trim"
for ((insn, index, nullness) <- List(
- (instof, 1, Unknown), // a after INSTANCEOF
- (instof, 2, Unknown), // x after INSTANCEOF
- (tost, 1, NotNull),
- (tost, 2, NotNull),
- (trim, 3, NotNull) // receiver at `trim`
+ (instof, 1, UnknownValue1), // a after INSTANCEOF
+ (instof, 2, UnknownValue1), // x after INSTANCEOF
+ (tost, 1, NotNullValue),
+ (tost, 2, NotNullValue),
+ (trim, 3, NotNullValue) // receiver at `trim`
)) testNullness(a, m, insn, index, nullness)
}
}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerTest.scala b/test/junit/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerTest.scala
index 941a167114..d54b8ac563 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerTest.scala
@@ -15,7 +15,7 @@ import CodeGenTools._
import AsmUtils._
object ProdConsAnalyzerTest extends ClearAfterClass.Clearable {
- var noOptCompiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:none")
+ var noOptCompiler = newCompiler(extraArgs = "-Yopt:l:none")
def clear(): Unit = {
noOptCompiler = null
@@ -26,6 +26,7 @@ object ProdConsAnalyzerTest extends ClearAfterClass.Clearable {
class ProdConsAnalyzerTest extends ClearAfterClass {
ClearAfterClass.stateToClear = ProdConsAnalyzerTest
val noOptCompiler = ProdConsAnalyzerTest.noOptCompiler
+ import noOptCompiler.genBCode.bTypes.backendUtils._
def prodToString(producer: AbstractInsnNode) = producer match {
case p: InitialProducer => p.toString
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/AnalyzerTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/AnalyzerTest.scala
new file mode 100644
index 0000000000..930f7f2f10
--- /dev/null
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/AnalyzerTest.scala
@@ -0,0 +1,63 @@
+package scala.tools.nsc
+package backend.jvm
+package opt
+
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.Test
+import org.junit.Assert._
+
+import scala.tools.asm.tree._
+import scala.tools.asm.tree.analysis._
+import scala.tools.nsc.backend.jvm.analysis.{AliasingFrame, AliasingAnalyzer}
+
+import CodeGenTools._
+import scala.tools.partest.ASMConverters
+import ASMConverters._
+import AsmUtils._
+import BackendReporting._
+import BytecodeUtils._
+
+import scala.collection.JavaConverters._
+import scala.tools.testing.ClearAfterClass
+
+object AnalyzerTest extends ClearAfterClass.Clearable {
+ var noOptCompiler = newCompiler(extraArgs = "-Yopt:l:none")
+ def clear(): Unit = { noOptCompiler = null }
+}
+
+@RunWith(classOf[JUnit4])
+class AnalyzerTest extends ClearAfterClass {
+ ClearAfterClass.stateToClear = AnalyzerTest
+ val noOptCompiler = AnalyzerTest.noOptCompiler
+
+ @Test
+ def aliasingOfPrimitives(): Unit = {
+ val code =
+ """class C {
+ | def f(a: Int, b: Long) = {
+ | val c = a - b // a is converted with i2l
+ | val d = c
+ | val e = a
+ | // locals: 0 this -- 1 a -- 2-3 b -- 4-5 c -- 6-7 d -- 8 e
+ | e + d // e is converted with i2l
+ | }
+ |}
+ """.stripMargin
+
+ val List(c) = compileClasses(noOptCompiler)(code)
+ val a = new AliasingAnalyzer(new BasicInterpreter)
+ val f = findAsmMethod(c, "f")
+ a.analyze("C", f)
+
+ val List(_, i2l) = findInstr(f, "I2L")
+ val aliasesAtI2l = a.frameAt(i2l, f).asInstanceOf[AliasingFrame[_]].aliases
+ assertEquals(aliasesAtI2l(1).iterator.toList, List(1, 8, 9)) // a, e and stack top
+ assertEquals(aliasesAtI2l(4).iterator.toList, List(4, 6))
+
+ val List(add) = findInstr(f, "LADD")
+ val aliasesAtAdd = a.frameAt(add, f).asInstanceOf[AliasingFrame[_]].aliases
+ assertEquals(aliasesAtAdd(1).iterator.toList, List(1, 8)) // after i2l the value on the stack is no longer an alias
+ assertEquals(aliasesAtAdd(4).iterator.toList, List(4, 6, 10)) // c, d and stack top
+ }
+}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala
index 1b6c080234..aba0aab038 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala
@@ -17,12 +17,12 @@ import ASMConverters._
import BackendReporting._
-import scala.collection.convert.decorateAsScala._
+import scala.collection.JavaConverters._
@RunWith(classOf[JUnit4])
class BTypesFromClassfileTest {
// inliner enabled -> inlineInfos are collected (and compared) in ClassBTypes
- val compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:inline-global")
+ val compiler = newCompiler(extraArgs = "-Yopt:inline-global")
import compiler._
import definitions._
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala
index 9fda034a04..1d30e42e3c 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala
@@ -6,6 +6,7 @@ import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.junit.Test
import scala.collection.generic.Clearable
+import scala.collection.immutable.IntMap
import scala.tools.asm.Opcodes._
import org.junit.Assert._
@@ -20,26 +21,57 @@ import ASMConverters._
import AsmUtils._
import BackendReporting._
-import scala.collection.convert.decorateAsScala._
+import scala.collection.JavaConverters._
+import scala.tools.testing.ClearAfterClass
-@RunWith(classOf[JUnit4])
-class CallGraphTest {
- val compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:inline-global -Yopt-warnings")
- import compiler.genBCode.bTypes._
+object CallGraphTest extends ClearAfterClass.Clearable {
+ var compiler = newCompiler(extraArgs = "-Yopt:inline-global -Yopt-warnings")
+ def clear(): Unit = { compiler = null }
// allows inspecting the caches after a compilation run
- val notPerRun: List[Clearable] = List(classBTypeFromInternalName, byteCodeRepository.classes, callGraph.callsites)
+ val notPerRun: List[Clearable] = List(
+ compiler.genBCode.bTypes.classBTypeFromInternalName,
+ compiler.genBCode.bTypes.byteCodeRepository.compilingClasses,
+ compiler.genBCode.bTypes.byteCodeRepository.parsedClasses,
+ compiler.genBCode.bTypes.callGraph.callsites)
notPerRun foreach compiler.perRunCaches.unrecordCache
+}
+
+@RunWith(classOf[JUnit4])
+class CallGraphTest extends ClearAfterClass {
+ ClearAfterClass.stateToClear = CallGraphTest
+
+ val compiler = CallGraphTest.compiler
+ import compiler.genBCode.bTypes._
+ import callGraph._
- def compile(code: String, allowMessage: StoreReporter#Info => Boolean): List[ClassNode] = {
- notPerRun.foreach(_.clear())
- compileClasses(compiler)(code, allowMessage = allowMessage)
+ def compile(code: String, allowMessage: StoreReporter#Info => Boolean = _ => false): List[ClassNode] = {
+ CallGraphTest.notPerRun.foreach(_.clear())
+ compileClasses(compiler)(code, allowMessage = allowMessage).map(c => byteCodeRepository.classNode(c.name).get)
}
def callsInMethod(methodNode: MethodNode): List[MethodInsnNode] = methodNode.instructions.iterator.asScala.collect({
case call: MethodInsnNode => call
}).toList
+ def checkCallsite(call: MethodInsnNode, callsiteMethod: MethodNode, target: MethodNode, calleeDeclClass: ClassBType,
+ safeToInline: Boolean, atInline: Boolean, atNoInline: Boolean, argInfos: IntMap[ArgInfo] = IntMap.empty) = {
+ val callsite = callGraph.callsites(callsiteMethod)(call)
+ try {
+ assert(callsite.callsiteInstruction == call)
+ assert(callsite.callsiteMethod == callsiteMethod)
+ val callee = callsite.callee.get
+ assert(callee.callee == target)
+ assert(callee.calleeDeclarationClass == calleeDeclClass)
+ assert(callee.safeToInline == safeToInline)
+ assert(callee.annotatedInline == atInline)
+ assert(callee.annotatedNoInline == atNoInline)
+ assert(callsite.argInfos == argInfos)
+ } catch {
+ case e: Throwable => println(callsite); throw e
+ }
+ }
+
@Test
def callGraphStructure(): Unit = {
val code =
@@ -83,70 +115,107 @@ class CallGraphTest {
msgCount += 1
ok exists (m.msg contains _)
}
- val List(cCls, cMod, dCls, testCls) = compile(code, checkMsg).map(c => byteCodeRepository.classNode(c.name).get)
+ val List(cCls, cMod, dCls, testCls) = compile(code, checkMsg)
assert(msgCount == 6, msgCount)
- val List(cf1, cf2, cf3, cf4, cf5, cf6, cf7) = cCls.methods.iterator.asScala.filter(_.name.startsWith("f")).toList.sortBy(_.name)
- val List(df1, df3) = dCls.methods.iterator.asScala.filter(_.name.startsWith("f")).toList.sortBy(_.name)
- val g1 = cMod.methods.iterator.asScala.find(_.name == "g1").get
- val List(t1, t2) = testCls.methods.iterator.asScala.filter(_.name.startsWith("t")).toList.sortBy(_.name)
+ val List(cf1, cf2, cf3, cf4, cf5, cf6, cf7) = findAsmMethods(cCls, _.startsWith("f"))
+ val List(df1, df3) = findAsmMethods(dCls, _.startsWith("f"))
+ val g1 = findAsmMethod(cMod, "g1")
+ val List(t1, t2) = findAsmMethods(testCls, _.startsWith("t"))
val List(cf1Call, cf2Call, cf3Call, cf4Call, cf5Call, cf6Call, cf7Call, cg1Call) = callsInMethod(t1)
val List(df1Call, df2Call, df3Call, df4Call, df5Call, df6Call, df7Call, dg1Call) = callsInMethod(t2)
- def checkCallsite(callsite: callGraph.Callsite,
- call: MethodInsnNode, callsiteMethod: MethodNode, target: MethodNode, calleeDeclClass: ClassBType,
- safeToInline: Boolean, atInline: Boolean, atNoInline: Boolean) = try {
- assert(callsite.callsiteInstruction == call)
- assert(callsite.callsiteMethod == callsiteMethod)
- val callee = callsite.callee.get
- assert(callee.callee == target)
- assert(callee.calleeDeclarationClass == calleeDeclClass)
- assert(callee.safeToInline == safeToInline)
- assert(callee.annotatedInline == atInline)
- assert(callee.annotatedNoInline == atNoInline)
-
- assert(callsite.argInfos == List()) // not defined yet
- } catch {
- case e: Throwable => println(callsite); throw e
- }
-
val cClassBType = classBTypeFromClassNode(cCls)
val cMClassBType = classBTypeFromClassNode(cMod)
val dClassBType = classBTypeFromClassNode(dCls)
- checkCallsite(callGraph.callsites(cf1Call),
- cf1Call, t1, cf1, cClassBType, false, false, false)
- checkCallsite(callGraph.callsites(cf2Call),
- cf2Call, t1, cf2, cClassBType, true, false, false)
- checkCallsite(callGraph.callsites(cf3Call),
- cf3Call, t1, cf3, cClassBType, false, true, false)
- checkCallsite(callGraph.callsites(cf4Call),
- cf4Call, t1, cf4, cClassBType, true, true, false)
- checkCallsite(callGraph.callsites(cf5Call),
- cf5Call, t1, cf5, cClassBType, false, false, true)
- checkCallsite(callGraph.callsites(cf6Call),
- cf6Call, t1, cf6, cClassBType, true, false, true)
- checkCallsite(callGraph.callsites(cf7Call),
- cf7Call, t1, cf7, cClassBType, false, true, true)
- checkCallsite(callGraph.callsites(cg1Call),
- cg1Call, t1, g1, cMClassBType, true, false, false)
-
- checkCallsite(callGraph.callsites(df1Call),
- df1Call, t2, df1, dClassBType, false, true, false)
- checkCallsite(callGraph.callsites(df2Call),
- df2Call, t2, cf2, cClassBType, true, false, false)
- checkCallsite(callGraph.callsites(df3Call),
- df3Call, t2, df3, dClassBType, true, false, false)
- checkCallsite(callGraph.callsites(df4Call),
- df4Call, t2, cf4, cClassBType, true, true, false)
- checkCallsite(callGraph.callsites(df5Call),
- df5Call, t2, cf5, cClassBType, false, false, true)
- checkCallsite(callGraph.callsites(df6Call),
- df6Call, t2, cf6, cClassBType, true, false, true)
- checkCallsite(callGraph.callsites(df7Call),
- df7Call, t2, cf7, cClassBType, false, true, true)
- checkCallsite(callGraph.callsites(dg1Call),
- dg1Call, t2, g1, cMClassBType, true, false, false)
+ checkCallsite(cf1Call, t1, cf1, cClassBType, false, false, false)
+ checkCallsite(cf2Call, t1, cf2, cClassBType, true, false, false)
+ checkCallsite(cf3Call, t1, cf3, cClassBType, false, true, false)
+ checkCallsite(cf4Call, t1, cf4, cClassBType, true, true, false)
+ checkCallsite(cf5Call, t1, cf5, cClassBType, false, false, true)
+ checkCallsite(cf6Call, t1, cf6, cClassBType, true, false, true)
+ checkCallsite(cf7Call, t1, cf7, cClassBType, false, true, true)
+ checkCallsite(cg1Call, t1, g1, cMClassBType, true, false, false)
+
+ checkCallsite(df1Call, t2, df1, dClassBType, false, true, false)
+ checkCallsite(df2Call, t2, cf2, cClassBType, true, false, false)
+ checkCallsite(df3Call, t2, df3, dClassBType, true, false, false)
+ checkCallsite(df4Call, t2, cf4, cClassBType, true, true, false)
+ checkCallsite(df5Call, t2, cf5, cClassBType, false, false, true)
+ checkCallsite(df6Call, t2, cf6, cClassBType, true, false, true)
+ checkCallsite(df7Call, t2, cf7, cClassBType, false, true, true)
+ checkCallsite(dg1Call, t2, g1, cMClassBType, true, false, false)
+ }
+
+ @Test
+ def callerSensitiveNotSafeToInline(): Unit = {
+ val code =
+ """class C {
+ | def m = java.lang.Class.forName("C")
+ |}
+ """.stripMargin
+ val List(c) = compile(code)
+ val m = findAsmMethod(c, "m")
+ val List(fn) = callsInMethod(m)
+ val forNameMeth = byteCodeRepository.methodNode("java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;").get._1
+ val classTp = classBTypeFromInternalName("java/lang/Class")
+ val r = callGraph.callsites(m)(fn)
+ checkCallsite(fn, m, forNameMeth, classTp, safeToInline = false, atInline = false, atNoInline = false)
+ }
+
+ @Test
+ def checkArgInfos(): Unit = {
+ val code =
+ """abstract class C {
+ | def h(f: Int => Int): Int = f(1)
+ | def t1 = h(x => x + 1)
+ | def t2(i: Int, f: Int => Int, z: Int) = h(f) + i - z
+ | def t3(f: Int => Int) = h(x => f(x + 1))
+ |}
+ |trait D {
+ | def iAmASam(x: Int): Int
+ | def selfSamCall = iAmASam(10)
+ |}
+ |""".stripMargin
+ val List(c, d) = compile(code)
+
+ def callIn(m: String) = callGraph.callsites.find(_._1.name == m).get._2.values.head
+ val t1h = callIn("t1")
+ assertEquals(t1h.argInfos.toList, List((1, FunctionLiteral)))
+
+ val t2h = callIn("t2")
+ assertEquals(t2h.argInfos.toList, List((1, ForwardedParam(2))))
+
+ val t3h = callIn("t3")
+ assertEquals(t3h.argInfos.toList, List((1, FunctionLiteral)))
+
+ val selfSamCall = callIn("selfSamCall")
+ assertEquals(selfSamCall.argInfos.toList, List((0,ForwardedParam(0))))
+ }
+
+ @Test
+ def argInfoAfterInlining(): Unit = {
+ val code =
+ """class C {
+ | def foo(f: Int => Int) = f(1) // not inlined
+ | @inline final def bar(g: Int => Int) = foo(g) // forwarded param 1
+ | @inline final def baz = foo(x => x + 1) // literal
+ |
+ | def t1 = bar(x => x + 1) // call to foo should have argInfo literal
+ | def t2(x: Int, f: Int => Int) = x + bar(f) // call to foo should have argInfo forwarded param 2
+ | def t3 = baz // call to foo should have argInfo literal
+ | def someFun: Int => Int = null
+ | def t4(x: Int) = x + bar(someFun) // call to foo has empty argInfo
+ |}
+ """.stripMargin
+
+ compile(code)
+ def callIn(m: String) = callGraph.callsites.find(_._1.name == m).get._2.values.head
+ assertEquals(callIn("t1").argInfos.toList, List((1, FunctionLiteral)))
+ assertEquals(callIn("t2").argInfos.toList, List((1, ForwardedParam(2))))
+ assertEquals(callIn("t3").argInfos.toList, List((1, FunctionLiteral)))
+ assertEquals(callIn("t4").argInfos.toList, Nil)
}
}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/ClosureOptimizerTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/ClosureOptimizerTest.scala
new file mode 100644
index 0000000000..12bfba71a8
--- /dev/null
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/ClosureOptimizerTest.scala
@@ -0,0 +1,108 @@
+package scala.tools.nsc
+package backend.jvm
+package opt
+
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.Test
+import scala.collection.generic.Clearable
+import scala.collection.mutable.ListBuffer
+import scala.reflect.internal.util.BatchSourceFile
+import scala.tools.asm.Opcodes._
+import org.junit.Assert._
+
+import scala.tools.asm.tree._
+import scala.tools.asm.tree.analysis._
+import scala.tools.nsc.io._
+import scala.tools.nsc.reporters.StoreReporter
+import scala.tools.testing.AssertUtil._
+
+import CodeGenTools._
+import scala.tools.partest.ASMConverters
+import ASMConverters._
+import AsmUtils._
+
+import BackendReporting._
+
+import scala.collection.JavaConverters._
+import scala.tools.testing.ClearAfterClass
+
+object ClosureOptimizerTest extends ClearAfterClass.Clearable {
+ var compiler = newCompiler(extraArgs = "-Yopt:l:classpath -Yopt-warnings:_")
+ def clear(): Unit = { compiler = null }
+}
+
+@RunWith(classOf[JUnit4])
+class ClosureOptimizerTest extends ClearAfterClass {
+ ClearAfterClass.stateToClear = ClosureOptimizerTest
+
+ val compiler = ClosureOptimizerTest.compiler
+
+ @Test
+ def nothingTypedClosureBody(): Unit = {
+ val code =
+ """abstract class C {
+ | def isEmpty: Boolean
+ | @inline final def getOrElse[T >: C](f: => T) = if (isEmpty) f else this
+ | def t = getOrElse(throw new Error(""))
+ |}
+ """.stripMargin
+
+ val List(c) = compileClasses(compiler)(code)
+ val t = findAsmMethod(c, "t")
+ val List(bodyCall) = findInstr(t, "INVOKESTATIC C.C$$$anonfun$1 ()Lscala/runtime/Nothing$")
+ assert(bodyCall.getNext.getOpcode == ATHROW)
+ }
+
+ @Test
+ def nullTypedClosureBody(): Unit = {
+ val code =
+ """abstract class C {
+ | def isEmpty: Boolean
+ | @inline final def getOrElse[T >: C](f: => T) = if (isEmpty) f else this
+ | def t = getOrElse(null)
+ |}
+ """.stripMargin
+
+ val List(c) = compileClasses(compiler)(code)
+ val t = findAsmMethod(c, "t")
+ val List(bodyCall) = findInstr(t, "INVOKESTATIC C.C$$$anonfun$1 ()Lscala/runtime/Null$")
+ assert(bodyCall.getNext.getOpcode == POP)
+ assert(bodyCall.getNext.getNext.getOpcode == ACONST_NULL)
+ }
+
+ @Test
+ def makeLMFCastExplicit(): Unit = {
+ val code =
+ """class C {
+ | def t(l: List[String]) = {
+ | val fun: String => String = s => s
+ | fun(l.head)
+ | }
+ |}
+ """.stripMargin
+ val List(c) = compileClasses(compiler)(code)
+ assertSameCode(getSingleMethod(c, "t"),
+ List(VarOp(ALOAD, 1), Invoke(INVOKEVIRTUAL, "scala/collection/immutable/List", "head", "()Ljava/lang/Object;", false),
+ TypeOp(CHECKCAST, "java/lang/String"), Invoke(INVOKESTATIC, "C", "C$$$anonfun$1", "(Ljava/lang/String;)Ljava/lang/String;", false),
+ Op(ARETURN)))
+ }
+
+ @Test
+ def closureOptWithUnreachableCode(): Unit = {
+ // this example used to crash the ProdCons analysis in the closure optimizer - ProdCons
+ // expects no unreachable code.
+ val code =
+ """class C {
+ | @inline final def m = throw new Error("")
+ | def t = {
+ | val f = (x: Int) => x + 1
+ | m
+ | f(10) // unreachable after inlining m
+ | }
+ |}
+ """.stripMargin
+ val List(c) = compileClasses(compiler)(code)
+ assertSameSummary(getSingleMethod(c, "t"), List(NEW, DUP, LDC, "<init>", ATHROW))
+ }
+}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala
index 76492cfa23..ac1b759fe2 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala
@@ -17,8 +17,8 @@ class CompactLocalVariablesTest {
// recurse-unreachable-jumps is required for eliminating catch blocks, in the first dce round they
// are still live.only after eliminating the empty handler the catch blocks become unreachable.
- val methodOptCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:unreachable-code,compact-locals")
- val noCompactVarsCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:unreachable-code")
+ val methodOptCompiler = newCompiler(extraArgs = "-Yopt:unreachable-code,compact-locals")
+ val noCompactVarsCompiler = newCompiler(extraArgs = "-Yopt:unreachable-code")
@Test
def compactUnused(): Unit = {
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala
index cb01f3d164..22aed4207f 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala
@@ -14,8 +14,8 @@ import ASMConverters._
import scala.tools.testing.ClearAfterClass
object EmptyExceptionHandlersTest extends ClearAfterClass.Clearable {
- var noOptCompiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:none")
- var dceCompiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:unreachable-code")
+ var noOptCompiler = newCompiler(extraArgs = "-Yopt:l:none")
+ var dceCompiler = newCompiler(extraArgs = "-Yopt:unreachable-code")
def clear(): Unit = {
noOptCompiler = null
dceCompiler = null
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala
index 57088bdd2f..23386bb5ae 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala
@@ -16,18 +16,23 @@ import scala.tools.testing.ClearAfterClass
import BackendReporting._
-import scala.collection.convert.decorateAsScala._
+import scala.collection.JavaConverters._
object InlineInfoTest extends ClearAfterClass.Clearable {
- var compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:classpath")
+ var compiler = newCompiler(extraArgs = "-Yopt:l:classpath")
def clear(): Unit = { compiler = null }
- def notPerRun: List[Clearable] = List(compiler.genBCode.bTypes.classBTypeFromInternalName, compiler.genBCode.bTypes.byteCodeRepository.classes)
+ def notPerRun: List[Clearable] = List(
+ compiler.genBCode.bTypes.classBTypeFromInternalName,
+ compiler.genBCode.bTypes.byteCodeRepository.compilingClasses,
+ compiler.genBCode.bTypes.byteCodeRepository.parsedClasses)
notPerRun foreach compiler.perRunCaches.unrecordCache
}
@RunWith(classOf[JUnit4])
-class InlineInfoTest {
+class InlineInfoTest extends ClearAfterClass {
+ ClearAfterClass.stateToClear = InlineInfoTest
+
val compiler = InlineInfoTest.compiler
def compile(code: String) = {
@@ -55,6 +60,7 @@ class InlineInfoTest {
|class C extends T with U
""".stripMargin
val classes = compile(code)
+
val fromSyms = classes.map(c => compiler.genBCode.bTypes.classBTypeFromInternalName(c.name).info.get.inlineInfo)
val fromAttrs = classes.map(c => {
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala
index 029caa995c..1597c75a7e 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala
@@ -13,7 +13,6 @@ import org.junit.Assert._
import scala.tools.asm.tree._
import scala.tools.asm.tree.analysis._
-import scala.tools.nsc.backend.jvm.opt.BytecodeUtils.AsmAnalyzer
import scala.tools.nsc.io._
import scala.tools.nsc.reporters.StoreReporter
import scala.tools.testing.AssertUtil._
@@ -25,14 +24,15 @@ import AsmUtils._
import BackendReporting._
-import scala.collection.convert.decorateAsScala._
+import scala.collection.JavaConverters._
import scala.tools.testing.ClearAfterClass
object InlineWarningTest extends ClearAfterClass.Clearable {
- val argsNoWarn = "-Ybackend:GenBCode -Yopt:l:classpath"
+ val argsNoWarn = "-Yopt:l:classpath"
val args = argsNoWarn + " -Yopt-warnings"
var compiler = newCompiler(extraArgs = args)
- def clear(): Unit = { compiler = null }
+ var compilerWarnAll = newCompiler(extraArgs = argsNoWarn + " -Yopt-warnings:_")
+ def clear(): Unit = { compiler = null; compilerWarnAll = null }
}
@RunWith(classOf[JUnit4])
@@ -40,8 +40,9 @@ class InlineWarningTest extends ClearAfterClass {
ClearAfterClass.stateToClear = InlineWarningTest
val compiler = InlineWarningTest.compiler
+ val compilerWarnAll = InlineWarningTest.compilerWarnAll
- def compile(scalaCode: String, javaCode: List[(String, String)] = Nil, allowMessage: StoreReporter#Info => Boolean = _ => false): List[ClassNode] = {
+ def compile(scalaCode: String, javaCode: List[(String, String)] = Nil, allowMessage: StoreReporter#Info => Boolean = _ => false, compiler: Global = compiler): List[ClassNode] = {
compileClasses(compiler)(scalaCode, javaCode, allowMessage)
}
@@ -70,29 +71,6 @@ class InlineWarningTest extends ClearAfterClass {
}
@Test
- def traitMissingImplClass(): Unit = {
- val codeA = "trait T { @inline final def f = 1 }"
- val codeB = "class C { def t1(t: T) = t.f }"
-
- val removeImpl = (outDir: AbstractFile) => {
- val f = outDir.lookupName("T$class.class", directory = false)
- if (f != null) f.delete()
- }
-
- val warn =
- """T::f()I is annotated @inline but cannot be inlined: the trait method call could not be rewritten to the static implementation method. Possible reason:
- |The method f(LT;)I could not be found in the class T$class or any of its parents.
- |Note that the following parent classes could not be found on the classpath: T$class""".stripMargin
-
- var c = 0
- compileSeparately(List(codeA, codeB), extraArgs = InlineWarningTest.args, afterEach = removeImpl, allowMessage = i => {c += 1; i.msg contains warn})
- assert(c == 1, c)
-
- // only summary here
- compileSeparately(List(codeA, codeB), extraArgs = InlineWarningTest.argsNoWarn, afterEach = removeImpl, allowMessage = _.msg contains "there was one inliner warning")
- }
-
- @Test
def handlerNonEmptyStack(): Unit = {
val code =
"""class C {
@@ -125,12 +103,12 @@ class InlineWarningTest extends ClearAfterClass {
val warns = List(
"""failed to determine if bar should be inlined:
|The method bar()I could not be found in the class A or any of its parents.
- |Note that the following parent classes are defined in Java sources (mixed compilation), no bytecode is available: A""".stripMargin,
+ |Note that the parent class A is defined in a Java source (mixed compilation), no bytecode is available.""".stripMargin,
"""B::flop()I is annotated @inline but could not be inlined:
|Failed to check if B::flop()I can be safely inlined to B without causing an IllegalAccessError. Checking instruction INVOKESTATIC A.bar ()I failed:
|The method bar()I could not be found in the class A or any of its parents.
- |Note that the following parent classes are defined in Java sources (mixed compilation), no bytecode is available: A""".stripMargin)
+ |Note that the parent class A is defined in a Java source (mixed compilation), no bytecode is available.""".stripMargin)
var c = 0
val List(b) = compile(scalaCode, List((javaCode, "A.java")), allowMessage = i => {c += 1; warns.tail.exists(i.msg contains _)})
@@ -172,6 +150,33 @@ class InlineWarningTest extends ClearAfterClass {
}
@Test
+ def dontWarnWhenNotIlnineAnnotated(): Unit = {
+ val code =
+ """class M {
+ | final def f(t: Int => Int) = {
+ | @noinline def nested = 0
+ | nested + t(1)
+ | }
+ | def t = f(x => x + 1)
+ |}
+ |
+ |class N {
+ | def t(a: M) = a.f(x => x + 1)
+ |}
+ """.stripMargin
+ compile(code, allowMessage = _ => false) // no warnings allowed
+
+ val warn =
+ """M::f(Lscala/Function1;)I could not be inlined:
+ |The callee M::f(Lscala/Function1;)I contains the instruction INVOKESPECIAL M.nested$1 ()I
+ |that would cause an IllegalAccessError when inlined into class N""".stripMargin
+
+ var c = 0
+ compile(code, compiler = compilerWarnAll, allowMessage = i => { c += 1; i.msg contains warn })
+ assert(c == 1, c)
+ }
+
+ @Test
def cannotMixStrictfp(): Unit = {
val code =
"""import annotation.strictfp
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala
index 7ed0e13226..6460158e71 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala
@@ -16,11 +16,11 @@ import scala.tools.partest.ASMConverters
import ASMConverters._
import AsmUtils._
-import scala.collection.convert.decorateAsScala._
+import scala.collection.JavaConverters._
import scala.tools.testing.ClearAfterClass
object InlinerIllegalAccessTest extends ClearAfterClass.Clearable {
- var compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:none")
+ var compiler = newCompiler(extraArgs = "-Yopt:l:none")
def clear(): Unit = { compiler = null }
}
@@ -67,7 +67,7 @@ class InlinerIllegalAccessTest extends ClearAfterClass {
check(dClass, assertEmpty)
check(eClass, assertEmpty) // C is public, so accessible in E
- byteCodeRepository.classes.clear()
+ byteCodeRepository.parsedClasses.clear()
classBTypeFromInternalName.clear()
cClass.access &= ~ACC_PUBLIC // ftw
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala
index 5c9bd1c188..075513a2b7 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala
@@ -13,16 +13,15 @@ import scala.tools.partest.ASMConverters
import ASMConverters._
import AsmUtils._
-import scala.collection.convert.decorateAsScala._
+import scala.collection.JavaConverters._
object InlinerSeparateCompilationTest {
- val args = "-Ybackend:GenBCode -Yopt:l:classpath"
+ val args = "-Yopt:l:classpath"
}
@RunWith(classOf[JUnit4])
class InlinerSeparateCompilationTest {
import InlinerSeparateCompilationTest._
- import InlinerTest.{listStringLines, assertInvoke, assertNoInvoke}
@Test
def inlnieMixedinMember(): Unit = {
@@ -44,7 +43,7 @@ class InlinerSeparateCompilationTest {
""".stripMargin
val warn = "T::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden"
- val List(c, o, oMod, t, tCls) = compileClassesSeparately(List(codeA, codeB), args + " -Yopt-warnings", _.msg contains warn)
+ val List(c, o, oMod, t) = compileClassesSeparately(List(codeA, codeB), args + " -Yopt-warnings", _.msg contains warn)
assertInvoke(getSingleMethod(c, "t1"), "T", "f")
assertNoInvoke(getSingleMethod(c, "t2"))
assertNoInvoke(getSingleMethod(c, "t3"))
@@ -64,7 +63,7 @@ class InlinerSeparateCompilationTest {
|}
""".stripMargin
- val List(c, t, tCls) = compileClassesSeparately(List(codeA, codeB), args)
+ val List(c, t) = compileClassesSeparately(List(codeA, codeB), args)
assertNoInvoke(getSingleMethod(c, "t1"))
}
@@ -87,7 +86,7 @@ class InlinerSeparateCompilationTest {
|}
""".stripMargin
- val List(c, t, tCls, u, uCls) = compileClassesSeparately(List(codeA, codeB), args)
+ val List(c, t, u) = compileClassesSeparately(List(codeA, codeB), args)
for (m <- List("t1", "t2", "t3")) assertNoInvoke(getSingleMethod(c, m))
}
@@ -108,8 +107,8 @@ class InlinerSeparateCompilationTest {
|$assembly
""".stripMargin
- val List(a, aCls, t, tCls) = compileClassesSeparately(List(codeA, assembly), args)
- assertNoInvoke(getSingleMethod(tCls, "f"))
- assertNoInvoke(getSingleMethod(aCls, "n"))
+ val List(a, t) = compileClassesSeparately(List(codeA, assembly), args)
+ assertNoInvoke(getSingleMethod(t, "f"))
+ assertNoInvoke(getSingleMethod(a, "n"))
}
}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala
index 0309bb97cc..e2a495fb2b 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala
@@ -6,17 +6,11 @@ import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.junit.Test
import scala.collection.generic.Clearable
-import scala.collection.mutable.ListBuffer
-import scala.reflect.internal.util.BatchSourceFile
import scala.tools.asm.Opcodes._
import org.junit.Assert._
import scala.tools.asm.tree._
-import scala.tools.asm.tree.analysis._
-import scala.tools.nsc.backend.jvm.opt.BytecodeUtils.AsmAnalyzer
-import scala.tools.nsc.io._
import scala.tools.nsc.reporters.StoreReporter
-import scala.tools.testing.AssertUtil._
import CodeGenTools._
import scala.tools.partest.ASMConverters
@@ -25,49 +19,43 @@ import AsmUtils._
import BackendReporting._
-import scala.collection.convert.decorateAsScala._
+import scala.collection.JavaConverters._
import scala.tools.testing.ClearAfterClass
object InlinerTest extends ClearAfterClass.Clearable {
- val args = "-Ybackend:GenBCode -Yopt:l:classpath -Yopt-warnings"
+ val args = "-Yopt:l:classpath -Yopt-warnings"
var compiler = newCompiler(extraArgs = args)
+ var inlineOnlyCompiler = newCompiler(extraArgs = "-Yopt:inline-project")
// allows inspecting the caches after a compilation run
- def notPerRun: List[Clearable] = List(compiler.genBCode.bTypes.classBTypeFromInternalName, compiler.genBCode.bTypes.byteCodeRepository.classes, compiler.genBCode.bTypes.callGraph.callsites)
+ def notPerRun: List[Clearable] = List(
+ compiler.genBCode.bTypes.classBTypeFromInternalName,
+ compiler.genBCode.bTypes.byteCodeRepository.compilingClasses,
+ compiler.genBCode.bTypes.byteCodeRepository.parsedClasses,
+ compiler.genBCode.bTypes.callGraph.callsites)
notPerRun foreach compiler.perRunCaches.unrecordCache
- def clear(): Unit = { compiler = null }
-
- implicit class listStringLines[T](val l: List[T]) extends AnyVal {
- def stringLines = l.mkString("\n")
- }
-
- def assertNoInvoke(m: Method): Unit = assertNoInvoke(m.instructions)
- def assertNoInvoke(ins: List[Instruction]): Unit = {
- assert(!ins.exists(_.isInstanceOf[Invoke]), ins.stringLines)
- }
-
- def assertInvoke(m: Method, receiver: String, method: String): Unit = assertInvoke(m.instructions, receiver, method)
- def assertInvoke(l: List[Instruction], receiver: String, method: String): Unit = {
- assert(l.exists {
- case Invoke(_, `receiver`, `method`, _, _) => true
- case _ => false
- }, l.stringLines)
- }
+ def clear(): Unit = { compiler = null; inlineOnlyCompiler = null }
}
@RunWith(classOf[JUnit4])
class InlinerTest extends ClearAfterClass {
ClearAfterClass.stateToClear = InlinerTest
- import InlinerTest.{listStringLines, assertInvoke, assertNoInvoke}
-
val compiler = InlinerTest.compiler
import compiler.genBCode.bTypes._
+ import compiler.genBCode.bTypes.backendUtils._
+ import inlinerHeuristics._
+
+ val inlineOnlyCompiler = InlinerTest.inlineOnlyCompiler
def compile(scalaCode: String, javaCode: List[(String, String)] = Nil, allowMessage: StoreReporter#Info => Boolean = _ => false): List[ClassNode] = {
InlinerTest.notPerRun.foreach(_.clear())
compileClasses(compiler)(scalaCode, javaCode, allowMessage)
+ // Use the class nodes stored in the byteCodeRepository. The ones returned by compileClasses are not the same,
+ // these are created new from the classfile byte array. They are completely separate instances which cannot
+ // be used to look up methods / callsites in the callGraph hash maps for example.
+ byteCodeRepository.compilingClasses.valuesIterator.toList.sortBy(_.name)
}
def checkCallsite(callsite: callGraph.Callsite, callee: MethodNode) = {
@@ -79,27 +67,25 @@ class InlinerTest extends ClearAfterClass {
assert(callsite.callee.get.callee == callee, callsite.callee.get.callee.name)
}
- // inline first invocation of f into g in class C
- def inlineTest(code: String, mod: ClassNode => Unit = _ => ()): (MethodNode, Option[CannotInlineWarning]) = {
- val List(cls) = compile(code)
- mod(cls)
- val clsBType = classBTypeFromParsedClassfile(cls.name)
+ def getCallsite(method: MethodNode, calleeName: String) = callGraph.callsites(method).valuesIterator.find(_.callee.get.callee.name == calleeName).get
- val List(f, g) = cls.methods.asScala.filter(m => Set("f", "g")(m.name)).toList.sortBy(_.name)
- val fCall = g.instructions.iterator.asScala.collect({ case i: MethodInsnNode if i.name == "f" => i }).next()
+ def gMethAndFCallsite(code: String, mod: ClassNode => Unit = _ => ()) = {
+ val List(c) = compile(code)
+ mod(c)
+ val gMethod = findAsmMethod(c, "g")
+ val fCall = getCallsite(gMethod, "f")
+ (gMethod, fCall)
+ }
- val analyzer = new AsmAnalyzer(g, clsBType.internalName)
+ def canInlineTest(code: String, mod: ClassNode => Unit = _ => ()): Option[OptimizerWarning] = {
+ val cs = gMethAndFCallsite(code, mod)._2
+ inliner.earlyCanInlineCheck(cs) orElse inliner.canInlineBody(cs)
+ }
- val r = inliner.inline(
- fCall,
- analyzer.frameAt(fCall).getStackSize,
- g,
- clsBType,
- f,
- clsBType,
- receiverKnownNotNull = true,
- keepLineNumbers = true)
- (g, r)
+ def inlineTest(code: String, mod: ClassNode => Unit = _ => ()): MethodNode = {
+ val (gMethod, fCall) = gMethAndFCallsite(code, mod)
+ inliner.inline(InlineRequest(fCall, Nil))
+ gMethod
}
@Test
@@ -111,10 +97,10 @@ class InlinerTest extends ClearAfterClass {
|}
""".stripMargin
- val (g, _) = inlineTest(code)
+ val g = inlineTest(code)
val gConv = convertMethod(g)
- assertSameCode(gConv.instructions.dropNonOp,
+ assertSameCode(gConv,
List(
VarOp(ALOAD, 0), VarOp(ASTORE, 1), // store this
Op(ICONST_1), VarOp(ISTORE, 2), Jump(GOTO, Label(10)), // store return value
@@ -145,16 +131,23 @@ class InlinerTest extends ClearAfterClass {
// See also discussion around ATHROW in BCodeBodyBuilder
- val (g, _) = inlineTest(code)
- val expectedInlined = List(
- VarOp(ALOAD, 0), VarOp(ASTORE, 1), // store this
- Field(GETSTATIC, "scala/Predef$", "MODULE$", "Lscala/Predef$;"), Invoke(INVOKEVIRTUAL, "scala/Predef$", "$qmark$qmark$qmark", "()Lscala/runtime/Nothing$;", false)) // inlined call to ???
+ val g = inlineTest(code)
- assertSameCode(convertMethod(g).instructions.dropNonOp.take(4), expectedInlined)
+ val invokeQQQ = List(
+ Field(GETSTATIC, "scala/Predef$", "MODULE$", "Lscala/Predef$;"),
+ Invoke(INVOKEVIRTUAL, "scala/Predef$", "$qmark$qmark$qmark", "()Lscala/runtime/Nothing$;", false))
+
+ val gBeforeLocalOpt = VarOp(ALOAD, 0) :: VarOp(ASTORE, 1) :: invokeQQQ ::: List(
+ VarOp(ASTORE, 2),
+ Jump(GOTO, Label(11)),
+ Label(11),
+ VarOp(ALOAD, 2),
+ Op(ATHROW))
+
+ assertSameCode(convertMethod(g), gBeforeLocalOpt)
compiler.genBCode.bTypes.localOpt.methodOptimizations(g, "C")
- assertSameCode(convertMethod(g).instructions.dropNonOp,
- expectedInlined ++ List(VarOp(ASTORE, 2), VarOp(ALOAD, 2), Op(ATHROW)))
+ assertSameCode(convertMethod(g), invokeQQQ :+ Op(ATHROW))
}
@Test
@@ -166,11 +159,11 @@ class InlinerTest extends ClearAfterClass {
|}
""".stripMargin
- val (_, can) = inlineTest(code, cls => {
+ val can = canInlineTest(code, cls => {
val f = cls.methods.asScala.find(_.name == "f").get
f.access |= ACC_SYNCHRONIZED
})
- assert(can.get.isInstanceOf[SynchronizedMethod], can)
+ assert(can.nonEmpty && can.get.isInstanceOf[SynchronizedMethod], can)
}
@Test
@@ -181,7 +174,7 @@ class InlinerTest extends ClearAfterClass {
| def g = f + 1
|}
""".stripMargin
- val (_, r) = inlineTest(code)
+ val r = canInlineTest(code)
assert(r.isEmpty, r)
}
@@ -195,8 +188,8 @@ class InlinerTest extends ClearAfterClass {
| def g = println(f)
|}
""".stripMargin
- val (_, r) = inlineTest(code)
- assert(r.get.isInstanceOf[MethodWithHandlerCalledOnNonEmptyStack], r)
+ val r = canInlineTest(code)
+ assert(r.nonEmpty && r.get.isInstanceOf[MethodWithHandlerCalledOnNonEmptyStack], r)
}
@Test
@@ -216,29 +209,10 @@ class InlinerTest extends ClearAfterClass {
""".stripMargin
val List(c, d) = compile(code)
-
- val cTp = classBTypeFromParsedClassfile(c.name)
- val dTp = classBTypeFromParsedClassfile(d.name)
-
- val g = c.methods.asScala.find(_.name == "g").get
- val h = d.methods.asScala.find(_.name == "h").get
- val gCall = h.instructions.iterator.asScala.collect({
- case m: MethodInsnNode if m.name == "g" => m
- }).next()
-
- val analyzer = new AsmAnalyzer(h, dTp.internalName)
-
- val r = inliner.inline(
- gCall,
- analyzer.frameAt(gCall).getStackSize,
- h,
- dTp,
- g,
- cTp,
- receiverKnownNotNull = true,
- keepLineNumbers = true)
-
- assert(r.get.isInstanceOf[IllegalAccessInstruction], r)
+ val hMeth = findAsmMethod(d, "h")
+ val gCall = getCallsite(hMeth, "g")
+ val r = inliner.canInlineBody(gCall)
+ assert(r.nonEmpty && r.get.isInstanceOf[IllegalAccessInstruction], r)
}
@Test
@@ -273,7 +247,7 @@ class InlinerTest extends ClearAfterClass {
assert(gIns contains invokeG, gIns) // f is inlined into g, g invokes itself recursively
assert(callGraph.callsites.size == 3, callGraph.callsites)
- for (callsite <- callGraph.callsites.values if methods.contains(callsite.callsiteMethod)) {
+ for (callsite <- callGraph.callsites.valuesIterator.flatMap(_.valuesIterator) if methods.contains(callsite.callsiteMethod)) {
checkCallsite(callsite, g)
}
}
@@ -295,8 +269,8 @@ class InlinerTest extends ClearAfterClass {
assert(gIns.count(_ == invokeG) == 2, gIns)
assert(hIns.count(_ == invokeG) == 2, hIns)
- assert(callGraph.callsites.size == 7, callGraph.callsites)
- for (callsite <- callGraph.callsites.values if methods.contains(callsite.callsiteMethod)) {
+ assert(callGraph.callsites.valuesIterator.flatMap(_.valuesIterator).size == 7, callGraph.callsites)
+ for (callsite <- callGraph.callsites.valuesIterator.flatMap(_.valuesIterator) if methods.contains(callsite.callsiteMethod)) {
checkCallsite(callsite, g)
}
}
@@ -336,7 +310,7 @@ class InlinerTest extends ClearAfterClass {
|}
""".stripMargin
val List(c) = compile(code)
- assert(callGraph.callsites.values exists (_.callsiteInstruction.name == "clone"))
+ assert(callGraph.callsites.valuesIterator.flatMap(_.valuesIterator) exists (_.callsiteInstruction.name == "clone"))
}
@Test
@@ -349,7 +323,7 @@ class InlinerTest extends ClearAfterClass {
| def g(t: T) = t.f
|}
""".stripMargin
- val List(c, t, tClass) = compile(code)
+ val List(c, t) = compile(code)
assertNoInvoke(getSingleMethod(c, "g"))
}
@@ -375,26 +349,14 @@ class InlinerTest extends ClearAfterClass {
""".stripMargin
val List(c) = compile(code)
- val f = c.methods.asScala.find(_.name == "f").get
- val callsiteIns = f.instructions.iterator().asScala.collect({ case c: MethodInsnNode => c }).next()
- val clsBType = classBTypeFromParsedClassfile(c.name)
- val analyzer = new AsmAnalyzer(f, clsBType.internalName)
-
- val integerClassBType = classBTypeFromInternalName("java/lang/Integer")
- val lowestOneBitMethod = byteCodeRepository.methodNode(integerClassBType.internalName, "lowestOneBit", "(I)I").get._1
-
- val r = inliner.inline(
- callsiteIns,
- analyzer.frameAt(callsiteIns).getStackSize,
- f,
- clsBType,
- lowestOneBitMethod,
- integerClassBType,
- receiverKnownNotNull = false,
- keepLineNumbers = false)
+ val fMeth = findAsmMethod(c, "f")
+ val call = getCallsite(fMeth, "lowestOneBit")
- assert(r.isEmpty, r)
- val ins = instructionsFromMethod(f)
+ val warning = inliner.canInlineBody(call)
+ assert(warning.isEmpty, warning)
+
+ inliner.inline(InlineRequest(call, Nil))
+ val ins = instructionsFromMethod(fMeth)
// no invocations, lowestOneBit is inlined
assertNoInvoke(ins)
@@ -425,7 +387,8 @@ class InlinerTest extends ClearAfterClass {
|}
""".stripMargin
- val List(c) = compile(code)
+ // use a compiler without local optimizations (cleanups)
+ val List(c) = compileClasses(inlineOnlyCompiler)(code)
val ms @ List(f1, f2, g1, g2) = c.methods.asScala.filter(_.name.length == 2).toList
// stack height at callsite of f1 is 1, so max of g1 after inlining is max of f1 + 1
@@ -465,7 +428,7 @@ class InlinerTest extends ClearAfterClass {
"""B::flop()I is annotated @inline but could not be inlined:
|Failed to check if B::flop()I can be safely inlined to B without causing an IllegalAccessError. Checking instruction INVOKESTATIC A.bar ()I failed:
|The method bar()I could not be found in the class A or any of its parents.
- |Note that the following parent classes are defined in Java sources (mixed compilation), no bytecode is available: A""".stripMargin
+ |Note that the parent class A is defined in a Java source (mixed compilation), no bytecode is available.""".stripMargin
var c = 0
val List(b) = compile(scalaCode, List((javaCode, "A.java")), allowMessage = i => {c += 1; i.msg contains warn})
@@ -488,26 +451,13 @@ class InlinerTest extends ClearAfterClass {
| def t2(c: C) = c.f
|}
""".stripMargin
- val List(c, t, tClass) = compile(code)
+ val List(c, t) = compile(code)
// both are just `return 1`, no more calls
assertNoInvoke(getSingleMethod(c, "t1"))
assertNoInvoke(getSingleMethod(c, "t2"))
}
@Test
- def inlineMixinMethods(): Unit = {
- val code =
- """trait T {
- | @inline final def f = 1
- |}
- |class C extends T
- """.stripMargin
- val List(c, t, tClass) = compile(code)
- // the static implementation method is inlined into the mixin, so there's no invocation in the mixin
- assertNoInvoke(getSingleMethod(c, "f"))
- }
-
- @Test
def inlineTraitInherited(): Unit = {
val code =
"""trait T {
@@ -521,7 +471,7 @@ class InlinerTest extends ClearAfterClass {
| def t2 = g
|}
""".stripMargin
- val List(c, t, tClass, u, uClass) = compile(code)
+ val List(c, t, u) = compile(code)
assertNoInvoke(getSingleMethod(c, "t1"))
assertNoInvoke(getSingleMethod(c, "t2"))
}
@@ -541,7 +491,7 @@ class InlinerTest extends ClearAfterClass {
"C::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden",
"T::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden")
var count = 0
- val List(c, t, tClass) = compile(code, allowMessage = i => {count += 1; warns.exists(i.msg contains _)})
+ val List(c, t) = compile(code, allowMessage = i => {count += 1; warns.exists(i.msg contains _)})
assert(count == 2, count)
assertInvoke(getSingleMethod(c, "t1"), "T", "f")
assertInvoke(getSingleMethod(c, "t2"), "C", "f")
@@ -557,7 +507,7 @@ class InlinerTest extends ClearAfterClass {
| def t1(t: T) = t.f
|}
""".stripMargin
- val List(c, t, tClass) = compile(code)
+ val List(c, t) = compile(code)
assertNoInvoke(getSingleMethod(c, "t1"))
}
@@ -569,7 +519,7 @@ class InlinerTest extends ClearAfterClass {
|}
|object O extends T {
| @inline def g = 1
- | // mixin generates `def f = T$class.f(this)`, which is inlined here (we get ICONST_0)
+ | // mixin generates `def f = super[T].f`, which is inlined here (we get ICONST_0)
|}
|class C {
| def t1 = O.f // the mixin method of O is inlined, so we directly get the ICONST_0
@@ -579,10 +529,10 @@ class InlinerTest extends ClearAfterClass {
""".stripMargin
val warn = "T::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden"
var count = 0
- val List(c, oMirror, oModule, t, tClass) = compile(code, allowMessage = i => {count += 1; i.msg contains warn})
+ val List(c, oMirror, oModule, t) = compile(code, allowMessage = i => {count += 1; i.msg contains warn})
assert(count == 1, count)
- assertNoInvoke(getSingleMethod(oModule, "f"))
+ assertNoInvoke(getSingleMethod(t, "f"))
assertNoInvoke(getSingleMethod(c, "t1"))
assertNoInvoke(getSingleMethod(c, "t2"))
@@ -598,21 +548,19 @@ class InlinerTest extends ClearAfterClass {
|}
|trait Assembly extends T {
| @inline final def g = 1
- | @inline final def n = m // inlined. (*)
- | // (*) the declaration class of m is T. the signature of T$class.m is m(LAssembly;)I. so we need the self type to build the
- | // signature. then we can look up the MethodNode of T$class.m and then rewrite the INVOKEINTERFACE to INVOKESTATIC.
+ | @inline final def n = m // inlined (m is final)
|}
|class C {
- | def t1(a: Assembly) = a.f // like above, decl class is T, need self-type of T to rewrite the interface call to static.
+ | def t1(a: Assembly) = a.f // inlined (f is final)
| def t2(a: Assembly) = a.n
|}
""".stripMargin
- val List(assembly, assemblyClass, c, t, tClass) = compile(code)
+ val List(assembly, c, t) = compile(code)
- assertNoInvoke(getSingleMethod(tClass, "f"))
+ assertNoInvoke(getSingleMethod(t, "f"))
- assertNoInvoke(getSingleMethod(assemblyClass, "n"))
+ assertNoInvoke(getSingleMethod(assembly, "n"))
assertNoInvoke(getSingleMethod(c, "t1"))
assertNoInvoke(getSingleMethod(c, "t2"))
@@ -647,30 +595,30 @@ class InlinerTest extends ClearAfterClass {
val code =
"""trait T1 {
| @inline def f: Int = 0
- | @inline def g1 = f // not inlined: f not final, so T1$class.g1 has an interface call T1.f
+ | @inline def g1 = f // not inlined: f not final
|}
|
- |// erased self-type (used in impl class for `self` parameter): T1
+ |// erased self-type: T1
|trait T2a { self: T1 with T2a =>
| @inline override final def f = 1
- | @inline def g2a = f // inlined: resolved as T2a.f, which is re-written to T2a$class.f, so T2a$class.g2a has ICONST_1
+ | @inline def g2a = f // inlined: resolved as T2a.f
|}
|
|final class Ca extends T1 with T2a {
- | // mixin generates accessors like `def g1 = T1$class.g1`, the impl class method call is inlined into the accessor.
+ | // mixin generates accessors like `def g1 = super[T1].g1`, the impl super call is inlined into the accessor.
|
| def m1a = g1 // call to accessor, inlined, we get the interface call T1.f
| def m2a = g2a // call to accessor, inlined, we get ICONST_1
| def m3a = f // call to accessor, inlined, we get ICONST_1
|
- | def m4a(t: T1) = t.f // T1.f is not final, so not inlined, interface call to T1.f
- | def m5a(t: T2a) = t.f // re-written to T2a$class.f, inlined, ICONST_1
+ | def m4a(t: T1) = t.f // T1.f is not final, so not inlined, we get an interface call T1.f
+ | def m5a(t: T2a) = t.f // inlined, we get ICONST_1
|}
|
|// erased self-type: T2b
|trait T2b { self: T2b with T1 =>
| @inline override final def f = 1
- | @inline def g2b = f // not inlined: resolved as T1.f, so T2b$class.g2b has an interface call T1.f
+ | @inline def g2b = f // not inlined: resolved as T1.f, we get an interface call T1.f
|}
|
|final class Cb extends T1 with T2b {
@@ -679,23 +627,17 @@ class InlinerTest extends ClearAfterClass {
| def m3b = f // inlined, we get ICONST_1
|
| def m4b(t: T1) = t.f // T1.f is not final, so not inlined, interface call to T1.f
- | def m5b(t: T2b) = t.f // re-written to T2b$class.f, inlined, ICONST_1
+ | def m5b(t: T2b) = t.f // inlined, ICONST_1
|}
""".stripMargin
val warning = "T1::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden"
var count = 0
- val List(ca, cb, t1, t1C, t2a, t2aC, t2b, t2bC) = compile(code, allowMessage = i => {count += 1; i.msg contains warning})
+ val List(ca, cb, t1, t2a, t2b) = compile(code, allowMessage = i => {count += 1; i.msg contains warning})
assert(count == 4, count) // see comments, f is not inlined 4 times
- val t2aCfDesc = t2aC.methods.asScala.find(_.name == "f").get.desc
- assert(t2aCfDesc == "(LT1;)I", t2aCfDesc) // self-type of T2a is T1
-
- val t2bCfDesc = t2bC.methods.asScala.find(_.name == "f").get.desc
- assert(t2bCfDesc == "(LT2b;)I", t2bCfDesc) // self-type of T2b is T2b
-
- assertNoInvoke(getSingleMethod(t2aC, "g2a"))
- assertInvoke(getSingleMethod(t2bC, "g2b"), "T1", "f")
+ assertNoInvoke(getSingleMethod(t2a, "g2a"))
+ assertInvoke(getSingleMethod(t2b, "g2b"), "T1", "f")
assertInvoke(getSingleMethod(ca, "m1a"), "T1", "f")
assertNoInvoke(getSingleMethod(ca, "m2a")) // no invoke, see comment on def g2a
@@ -732,14 +674,13 @@ class InlinerTest extends ClearAfterClass {
val code =
"""class C {
| trait T { @inline final def f = 1 }
- | class D extends T{
+ | class D extends T {
| def m(t: T) = t.f
| }
- |
| def m(d: D) = d.f
|}
""".stripMargin
- val List(c, d, t, tC) = compile(code)
+ val List(c, d, t) = compile(code)
assertNoInvoke(getSingleMethod(d, "m"))
assertNoInvoke(getSingleMethod(c, "m"))
}
@@ -754,9 +695,9 @@ class InlinerTest extends ClearAfterClass {
| def t2(t: T) = t.f(2)
|}
""".stripMargin
- val List(c, t, tc) = compile(code)
- val t1 = getSingleMethod(tc, "t1")
- val t2 = getSingleMethod(tc, "t2")
+ val List(c, t) = compile(code)
+ val t1 = getSingleMethod(t, "t1")
+ val t2 = getSingleMethod(t, "t2")
val cast = TypeOp(CHECKCAST, "C")
Set(t1, t2).foreach(m => assert(m.instructions.contains(cast), m.instructions))
}
@@ -803,8 +744,8 @@ class InlinerTest extends ClearAfterClass {
| final val d = 3
| final val d1: Int = 3
|
- | @noinline def f = 5 // re-written to T$class
- | @noinline final def g = 6 // re-written
+ | @noinline def f = 5
+ | @noinline final def g = 6
|
| @noinline def h: Int
| @inline def i: Int
@@ -817,8 +758,8 @@ class InlinerTest extends ClearAfterClass {
| final val d = 3
| final val d1: Int = 3
|
- | @noinline def f = 5 // not re-written (not final)
- | @noinline final def g = 6 // re-written
+ | @noinline def f = 5
+ | @noinline final def g = 6
|
| @noinline def h: Int
| @inline def i: Int
@@ -835,7 +776,7 @@ class InlinerTest extends ClearAfterClass {
|}
""".stripMargin
- val List(c, t, tClass, u, uClass) = compile(code, allowMessage = _.msg contains "i()I is annotated @inline but cannot be inlined")
+ val List(c, t, u) = compile(code, allowMessage = _.msg contains "i()I is annotated @inline but cannot be inlined")
val m1 = getSingleMethod(c, "m1")
assertInvoke(m1, "T", "a")
assertInvoke(m1, "T", "b")
@@ -844,8 +785,8 @@ class InlinerTest extends ClearAfterClass {
assertNoInvoke(getSingleMethod(c, "m2"))
val m3 = getSingleMethod(c, "m3")
- assertInvoke(m3, "T$class", "f")
- assertInvoke(m3, "T$class", "g")
+ assertInvoke(m3, "T", "f")
+ assertInvoke(m3, "T", "g")
assertInvoke(m3, "T", "h")
assertInvoke(m3, "T", "i")
@@ -858,7 +799,7 @@ class InlinerTest extends ClearAfterClass {
val m6 = getSingleMethod(c, "m6")
assertInvoke(m6, "U", "f")
- assertInvoke(m6, "U$class", "g")
+ assertInvoke(m6, "U", "g")
assertInvoke(m6, "U", "h")
assertInvoke(m6, "U", "i")
}
@@ -892,7 +833,7 @@ class InlinerTest extends ClearAfterClass {
val warn =
"""failed to determine if <init> should be inlined:
|The method <init>()V could not be found in the class A$Inner or any of its parents.
- |Note that the following parent classes could not be found on the classpath: A$Inner""".stripMargin
+ |Note that the parent class A$Inner could not be found on the classpath.""".stripMargin
var c = 0
@@ -988,7 +929,588 @@ class InlinerTest extends ClearAfterClass {
val List(c) = compile(code)
val t = getSingleMethod(c, "t").instructions
assertNoInvoke(t)
- assert(2 == t.collect({case Ldc(_, "hai!") => }).size) // twice the body of f
+ assert(1 == t.collect({case Ldc(_, "hai!") => }).size) // push-pop eliminates the first LDC("hai!")
assert(1 == t.collect({case Jump(IFNONNULL, _) => }).size) // one single null check
}
+
+ @Test
+ def inlineIndyLambda(): Unit = {
+ val code =
+ """object M {
+ | @inline def m(s: String) = {
+ | val f = (x: String) => x.trim
+ | f(s)
+ | }
+ |}
+ |class C {
+ | @inline final def m(s: String) = {
+ | val f = (x: String) => x.trim
+ | f(s)
+ | }
+ | def t1 = m("foo")
+ | def t2 = M.m("bar")
+ |}
+ """.stripMargin
+
+ val List(c, _, _) = compile(code)
+
+ val t1 = getSingleMethod(c, "t1")
+ assertNoIndy(t1)
+ // the indy call is inlined into t, and the closure elimination rewrites the closure invocation to the body method
+ assertInvoke(t1, "C", "C$$$anonfun$2")
+
+ val t2 = getSingleMethod(c, "t2")
+ assertNoIndy(t2)
+ assertInvoke(t2, "M$", "M$$$anonfun$1")
+ }
+
+ @Test
+ def inlinePostRequests(): Unit = {
+ val code =
+ """class C {
+ | final def f = 10
+ | final def g = f + 19
+ | final def h = g + 29
+ | final def i = h + 39
+ |}
+ """.stripMargin
+
+ val List(c) = compile(code)
+ val hMeth = findAsmMethod(c, "h")
+ val gMeth = findAsmMethod(c, "g")
+ val iMeth = findAsmMethod(c, "i")
+ val fCall = getCallsite(gMeth, "f")
+ val gCall = getCallsite(hMeth, "g")
+ val hCall = getCallsite(iMeth, "h")
+
+ val warning = inliner.canInlineBody(gCall)
+ assert(warning.isEmpty, warning)
+
+ inliner.inline(InlineRequest(hCall,
+ post = List(InlineRequest(gCall,
+ post = List(InlineRequest(fCall, Nil))))))
+ assertNoInvoke(convertMethod(iMeth)) // no invoke in i: first h is inlined, then the inlined call to g is also inlined, etc for f
+ assertInvoke(convertMethod(gMeth), "C", "f") // g itself still has the call to f
+ }
+
+ @Test
+ def postRequestSkipAlreadyInlined(): Unit = {
+ val code =
+ """class C {
+ | final def a = 10
+ | final def b = a + 20
+ | final def c = b + 30
+ | final def d = c + 40
+ |}
+ """.stripMargin
+
+ val List(cl) = compile(code)
+ val List(b, c, d) = List("b", "c", "d").map(findAsmMethod(cl, _))
+ val aCall = getCallsite(b, "a")
+ val bCall = getCallsite(c, "b")
+ val cCall = getCallsite(d, "c")
+
+ inliner.inline(InlineRequest(bCall, Nil))
+
+ val req = InlineRequest(cCall,
+ List(InlineRequest(bCall,
+ List(InlineRequest(aCall, Nil)))))
+ inliner.inline(req)
+
+ assertNoInvoke(convertMethod(d))
+ }
+
+ @Test
+ def inlineAnnotatedCallsite(): Unit = {
+ val code =
+ """class C {
+ | final def a(x: Int, f: Int => Int): Int = f(x)
+ | final def b(x: Int) = x
+ | final def c = 1
+ | final def d[T] = 2
+ | final def e[T](x: T) = c
+ | final def f[T](x: T) = println(x)
+ | final def g(x: Int)(y: Int) = x
+ |
+ | def t1 = a(10, _ + 1)
+ | def t2 = a(10, _ + 1): @noinline
+ | def t3 = b(3)
+ | def t4 = b(3): @inline
+ | def t5 = c: @inline
+ | def t6 = d[Int]: @inline
+ | def t7 = e[Int](2): @inline
+ | def t8 = f[Int](2): @inline
+ | def t9 = g(1)(2): @inline
+ |}
+ """.stripMargin
+
+ val List(c) = compile(code)
+ assertInvoke(getSingleMethod(c, "t1"), "C", "C$$$anonfun$1")
+ assertInvoke(getSingleMethod(c, "t2"), "C", "a")
+ assertInvoke(getSingleMethod(c, "t3"), "C", "b")
+ assertNoInvoke(getSingleMethod(c, "t4"))
+ assertNoInvoke(getSingleMethod(c, "t5"))
+ assertNoInvoke(getSingleMethod(c, "t6"))
+ assertInvoke(getSingleMethod(c, "t7"), "C", "c")
+ assertInvoke(getSingleMethod(c, "t8"), "scala/Predef$", "println")
+ assertNoInvoke(getSingleMethod(c, "t9"))
+ }
+
+ @Test
+ def inlineNoInlineOverride(): Unit = {
+ val code =
+ """class C {
+ | @inline final def f1(x: Int) = x
+ | @noinline final def f2(x: Int) = x
+ | final def f3(x: Int) = x
+ |
+ | def t1 = f1(1) // inlined
+ | def t2 = f2(1) // not inlined
+ | def t3 = f1(1): @noinline // not inlined
+ | def t4 = f2(1): @inline // not inlined (cannot override the def-site @noinline)
+ | def t5 = f3(1): @inline // inlined
+ | def t6 = f3(1): @noinline // not inlined
+ |
+ | def t7 = f1(1) + (f3(1): @inline) // without parenthesis, the ascription encloses the entire expression..
+ | def t8 = f1(1) + (f1(1): @noinline)
+ | def t9 = f1(1) + f1(1) : @noinline // the ascription goes on the entire expression, so on the + invocation.. both f1 are inlined
+ |}
+ """.stripMargin
+
+ val List(c) = compile(code)
+ assertNoInvoke(getSingleMethod(c, "t1"))
+ assertInvoke(getSingleMethod(c, "t2"), "C", "f2")
+ assertInvoke(getSingleMethod(c, "t3"), "C", "f1")
+ assertInvoke(getSingleMethod(c, "t4"), "C", "f2")
+ assertNoInvoke(getSingleMethod(c, "t5"))
+ assertInvoke(getSingleMethod(c, "t6"), "C", "f3")
+ assertNoInvoke(getSingleMethod(c, "t7"))
+ assertInvoke(getSingleMethod(c, "t8"), "C", "f1")
+ assertNoInvoke(getSingleMethod(c, "t9"))
+ }
+
+ @Test
+ def inlineHigherOrder(): Unit = {
+ val code =
+ """class C {
+ | final def h(f: Int => Int): Int = f(0)
+ | def t1 = h(x => x + 1)
+ | def t2 = {
+ | val fun = (x: Int) => x + 1
+ | h(fun)
+ | }
+ | def t3(f: Int => Int) = h(f)
+ | def t4(f: Int => Int) = {
+ | val fun = f
+ | h(fun)
+ | }
+ | def t5 = h(Map(0 -> 10)) // not currently inlined
+ |}
+ """.stripMargin
+
+ val List(c) = compile(code)
+ assertInvoke(getSingleMethod(c, "t1"), "C", "C$$$anonfun$1")
+ assertInvoke(getSingleMethod(c, "t2"), "C", "C$$$anonfun$2")
+ assertInvoke(getSingleMethod(c, "t3"), "scala/Function1", "apply$mcII$sp")
+ assertInvoke(getSingleMethod(c, "t4"), "scala/Function1", "apply$mcII$sp")
+ assertInvoke(getSingleMethod(c, "t5"), "C", "h")
+ }
+
+ @Test
+ def twoStepNoInlineHandler(): Unit = {
+ val code =
+ """class C {
+ | @inline final def f = try 1 catch { case _: Throwable => 2 }
+ | @inline final def g = f
+ | def t = println(g) // cannot inline g onto non-empty stack once that f was inlined into g
+ |}
+ """.stripMargin
+
+ val warn =
+ """C::g()I is annotated @inline but could not be inlined:
+ |The operand stack at the callsite in C::t()V contains more values than the
+ |arguments expected by the callee C::g()I. These values would be discarded
+ |when entering an exception handler declared in the inlined method.""".stripMargin
+
+ val List(c) = compile(code, allowMessage = _.msg contains warn)
+ assertInvoke(getSingleMethod(c, "t"), "C", "g")
+ }
+
+ @Test
+ def twoStepNoInlinePrivate(): Unit = {
+ val code =
+ """class C {
+ | @inline final def g = {
+ | @noinline def f = 0
+ | f
+ | }
+ | @inline final def h = g // after inlining g, h has an invocate of private method f$1
+ |}
+ |class D {
+ | def t(c: C) = c.h // cannot inline
+ |}
+ """.stripMargin
+
+ val warn =
+ """C::h()I is annotated @inline but could not be inlined:
+ |The callee C::h()I contains the instruction INVOKESPECIAL C.f$1 ()I
+ |that would cause an IllegalAccessError when inlined into class D.""".stripMargin
+
+ val List(c, d) = compile(code, allowMessage = _.msg contains warn)
+ assertInvoke(getSingleMethod(c, "h"), "C", "f$1")
+ assertInvoke(getSingleMethod(d, "t"), "C", "h")
+ }
+
+ @Test
+ def twoStepInlinePrivate(): Unit = {
+ val code =
+ """class C {
+ | @inline final def g = { // initially, g invokes the private method f$1, but then f$1 is inlined
+ | @inline def f = 0
+ | f
+ | }
+ |}
+ |class D {
+ | def t(c: C) = c.g // can inline
+ |}
+ """.stripMargin
+
+ val List(c, d) = compile(code)
+ assertNoInvoke(getSingleMethod(c, "g"))
+ assertNoInvoke(getSingleMethod(d, "t"))
+ }
+
+ @Test
+ def optimizeSpecializedClosures(): Unit = {
+ val code =
+ """class ValKl(val x: Int) extends AnyVal
+ |
+ |class C {
+ | def t1 = {
+ | // IndyLambda: SAM type is JFunction1$mcII$sp, SAM is apply$mcII$sp(I)I, body method is $anonfun(I)I
+ | val f = (x: Int) => x + 1
+ | // invocation of apply$mcII$sp(I)I, matches the SAM in IndyLambda. no boxing / unboxing needed.
+ | f(10)
+ | // opt: re-write the invocation to the body method
+ | }
+ |
+ | @inline final def m1a(f: Long => Int) = f(1l)
+ | def t1a = m1a(l => l.toInt) // after inlining m1a, we have the same situation as in t1
+ |
+ | def t2 = {
+ | // there is no specialized variant of Function2 for this combination of types, so the IndyLambda has to create a generic Function2.
+ | // IndyLambda: SAM type is JFunction2, SAM is apply(ObjectObject)Object, body method is $anonfun$adapted(ObjectObject)Object
+ | val f = (b: Byte, i: Int) => i + b
+ | // invocation of apply(ObjectOjbect)Object, matches SAM in IndyLambda. arguments are boxed, result unboxed.
+ | f(1, 2)
+ | // opt: re-wrtie to $anonfun$adapted
+ | // inline that call, then we get box-unbox pairs (can be eliminated) and a call to $anonfun(BI)I
+ | }
+ |
+ | def t3 = {
+ | // similar to t2: for functions with value class parameters, IndyLambda always uses the generic Function version.
+ | // IndyLambda: SAM type is JFunction1, SAM is apply(Object)Object, body method is $anonfun$adapted(Object)Object
+ | val f = (a: ValKl) => a
+ | // invocation of apply(Object)Object, ValKl instance is created, result extracted
+ | f(new ValKl(1))
+ | // opt: re-write to $anonfun$adapted.
+ | // inline that call, then we get value class instantiation-extraction pairs and a call to $anonfun(I)I
+ | }
+ |
+ | def t4 = {
+ | // IndyLambda: SAM type is JFunction1$mcII$sp, SAM is apply$mcII$sp(I)I, body method is $anonfun(I)I
+ | val f: Int => Any = (x: Int) => 1
+ | // invocation of apply(Object)Object, argument is boxed. method name and type doesn't match IndyLambda.
+ | f(10)
+ | // opt: rewriting to the body method requires inserting an unbox operation for the argument, and a box operation for the result
+ | // that produces a box-unbox pair and a call to $anonfun(I)I
+ | }
+ |
+ |
+ | @inline final def m4a[T, U, V](f: (T, U) => V, x: T, y: U) = f(x, y) // invocation to generic apply(ObjectObject)Object
+ | def t4a = m4a((x: Int, y: Double) => 1l + x + y.toLong, 1, 2d) // IndyLambda uses specilized JFunction2$mcJID$sp. after inlining m4a, similar to t4.
+ |
+ | def t5 = {
+ | // no specialization for the comibnation of primitives
+ | // IndyLambda: SAM type is JFunction2, SAM is generic apply, body method is $anonfun$adapted
+ | val f: (Int, Byte) => Any = (x: Int, b: Byte) => 1
+ | // invocation of generic apply.
+ | f(10, 3)
+ | // opt: re-write to $anonfun$adapted, inline that method. generates box-unbox pairs and a call to $anonfun(IB)I
+ | }
+ |
+ | def t5a = m4a((x: Int, y: Byte) => 1, 12, 31.toByte) // similar to t5 after inlining m4a
+ |
+ | // m6$mIVc$sp invokes apply$mcVI$sp
+ | @inline final def m6[@specialized(Int) T, @specialized(Unit) U](f: T => U, x: T): Unit = f(x)
+ | // IndyLambda: JFunction1$mcVI$sp, SAM is apply$mcVI$sp, body method $anonfun(I)V
+ | // invokes m6$mIVc$sp (Lscala/Function1;I)V
+ | def t6 = m6((x: Int) => (), 10)
+ | // opt: after inlining m6, the closure method invocation (apply$mcVI$sp) matches the IndyLambda, the call can be rewritten, no boxing
+ |
+ | // m7 invokes apply
+ | @inline final def m7[@specialized(Boolean) T, @specialized(Int) U](f: T => U, x: T): Unit = f(x)
+ | // IndyLambda: JFunction1, SAM is apply(Object)Object, body method is $anonfun$adapted(Obj)Obj
+ | // `true` is boxed before passing to m7
+ | def t7 = m7((x: Boolean) => (), true)
+ | // opt: after inlining m7, the apply call is re-written to $anonfun$adapted, which is then inlined.
+ | // we get a box-unbox pair and a call to $anonfun(Z)V
+ |
+ |
+ | // invokes the generic apply(ObjObj)Obj
+ | @inline final def m8[T, U, V](f: (T, U) => V, x: T, y: U) = f(x, y)
+ | // IndyLambda: JFunction2$mcJID$sp, SAM is apply$mcJID$sp, body method $anonfun(ID)J
+ | // boxes the int and double arguments and calls m8, unboxToLong the result
+ | def t8 = m8((x: Int, y: Double) => 1l + x + y.toLong, 1, 2d)
+ | // opt: after inlining m8, rewrite to the body method $anonfun(ID)J, which requires inserting unbox operations for the params, box for the result
+ | // the box-unbox pairs can then be optimized away
+ |
+ | // m9$mVc$sp invokes apply$mcVI$sp
+ | @inline final def m9[@specialized(Unit) U](f: Int => U): Unit = f(1)
+ | // IndyLambda: JFunction1, SAM is apply(Obj)Obj, body method $anonfun$adapted(Ojb)Obj
+ | // invocation of m9$mVc$sp
+ | def t9 = m9(println)
+ | // opt: after inlining m9, rewrite to $anonfun$adapted(Ojb)Obj, which requires inserting a box operation for the parameter.
+ | // then we inline $adapted, which has signature (Obj)V. the `BoxedUnit.UNIT` from the body of $anonfun$adapted is eliminated by push-pop
+ |
+ | def t9a = (1 to 10) foreach println // similar to t9
+ |
+ | def intCons(i: Int): Unit = ()
+ | // IndyLambda: JFunction1$mcVI$sp, SAM is apply$mcVI$sp, body method $anonfun(I)V
+ | def t10 = m9(intCons)
+ | // after inlining m9, rewrite the apply$mcVI$sp call to the body method, no adaptations required
+ |
+ | def t10a = (1 to 10) foreach intCons // similar to t10
+ |}
+ """.stripMargin
+ val List(c, _, _) = compile(code)
+
+ assertSameSummary(getSingleMethod(c, "t1"), List(BIPUSH, "C$$$anonfun$1", IRETURN))
+ assertSameSummary(getSingleMethod(c, "t1a"), List(LCONST_1, "C$$$anonfun$2", IRETURN))
+ assertSameSummary(getSingleMethod(c, "t2"), List(ICONST_1, ICONST_2, "C$$$anonfun$3",IRETURN))
+
+ // val a = new ValKl(n); new ValKl(anonfun(a.x)).x
+ // value class instantiation-extraction should be optimized by boxing elim
+ assertSameSummary(getSingleMethod(c, "t3"), List(
+ NEW, DUP, ICONST_1, "<init>", ASTORE,
+ NEW, DUP, ALOAD, "x",
+ "C$$$anonfun$4",
+ "<init>",
+ "x", IRETURN))
+
+ assertSameSummary(getSingleMethod(c, "t4"), List(BIPUSH, "C$$$anonfun$5", "boxToInteger", ARETURN))
+ assertSameSummary(getSingleMethod(c, "t4a"), List(ICONST_1, LDC, "C$$$anonfun$6", LRETURN))
+ assertSameSummary(getSingleMethod(c, "t5"), List(BIPUSH, ICONST_3, "C$$$anonfun$7", "boxToInteger", ARETURN))
+ assertSameSummary(getSingleMethod(c, "t5a"), List(BIPUSH, BIPUSH, I2B, "C$$$anonfun$8", IRETURN))
+ assertSameSummary(getSingleMethod(c, "t6"), List(BIPUSH, "C$$$anonfun$9", RETURN))
+ assertSameSummary(getSingleMethod(c, "t7"), List(ICONST_1, "C$$$anonfun$10", RETURN))
+ assertSameSummary(getSingleMethod(c, "t8"), List(ICONST_1, LDC, "C$$$anonfun$11", LRETURN))
+ assertSameSummary(getSingleMethod(c, "t9"), List(ICONST_1, "boxToInteger", "C$$$anonfun$12", RETURN))
+
+ // t9a inlines Range.foreach, which is quite a bit of code, so just testing the core
+ assertInvoke(getSingleMethod(c, "t9a"), "C", "C$$$anonfun$13")
+ assertInvoke(getSingleMethod(c, "t9a"), "scala/runtime/BoxesRunTime", "boxToInteger")
+
+ assertSameSummary(getSingleMethod(c, "t10"), List(
+ ICONST_1, ISTORE,
+ ALOAD, ILOAD,
+ "C$$$anonfun$14", RETURN))
+
+ // t10a inlines Range.foreach
+ assertInvoke(getSingleMethod(c, "t10a"), "C", "C$$$anonfun$15")
+ assertDoesNotInvoke(getSingleMethod(c, "t10a"), "boxToInteger")
+ }
+
+ @Test
+ def refElimination(): Unit = {
+ val code =
+ """class C {
+ | def t1 = {
+ | var i = 0
+ | @inline def inner() = i += 1
+ | inner()
+ | i
+ | }
+ |
+ | final def m(f: Int => Unit) = f(10)
+ | def t2 = {
+ | var x = -1 // IntRef not yet eliminated: closure elimination does not
+ | m(i => if (i == 10) x = 1) // yet inline the anonfun method, need to improve the heuristsics
+ | x
+ | }
+ |}
+ """.stripMargin
+ val List(c) = compile(code)
+ assertSameCode(getSingleMethod(c, "t1"), List(Op(ICONST_0), Op(ICONST_1), Op(IADD), Op(IRETURN)))
+ assertEquals(getSingleMethod(c, "t2").instructions collect { case i: Invoke => i.owner +"."+ i.name }, List(
+ "scala/runtime/IntRef.create", "C.C$$$anonfun$1"))
+ }
+
+ @Test
+ def tupleElimination(): Unit = {
+ val code =
+ """class C {
+ | @inline final def tpl[A, B](a: A, b: B) = (a, b)
+ | @inline final def t_1[A, B](t: (A, B)) = t._1
+ | @inline final def t_2[A, B](t: (A, B)) = t._2
+ |
+ | def t1 = {
+ | val t = (3, 4) // specialized tuple
+ | t_1(t) + t_2(t) // invocations to generic _1 / _2, box operation inserted when eliminated
+ | }
+ |
+ | def t2 = {
+ | val t = tpl(1, 2) // generic Tuple2[Integer, Integer] created
+ | t._1 + t._2 // invokes the specialized _1$mcI$sp, eliminating requires adding an unbox operation
+ | }
+ |
+ | @inline final def m = (1, 3)
+ | def t3 = {
+ | val (a, b) = m
+ | a - b
+ | }
+ |
+ | def t4 = {
+ | val ((a, b), (c, d)) = (m, m)
+ | a + b + c + d
+ | }
+ |
+ | def t5 = m match {
+ | case (1, y) => y
+ | case (x, y) => x * y
+ | }
+ |}
+ """.stripMargin
+ val List(c) = compile(code)
+ assertSameCode(getSingleMethod(c, "t1"), List(Op(ICONST_3), Op(ICONST_4), Op(IADD), Op(IRETURN)))
+ assertSameCode(getSingleMethod(c, "t2"), List(Op(ICONST_1), Op(ICONST_2), Op(IADD), Op(IRETURN)))
+ assertSameCode(getSingleMethod(c, "t3"), List(Op(ICONST_1), Op(ICONST_3), Op(ISUB), Op(IRETURN)))
+ assertNoInvoke(getSingleMethod(c, "t4"))
+ assertNoInvoke(getSingleMethod(c, "t5"))
+ }
+
+ @Test
+ def redundantCasts(): Unit = {
+
+ // we go through the hoop of inlining the casts because erasure eliminates `asInstanceOf` calls
+ // that are statically known to succeed. For example the following cast is removed by erasure:
+ // `(if (b) c else d).asInstanceOf[C]`
+
+ val code =
+ """class C {
+ | @inline final def asO(a: Any) = a.asInstanceOf[Object]
+ | @inline final def asC(a: Any) = a.asInstanceOf[C]
+ | @inline final def asD(a: Any) = a.asInstanceOf[D]
+ |
+ | def t1(c: C) = asC(c) // eliminated
+ | def t2(c: C) = asO(c) // eliminated
+ | def t3(c: Object) = asC(c) // not elimianted
+ | def t4(c: C, d: D, b: Boolean) = asC(if (b) c else d) // not eliminated: lub of two non-equal reference types approximated with Object
+ | def t5(c: C, d: D, b: Boolean) = asO(if (b) c else d)
+ | def t6(c: C, cs: Array[C], b: Boolean) = asO(if (b) c else cs)
+ |}
+ |class D extends C
+ """.stripMargin
+ val List(c, _) = compile(code)
+ def casts(m: String) = getSingleMethod(c, m).instructions collect { case TypeOp(CHECKCAST, tp) => tp }
+ assertSameCode(getSingleMethod(c, "t1"), List(VarOp(ALOAD, 1), Op(ARETURN)))
+ assertSameCode(getSingleMethod(c, "t2"), List(VarOp(ALOAD, 1), Op(ARETURN)))
+ assertSameCode(getSingleMethod(c, "t3"), List(VarOp(ALOAD, 1), TypeOp(CHECKCAST, "C"), Op(ARETURN)))
+ assertEquals(casts("t4"), List("C"))
+ assertEquals(casts("t5"), Nil)
+ assertEquals(casts("t6"), Nil)
+ }
+
+ @Test
+ def inlineFromSealed(): Unit = {
+ val code =
+ """sealed abstract class Foo {
+ | @inline def bar(x: Int) = x + 1
+ |}
+ |object Foo {
+ | def mkFoo(): Foo = new Baz2
+ |}
+ |
+ |object Baz1 extends Foo
+ |final class Baz2 extends Foo
+ |
+ |object Test {
+ | def f = Foo.mkFoo() bar 10
+ |}
+ """.stripMargin
+
+ val cls = compile(code)
+ val test = cls.find(_.name == "Test$").get
+ assertSameSummary(getSingleMethod(test, "f"), List(
+ GETSTATIC, "mkFoo",
+ BIPUSH, ISTORE,
+ IFNONNULL, ACONST_NULL, ATHROW, -1 /*label*/,
+ ILOAD, ICONST_1, IADD, IRETURN))
+ }
+
+ @Test // a test taken from the test suite for the 2.11 inliner
+ def oldInlineHigherOrderTest(): Unit = {
+ val code =
+ """class C {
+ | private var debug = false
+ | @inline private def ifelse[T](cond: => Boolean, ifPart: => T, elsePart: => T): T = if (cond) ifPart else elsePart
+ | final def t = ifelse(debug, 1, 2)
+ |}
+ """.stripMargin
+ val List(c) = compile(code)
+
+ // box-unbox will clean it up
+ assertSameSummary(getSingleMethod(c, "t"), List(
+ ALOAD, "C$$$anonfun$1", IFEQ /*A*/,
+ "C$$$anonfun$2", IRETURN,
+ -1 /*A*/, "C$$$anonfun$3", IRETURN))
+ }
+
+ @Test
+ def inlineProject(): Unit = {
+ val codeA = "final class A { @inline def f = 1 }"
+ val codeB = "class B { def t(a: A) = a.f }"
+ // tests that no warning is emitted
+ val List(a, b) = compileClassesSeparately(List(codeA, codeB), extraArgs = "-Yopt:l:project -Yopt-warnings")
+ assertInvoke(getSingleMethod(b, "t"), "A", "f")
+ }
+
+ @Test
+ def sd86(): Unit = {
+ val code =
+ """trait T1 { @inline def f = 999 }
+ |trait T2 { self: T1 => @inline override def f = 1 } // note that f is not final
+ |class C extends T1 with T2
+ """.stripMargin
+ val List(c, t1, t2) = compile(code, allowMessage = _ => true)
+ // the forwarder C.f is inlined, so there's no invocation
+ assertSameSummary(getSingleMethod(c, "f"), List(ICONST_1, IRETURN))
+ }
+
+ @Test
+ def sd140(): Unit = {
+ val code =
+ """trait T { @inline def f = 0 }
+ |trait U extends T { @inline override def f = 1 }
+ |trait V extends T { def m = 0 }
+ |final class K extends V with U { override def m = super[V].m }
+ |class C { def t = (new K).f }
+ """.stripMargin
+ val c :: _ = compile(code)
+ assertSameSummary(getSingleMethod(c, "t"), List(NEW, "<init>", ICONST_1, IRETURN)) // ICONST_1, U.f is inlined (not T.f)
+ }
+
+ @Test
+ def inlineArrayForeach(): Unit = {
+ val code =
+ """class C {
+ | def consume(x: Int) = ()
+ | def t(a: Array[Int]): Unit = a foreach consume
+ |}
+ """.stripMargin
+ val List(c) = compile(code)
+ val t = getSingleMethod(c, "t")
+ assertNoIndy(t)
+ assertInvoke(t, "C", "C$$$anonfun$1")
+ }
}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOpts.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOpts.scala
deleted file mode 100644
index 5ef2458c0a..0000000000
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOpts.scala
+++ /dev/null
@@ -1,92 +0,0 @@
-package scala.tools.nsc
-package backend.jvm
-package opt
-
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.junit.Test
-import scala.tools.asm.Opcodes._
-import org.junit.Assert._
-
-import scala.tools.testing.AssertUtil._
-
-import CodeGenTools._
-import scala.tools.partest.ASMConverters
-import ASMConverters._
-import scala.tools.testing.ClearAfterClass
-
-object MethodLevelOpts extends ClearAfterClass.Clearable {
- var methodOptCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:l:method")
- def clear(): Unit = { methodOptCompiler = null }
-}
-
-@RunWith(classOf[JUnit4])
-class MethodLevelOpts extends ClearAfterClass {
- ClearAfterClass.stateToClear = MethodLevelOpts
-
- val methodOptCompiler = MethodLevelOpts.methodOptCompiler
-
- def wrapInDefault(code: Instruction*) = List(Label(0), LineNumber(1, Label(0))) ::: code.toList ::: List(Label(1))
-
- @Test
- def eliminateEmptyTry(): Unit = {
- val code = "def f = { try {} catch { case _: Throwable => 0; () }; 1 }"
- val warn = "a pure expression does nothing in statement position"
- assertSameCode(singleMethodInstructions(methodOptCompiler)(code, allowMessage = _.msg contains warn), wrapInDefault(Op(ICONST_1), Op(IRETURN)))
- }
-
- @Test
- def cannotEliminateLoadBoxedUnit(): Unit = {
- // the compiler inserts a boxed into the try block. it's therefore non-empty (and live) and not eliminated.
- val code = "def f = { try {} catch { case _: Throwable => 0 }; 1 }"
- val m = singleMethod(methodOptCompiler)(code)
- assertTrue(m.handlers.length == 1)
- assertSameCode(m.instructions.take(3), List(Label(0), LineNumber(1, Label(0)), Field(GETSTATIC, "scala/runtime/BoxedUnit", "UNIT", "Lscala/runtime/BoxedUnit;")))
- }
-
- @Test
- def inlineThrowInCatchNotTry(): Unit = {
- // the try block does not contain the `ATHROW` instruction, but in the catch block, `ATHROW` is inlined
- val code = "def f(e: Exception) = throw { try e catch { case _: Throwable => e } }"
- val m = singleMethod(methodOptCompiler)(code)
- assertHandlerLabelPostions(m.handlers.head, m.instructions, 0, 3, 5)
- assertSameCode(m.instructions,
- wrapInDefault(VarOp(ALOAD, 1), Label(3), Op(ATHROW), Label(5), FrameEntry(4, List(), List("java/lang/Throwable")), Op(POP), VarOp(ALOAD, 1), Op(ATHROW))
- )
- }
-
- @Test
- def inlineReturnInCatchNotTry(): Unit = {
- val code = "def f: Int = return { try 1 catch { case _: Throwable => 2 } }"
- // cannot inline the IRETURN into the try block (because RETURN may throw IllegalMonitorState)
- val m = singleMethod(methodOptCompiler)(code)
- assertHandlerLabelPostions(m.handlers.head, m.instructions, 0, 3, 5)
- assertSameCode(m.instructions,
- wrapInDefault(Op(ICONST_1), Label(3), Op(IRETURN), Label(5), FrameEntry(4, List(), List("java/lang/Throwable")), Op(POP), Op(ICONST_2), Op(IRETURN)))
- }
-
- @Test
- def simplifyJumpsInTryCatchFinally(): Unit = {
- val code =
- """def f: Int =
- | try {
- | return 1
- | } catch {
- | case _: Throwable =>
- | return 2
- | } finally {
- | return 2
- | // dead
- | val x = try 10 catch { case _: Throwable => 11 }
- | println(x)
- | }
- """.stripMargin
- val m = singleMethod(methodOptCompiler)(code)
- assertTrue(m.handlers.length == 2)
- assertSameCode(m.instructions.dropNonOp, // drop line numbers and labels that are only used by line numbers
-
- // one single label left :-)
- List(Op(ICONST_1), VarOp(ISTORE, 2), Jump(GOTO, Label(20)), Op(POP), Op(ICONST_2), VarOp(ISTORE, 2), Jump(GOTO, Label(20)), VarOp(ASTORE, 3), Op(ICONST_2), Op(IRETURN), Label(20), Op(ICONST_2), Op(IRETURN))
- )
- }
-}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOptsTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOptsTest.scala
new file mode 100644
index 0000000000..dd7fbd9977
--- /dev/null
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOptsTest.scala
@@ -0,0 +1,762 @@
+package scala.tools.nsc
+package backend.jvm
+package opt
+
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.Test
+import scala.tools.asm.Opcodes._
+import org.junit.Assert._
+
+import scala.tools.asm.tree.ClassNode
+import scala.tools.nsc.backend.jvm.AsmUtils._
+import scala.tools.testing.AssertUtil._
+
+import CodeGenTools._
+import scala.tools.partest.ASMConverters
+import ASMConverters._
+import scala.tools.testing.ClearAfterClass
+import scala.collection.JavaConverters._
+
+object MethodLevelOptsTest extends ClearAfterClass.Clearable {
+ var methodOptCompiler = newCompiler(extraArgs = "-Yopt:l:method")
+ def clear(): Unit = { methodOptCompiler = null }
+}
+
+@RunWith(classOf[JUnit4])
+class MethodLevelOptsTest extends ClearAfterClass {
+ ClearAfterClass.stateToClear = MethodLevelOptsTest
+
+ val methodOptCompiler = MethodLevelOptsTest.methodOptCompiler
+
+ def wrapInDefault(code: Instruction*) = List(Label(0), LineNumber(1, Label(0))) ::: code.toList ::: List(Label(1))
+
+ def locals(c: ClassNode, m: String) = findAsmMethod(c, m).localVariables.asScala.toList.map(l => (l.name, l.index)).sortBy(_._2)
+
+ @Test
+ def eliminateEmptyTry(): Unit = {
+ val code = "def f = { try {} catch { case _: Throwable => 0; () }; 1 }"
+ val warn = "a pure expression does nothing in statement position"
+ assertSameCode(singleMethodInstructions(methodOptCompiler)(code, allowMessage = _.msg contains warn), wrapInDefault(Op(ICONST_1), Op(IRETURN)))
+ }
+
+ @Test
+ def eliminateLoadBoxedUnit(): Unit = {
+ // the compiler inserts a boxed into the try block. it's therefore non-empty (and live) and not eliminated.
+ val code = "def f = { try {} catch { case _: Throwable => 0 }; 1 }"
+ val m = singleMethod(methodOptCompiler)(code)
+ assertTrue(m.handlers.length == 0)
+ assertSameCode(m, List(Op(ICONST_1), Op(IRETURN)))
+ }
+
+ @Test
+ def inlineThrowInCatchNotTry(): Unit = {
+ // the try block does not contain the `ATHROW` instruction, but in the catch block, `ATHROW` is inlined
+ val code = "def f(e: Exception) = throw { try e catch { case _: Throwable => e } }"
+ val m = singleMethod(methodOptCompiler)(code)
+ assertHandlerLabelPostions(m.handlers.head, m.instructions, 0, 3, 5)
+ assertSameCode(m.instructions,
+ wrapInDefault(VarOp(ALOAD, 1), Label(3), Op(ATHROW), Label(5), FrameEntry(4, List(), List("java/lang/Throwable")), Op(POP), VarOp(ALOAD, 1), Op(ATHROW))
+ )
+ }
+
+ @Test
+ def inlineReturnInCatchNotTry(): Unit = {
+ val code = "def f: Int = return { try 1 catch { case _: Throwable => 2 } }"
+ // cannot inline the IRETURN into the try block (because RETURN may throw IllegalMonitorState)
+ val m = singleMethod(methodOptCompiler)(code)
+ assertHandlerLabelPostions(m.handlers.head, m.instructions, 0, 3, 5)
+ assertSameCode(m.instructions,
+ wrapInDefault(Op(ICONST_1), Label(3), Op(IRETURN), Label(5), FrameEntry(4, List(), List("java/lang/Throwable")), Op(POP), Op(ICONST_2), Op(IRETURN)))
+ }
+
+ @Test
+ def simplifyJumpsInTryCatchFinally(): Unit = {
+ val code =
+ """def f: Int =
+ | try {
+ | return 1
+ | } catch {
+ | case _: Throwable =>
+ | return 2
+ | } finally {
+ | return 3
+ | // dead
+ | val x = try 10 catch { case _: Throwable => 11 }
+ | println(x)
+ | }
+ """.stripMargin
+ val m = singleMethod(methodOptCompiler)(code)
+ assertTrue(m.handlers.isEmpty)
+ assertSameCode(m, List(Op(ICONST_3), Op(IRETURN)))
+ }
+
+ @Test
+ def nullStoreLoadElim(): Unit = {
+ // point of this test: we have two cleanups
+ // - remove `ACONST_NULL; ASTORE x` if x is otherwise not live
+ // - remove `ASTORE x; ALOAD x` if x is otherwise not live
+ // in the example below, we have `ACONST_NULL; ASTORE x; ALOAD x`. in this case the store-load
+ // should be removed (even though it looks like a null-store at first).
+ val code =
+ """class C {
+ | def t = {
+ | val x = null
+ | x.toString
+ | }
+ |}
+ """.stripMargin
+ val List(c) = compileClasses(methodOptCompiler)(code)
+ assertSameCode(getSingleMethod(c, "t"), List(
+ Op(ACONST_NULL), Invoke(INVOKEVIRTUAL, "java/lang/Object", "toString", "()Ljava/lang/String;", false), Op(ARETURN)))
+ }
+
+ @Test
+ def deadStoreReferenceElim(): Unit = {
+ val code =
+ """class C {
+ | def t = {
+ | var a = "a" // assign to non-initialized, directly removed by dead store
+ | a = "b" // assign to initialized, replaced by null-store, which is then removed: the var is not live, the uses are null-store or store-load
+ | a = "c"
+ | a // store-load pair will be eliminated
+ | }
+ |}
+ """.stripMargin
+ val List(c) = compileClasses(methodOptCompiler)(code)
+ assertSameCode(
+ getSingleMethod(c, "t"), List(Ldc(LDC, "c"), Op(ARETURN)))
+ }
+
+ @Test
+ def deadStoreReferenceKeepNull(): Unit = {
+ val code =
+ """class C {
+ | def t = {
+ | var a = "el" // this store is live, used in the println.
+ | println(a)
+ | a = "met" // since it's an ASTORE to a live variable, cannot elim the store (SI-5313), but store null instead.
+ | // so we get `LDC met; POP; ACONST_NULL; ASTORE 1`. the `LDC met; POP` is eliminated by push-pop.
+ | a = "zit" // this store is live, so we get `LDC zit; ASOTRE 1; ALOAD 1; ARETURN`.
+ | // we cannot eliminated the store-load sequence, because the local is live (again SI-5313).
+ | a
+ | }
+ |}
+ """.stripMargin
+ val List(c) = compileClasses(methodOptCompiler)(code)
+
+ assertSameCode(getSingleMethod(c, "t"), List(
+ Ldc(LDC, "el"), VarOp(ASTORE, 1),
+ Field(GETSTATIC, "scala/Predef$", "MODULE$", "Lscala/Predef$;"), VarOp(ALOAD, 1), Invoke(INVOKEVIRTUAL, "scala/Predef$", "println", "(Ljava/lang/Object;)V", false),
+ Op(ACONST_NULL), VarOp(ASTORE, 1),
+ Ldc(LDC, "zit"), VarOp(ASTORE, 1), VarOp(ALOAD, 1), Op(ARETURN)))
+ }
+
+ @Test
+ def elimUnusedTupleObjectStringBox(): Unit = {
+ val code =
+ """class C {
+ | def t(x: Int, y: Int): Int = {
+ | val a = (x, y) // Tuple2$mcII$sp
+ | val b = (a, y) // Tuple2
+ | val c = (new Object, "krik", new String) // unused java/lang/Object, java/lang/String allocation and string constant is also eliminated
+ | val d = new java.lang.Integer(x)
+ | val e = new String(new Array[Char](23)) // array allocation not eliminated, as it may throw (negative size, SI-8601)
+ | val f = new scala.runtime.IntRef(11)
+ | x + y
+ | }
+ |}
+ """.stripMargin
+ val List(c) = compileClasses(methodOptCompiler)(code)
+ assertSameCode(getSingleMethod(c, "t"), List(
+ IntOp(BIPUSH, 23), IntOp(NEWARRAY, 5), Op(POP), VarOp(ILOAD, 1), VarOp(ILOAD, 2), Op(IADD), Op(IRETURN)))
+ }
+
+ @Test
+ def noElimImpureConstructor(): Unit = {
+ val code =
+ """class C {
+ | def t(x: Int, y: Int): Int = {
+ | val a = new java.lang.Integer("nono")
+ | x + y
+ | }
+ |}
+ """.stripMargin
+ val List(c) = compileClasses(methodOptCompiler)(code)
+ assertSameCode(getSingleMethod(c, "t"), List(
+ TypeOp(NEW, "java/lang/Integer"), Ldc(LDC, "nono"), Invoke(INVOKESPECIAL, "java/lang/Integer", "<init>", "(Ljava/lang/String;)V", false),
+ VarOp(ILOAD, 1), VarOp(ILOAD, 2), Op(IADD), Op(IRETURN)))
+ }
+
+ @Test
+ def elimUnusedBoxUnbox(): Unit = {
+ val code =
+ """class C {
+ | def t(a: Long): Int = {
+ | val t = 3 + a
+ | val u = a + t
+ | val v: Any = u // scala/runtime/BoxesRunTime.boxToLong
+ |
+ | val w = (v, a) // a Tuple2 (not specialized because first value is Any)
+ | // so calls scala/runtime/BoxesRunTime.boxToLong on the second value
+ |
+ | val x = v.asInstanceOf[Long] // scala/runtime/BoxesRunTime.unboxToLong
+ |
+ | val z = (java.lang.Long.valueOf(a), t) // java box call on the left, scala/runtime/BoxesRunTime.boxToLong on the right
+ |
+ | 0
+ | }
+ |}
+ """.stripMargin
+ val List(c) = compileClasses(methodOptCompiler)(code)
+ assertSameCode(getSingleMethod(c, "t"), List(Op(ICONST_0), Op(IRETURN)))
+ }
+
+ @Test
+ def elimUnusedClosure(): Unit = {
+ val code =
+ """class C {
+ | def t(x: Int, y: Int): Int = {
+ | val f = (a: Int) => a + x + y
+ | val g = (b: Int) => b - x
+ | val h = (s: String) => println(s)
+ | f(30)
+ | }
+ |}
+ """.stripMargin
+ val List(c) = compileClasses(methodOptCompiler)(code)
+ assertSameCode(getSingleMethod(c, "t"), List(
+ IntOp(BIPUSH, 30), VarOp(ISTORE, 3), // no constant propagation, so we keep the store (and load below) of a const
+ VarOp(ILOAD, 1),
+ VarOp(ILOAD, 2),
+ VarOp(ILOAD, 3),
+ Invoke(INVOKESTATIC, "C", "C$$$anonfun$1", "(III)I", false), Op(IRETURN)))
+ }
+
+ @Test
+ def rewriteSpecializedClosureCall(): Unit = {
+ val code =
+ """class C {
+ | def t = {
+ | val f1 = (x: Int) => println(x) // int-unit specialization
+ | val f2 = (x: Int, y: Long) => x == y // int-long-boolean
+ | f1(1)
+ | f2(3, 4)
+ | }
+ |}
+ """.stripMargin
+ val List(c) = compileClasses(methodOptCompiler)(code)
+ val t = getSingleMethod(c, "t")
+ assert(!t.instructions.exists(_.opcode == INVOKEDYNAMIC), t)
+ }
+
+ @Test
+ def boxUnboxPrimitive(): Unit = {
+ val code =
+ """class C {
+ | def t1 = {
+ | val a: Any = runtime.BoxesRunTime.boxToInteger(1)
+ | runtime.BoxesRunTime.unboxToInt(a) + 1
+ | }
+ |
+ | // two box and two unbox operations
+ | def t2(b: Boolean) = {
+ | val a = if (b) (3l: Any) else 2l
+ | a.asInstanceOf[Long] + 1 + a.asInstanceOf[Long]
+ | }
+ |
+ | def t3(i: Integer): Int = i.asInstanceOf[Int]
+ |
+ | def t4(l: Long): Any = l
+ |
+ | def t5(i: Int): Int = {
+ | val b = Integer.valueOf(i)
+ | val c: Integer = i
+ | b.asInstanceOf[Int] + c.intValue
+ | }
+ |
+ | def t6: Long = {
+ | val y = new java.lang.Boolean(true)
+ | val i: Integer = if (y) new Integer(10) else 13
+ | val j: java.lang.Long = 3l
+ | j + i
+ | }
+ |
+ | def t7: Int = {
+ | val a: Any = 3
+ | a.asInstanceOf[Int] + a.asInstanceOf[Int]
+ | }
+ |
+ | def t8 = null.asInstanceOf[Int]
+ |
+ | def t9: Int = {
+ | val a = Integer.valueOf(10)
+ | val b = runtime.BoxesRunTime.unboxToInt(a)
+ | a + b
+ | }
+ |
+ | @noinline def escape(a: Any) = ()
+ |
+ | // example E4 in BoxUnbox doc comment
+ | def t10: Int = {
+ | val a = Integer.valueOf(10) // int 10 is stored into local
+ | escape(a)
+ | a // no unbox, 10 is read from local
+ | }
+ |
+ | // the boxes here cannot be eliminated. see doc comment in BoxUnbox, example E1.
+ | def t11(b: Boolean): Int = {
+ | val i = Integer.valueOf(10)
+ | val j = Integer.valueOf(41)
+ | escape(i) // force rewrite method M1 (see doc in BoxUnbox)
+ | val res: Integer = if (b) i else j
+ | res.toInt // cannot be re-written to a local variable read - we don't know which local to read
+ | }
+ |
+ | // both boxes have a single unboxing consumer, and the escape. note that the escape does
+ | // NOT put the two boxes into the same set of rewrite operations: we can rewrite both
+ | // boxes with their unbox individually. in both cases the box also escapes, so method
+ | // M1 will keep the box around.
+ | def t12(b: Boolean): Int = {
+ | val i = Integer.valueOf(10)
+ | val j = Integer.valueOf(32)
+ | escape(if (b) i else j) // force method M1. the escape here is a consumer for both boxes
+ | if (b) i.toInt else j.toInt // both boxes (i, j) have their own unboxing consumer
+ | }
+ |}
+ """.stripMargin
+
+ val List(c) = compileClasses(methodOptCompiler)(code)
+
+ assertNoInvoke(getSingleMethod(c, "t1"))
+ assertNoInvoke(getSingleMethod(c, "t2"))
+ assertInvoke(getSingleMethod(c, "t3"), "scala/runtime/BoxesRunTime", "unboxToInt")
+ assertInvoke(getSingleMethod(c, "t4"), "scala/runtime/BoxesRunTime", "boxToLong")
+ assertNoInvoke(getSingleMethod(c, "t5"))
+ assertNoInvoke(getSingleMethod(c, "t6"))
+ assertNoInvoke(getSingleMethod(c, "t7"))
+ assertSameSummary(getSingleMethod(c, "t8"), List(ICONST_0, IRETURN))
+ assertNoInvoke(getSingleMethod(c, "t9"))
+ // t10: no invocation of unbox
+ assertEquals(getSingleMethod(c, "t10").instructions collect { case Invoke(_, owner, name, _, _) => (owner, name) }, List(
+ ("java/lang/Integer", "valueOf"),
+ ("C", "escape")))
+
+ assertSameSummary(getSingleMethod(c, "t11"), List(
+ BIPUSH, "valueOf", ASTORE /*2*/,
+ BIPUSH, "valueOf", ASTORE /*3*/,
+ ALOAD /*0*/, ALOAD /*2*/, "escape",
+ ILOAD /*1*/, IFEQ /*L1*/, ALOAD /*2*/, GOTO /*L2*/, /*Label L1*/ -1, ALOAD /*3*/, /*Label L2*/ -1,
+ ASTORE /*4*/, GETSTATIC /*Predef*/, ALOAD /*4*/, "Integer2int", IRETURN))
+
+ // no unbox invocations
+ assertEquals(getSingleMethod(c, "t12").instructions collect { case Invoke(_, owner, name, _, _) => (owner, name) }, List(
+ ("java/lang/Integer", "valueOf"),
+ ("java/lang/Integer", "valueOf"),
+ ("C", "escape")))
+ }
+
+ @Test
+ def refEliminiation(): Unit = {
+ val code =
+ """class C {
+ | import runtime._
+ | @noinline def escape(a: Any) = ()
+ |
+ | def t1 = { // box eliminated
+ | val r = new IntRef(0)
+ | r.elem
+ | }
+ |
+ | def t2(b: Boolean) = {
+ | val r1 = IntRef.zero() // both eliminated
+ | val r2 = IntRef.create(1)
+ | val res: IntRef = if (b) r1 else r2
+ | res.elem
+ | }
+ |
+ | def t3 = {
+ | val r = LongRef.create(10l) // eliminated
+ | r.elem += 3
+ | r.elem
+ | }
+ |
+ | def t4(b: Boolean) = {
+ | val x = BooleanRef.create(false) // eliminated
+ | if (b) x.elem = true
+ | if (x.elem) "a" else "b"
+ | }
+ |
+ | def t5 = {
+ | val r = IntRef.create(10) // not eliminated: the box might be modified in the escape
+ | escape(r)
+ | r.elem
+ | }
+ |
+ | def t6(b: Boolean) = {
+ | val r1 = IntRef.zero()
+ | val r2 = IntRef.create(1)
+ | r1.elem = 39
+ | val res: IntRef = if (b) r1 else r2
+ | res.elem // boxes remain: can't rewrite this read, don't know which local
+ | }
+ |}
+ """.stripMargin
+ val List(c) = compileClasses(methodOptCompiler)(code)
+ assertSameSummary(getSingleMethod(c, "t1"), List(ICONST_0, IRETURN))
+ assertNoInvoke(getSingleMethod(c, "t2"))
+ assertSameSummary(getSingleMethod(c, "t3"), List(LDC, LDC, LADD, LRETURN))
+ assertNoInvoke(getSingleMethod(c, "t4"))
+ assertEquals(getSingleMethod(c, "t5").instructions collect { case Field(_, owner, name, _) => s"$owner.$name" },
+ List("scala/runtime/IntRef.elem"))
+ assertEquals(getSingleMethod(c, "t6").instructions collect { case Field(op, owner, name, _) => s"$op $owner.$name" },
+ List(s"$PUTFIELD scala/runtime/IntRef.elem", s"$GETFIELD scala/runtime/IntRef.elem"))
+ }
+
+ @Test
+ def tupleElimination(): Unit = {
+ val code =
+ """class C {
+ | def t1(b: Boolean) = {
+ | val t = ("hi", "fish")
+ | if (b) t._1 else t._2
+ | }
+ |
+ | def t2 = {
+ | val t = (1, 3) // specialized tuple
+ | t._1 + t._2 // specialized accessors (_1$mcII$sp)
+ | }
+ |
+ | def t3 = {
+ | // boxed before tuple creation, a non-specialized tuple is created
+ | val t = (new Integer(3), Integer.valueOf(4))
+ | t._1 + t._2 // invokes the generic `_1` / `_2` getters, both values unboxed by Integer2int
+ | }
+ |
+ | def t4: Any = {
+ | val t = (3, 3) // specialized tuple is created, ints are not boxed
+ | (t: Tuple2[Any, Any])._1 // when eliminating the _1 call, need to insert a boxing operation
+ | }
+ |
+ | // the inverse of t4 also happens: an Tuple[Integer] where _1$mcI$sp is invoked. In this
+ | // case, an unbox operation needs to be added when eliminating the extraction. The only
+ | // way I found to test this is with an inlined generic method, see InlinerTest.tupleElimination.
+ | def tpl[A, B](a: A, b: B) = (a, b)
+ | def t5: Int = tpl(1, 2)._1 // invokes _1$mcI$sp
+ |
+ | def t6 = {
+ | val (a, b) = (1, 2)
+ | a - b
+ | }
+ |
+ | def t7 = {
+ | // this example is more tricky to handle than it looks, see doc comment in BoxUnbox.
+ | val ((a, b), c) = ((1, 2), 3)
+ | a + b + c
+ | }
+ |
+ | def t8 = {
+ | val ((a, b), (c, d)) = ((1, 2), (3, Integer.valueOf(10)))
+ | a + b + c + d
+ | }
+ |
+ | def t9(a: Int, b: Int) = (a, b) match { // tuple is optimized away
+ | case (x, y) if x == y => 0
+ | case (x, y) => x + y
+ | }
+ |}
+ """.stripMargin
+ val List(c) = compileClasses(methodOptCompiler)(code)
+ assertNoInvoke(getSingleMethod(c, "t1"))
+ assertSameSummary(getSingleMethod(c, "t2"), List(ICONST_1, ICONST_3, IADD, IRETURN))
+ assertSameSummary(getSingleMethod(c, "t3"), List(ICONST_3, ICONST_4, IADD, IRETURN))
+ assertSameSummary(getSingleMethod(c, "t4"), List(ICONST_3, "boxToInteger", ARETURN))
+ assertEquals(getSingleMethod(c, "t5").instructions collect { case Invoke(_, owner, name, _, _) => (owner, name) }, List(
+ ("scala/runtime/BoxesRunTime", "boxToInteger"),
+ ("scala/runtime/BoxesRunTime", "boxToInteger"),
+ ("C", "tpl"),
+ ("scala/Tuple2", "_1$mcI$sp")))
+ assertSameSummary(getSingleMethod(c, "t6"), List(ICONST_1, ICONST_2, ISUB, IRETURN))
+ assertSameSummary(getSingleMethod(c, "t7"), List(
+ ICONST_1, ICONST_2, ISTORE, ISTORE,
+ ICONST_3, ISTORE,
+ ILOAD, ILOAD, IADD, ILOAD, IADD, IRETURN))
+ assertNoInvoke(getSingleMethod(c, "t8"))
+ assertNoInvoke(getSingleMethod(c, "t9"))
+ }
+
+ @Test
+ def nullnessOpts(): Unit = {
+ val code =
+ """class C {
+ | def t1 = {
+ | val a = new C
+ | if (a == null)
+ | println() // eliminated
+ | a
+ | }
+ |
+ | def t2 = null.asInstanceOf[Long] // replaced by zero value
+ |
+ | def t3 = {
+ | val t = (1, 3)
+ | val a = null
+ | if (t ne a) t._1
+ | else throw new Error()
+ | }
+ |
+ | def t4 = {
+ | val i = Integer.valueOf(1)
+ | val a = null
+ | if (i eq a) throw new Error()
+ | else i.toInt
+ | }
+ |
+ | def t5 = {
+ | val i = runtime.DoubleRef.zero()
+ | if (i == null) throw new Error()
+ | else i.elem
+ | }
+ |
+ | def t6 = {
+ | var a = null
+ | var i = null
+ | a = i // eliminated (store of null to variable that is already null)
+ | a // replaced by ACONST_NULL (load of variable that is known null)
+ | }
+ |
+ | def t7 = {
+ | val a = null
+ | a.isInstanceOf[String] // eliminated, replaced by 0 (null.isInstanceOf is always false)
+ | }
+ |}
+ """.stripMargin
+ val List(c) = compileClasses(methodOptCompiler)(code)
+ assertSameSummary(getSingleMethod(c, "t1"), List(NEW, DUP, "<init>", ARETURN))
+ assertSameCode(getSingleMethod(c, "t2"), List(Op(LCONST_0), Op(LRETURN)))
+ assertSameCode(getSingleMethod(c, "t3"), List(Op(ICONST_1), Op(IRETURN)))
+ assertSameCode(getSingleMethod(c, "t4"), List(Op(ICONST_1), Op(IRETURN)))
+ assertSameCode(getSingleMethod(c, "t5"), List(Op(DCONST_0), Op(DRETURN)))
+ assertSameCode(getSingleMethod(c, "t6"), List(Op(ACONST_NULL), Op(ARETURN)))
+ assertSameCode(getSingleMethod(c, "t7"), List(Op(ICONST_0), Op(IRETURN)))
+ }
+
+ @Test
+ def elimRedundantNullCheck(): Unit = {
+ val code =
+ """class C {
+ | def t(x: Object) = {
+ | val bool = x == null
+ | if (x != null) 1 else 0
+ | }
+ |}
+ """.stripMargin
+ val List(c) = compileClasses(methodOptCompiler)(code)
+ assertSameCode(
+ getSingleMethod(c, "t"), List(
+ VarOp(ALOAD, 1), Jump(IFNULL, Label(6)), Op(ICONST_1), Op(IRETURN), Label(6), Op(ICONST_0), Op(IRETURN)))
+ }
+
+ @Test
+ def t5313(): Unit = {
+ val code =
+ """class C {
+ | def randomBoolean = scala.util.Random.nextInt % 2 == 0
+ |
+ | // 3 stores to kept1 (slot 1), 1 store to result (slot 2)
+ | def t1 = {
+ | var kept1 = new Object
+ | val result = new java.lang.ref.WeakReference(kept1)
+ | kept1 = null // we can't eliminate this assignment because result can observe
+ | // when the object has no more references. See SI-5313
+ | kept1 = new Object // could eliminate this one with a more elaborate analysis (we know it contains null)
+ | // however, such is not implemented: if a var is live, then stores are kept.
+ | result
+ | }
+ |
+ | // only two variables are live: kept2 and kept3. they end up on slots 1 and 2.
+ | // kept2 has 2 stores, kept3 has 1 store.
+ | def t2 = {
+ | var erased2 = null // we can eliminate this store because it's never used
+ | val erased3 = erased2 // and this
+ | var erased4 = erased2 // and this
+ | val erased5 = erased4 // and this
+ | var kept2: Object = new Object // ultimately can't be eliminated
+ | while(randomBoolean) {
+ | val kept3 = kept2
+ | kept2 = null // this can't, because it clobbers kept2, which is used
+ | erased4 = null // safe to eliminate
+ | println(kept3)
+ | }
+ | 0
+ | }
+ |
+ | def t3 = {
+ | var kept4 = new Object // have to keep, it's used
+ | try
+ | println(kept4)
+ | catch {
+ | case _ : Throwable => kept4 = null // have to keep, it clobbers kept4 which is used
+ | }
+ | 0
+ | }
+ |
+ | def t4 = {
+ | var kept5 = new Object
+ | print(kept5)
+ | kept5 = null // can't eliminate it's a clobber and it's used
+ | print(kept5)
+ | kept5 = null // eliminated by nullness analysis (store null to a local that is known to be null)
+ | 0
+ | }
+ |
+ | def t5 = {
+ | while(randomBoolean) {
+ | var kept6: AnyRef = null // not used, but have to keep because it clobbers the next used store
+ | // on the back edge of the loop
+ | kept6 = new Object // used
+ | println(kept6)
+ | }
+ | 0
+ | }
+ |}
+ """.stripMargin
+
+ val List(c) = compileClasses(methodOptCompiler)(code)
+ def stores(m: String) = getSingleMethod(c, m).instructions.filter(_.opcode == ASTORE)
+
+ assertEquals(locals(c, "t1"), List(("this",0), ("kept1",1), ("result",2)))
+ assert(stores("t1") == List(VarOp(ASTORE, 1), VarOp(ASTORE, 2), VarOp(ASTORE, 1), VarOp(ASTORE, 1)),
+ textify(findAsmMethod(c, "t1")))
+
+ assertEquals(locals(c, "t2"), List(("this",0), ("kept2",1), ("kept3",2)))
+ assert(stores("t2") == List(VarOp(ASTORE, 1), VarOp(ASTORE, 2), VarOp(ASTORE, 1)),
+ textify(findAsmMethod(c, "t2")))
+
+ assertEquals(locals(c, "t3"), List(("this",0), ("kept4",1)))
+ assert(stores("t3") == List(VarOp(ASTORE, 1), VarOp(ASTORE, 1)),
+ textify(findAsmMethod(c, "t3")))
+
+ assertEquals(locals(c, "t4"), List(("this",0), ("kept5",1)))
+ assert(stores("t4") == List(VarOp(ASTORE, 1), VarOp(ASTORE, 1)),
+ textify(findAsmMethod(c, "t4")))
+
+ assertEquals(locals(c, "t5"), List(("this",0), ("kept6",1)))
+ assert(stores("t5") == List(VarOp(ASTORE, 1), VarOp(ASTORE, 1)),
+ textify(findAsmMethod(c, "t5")))
+ }
+
+ @Test
+ def testCpp(): Unit = {
+ // copied from an old test (run/test-cpp.scala)
+ val code =
+ """class C {
+ | import scala.util.Random._
+ |
+ | def t1(x: Int) = {
+ | val y = x
+ | println(y)
+ | }
+ |
+ | def t2 = {
+ | val x = 2
+ | val y = x
+ | println(y)
+ | }
+ |
+ | def t3 = {
+ | val x = this
+ | val y = x
+ | println(y)
+ | }
+ |
+ | def f = nextInt
+ |
+ | def t4 = {
+ | val x = f
+ | val y = x
+ | println(y)
+ | }
+ |
+ | def t5 = {
+ | var x = nextInt
+ | var y = x
+ | println(y)
+ |
+ | y = nextInt
+ | x = y
+ | println(x)
+ | }
+ |}
+ """.stripMargin
+
+ val List(c) = compileClasses(methodOptCompiler)(code)
+ assertEquals(locals(c, "t1"), List(("this", 0), ("x", 1)))
+
+ assertEquals(locals(c, "t2"), List(("this", 0), ("x", 1)))
+ // we don't have constant propagation (yet).
+ // the local var can't be optimized as a store;laod sequence, there's a GETSTATIC between the two
+ assertSameSummary(getSingleMethod(c, "t2"), List(
+ ICONST_2, ISTORE, GETSTATIC, ILOAD, "boxToInteger", "println", RETURN))
+
+ assertEquals(locals(c, "t3"), List(("this", 0)))
+ assertEquals(locals(c, "t4"), List(("this", 0), ("x", 1)))
+ assertEquals(locals(c, "t5"), List(("this", 0), ("x", 1)))
+ }
+
+ @Test
+ def t7006(): Unit = {
+ val code =
+ """class C {
+ | def t: Unit = {
+ | try {
+ | val x = 3
+ | } finally {
+ | print("hello")
+ | }
+ | while(true) { }
+ | }
+ |}
+ """.stripMargin
+ val List(c) = compileClasses(methodOptCompiler)(code)
+ val t = getSingleMethod(c, "t")
+ assertEquals(t.handlers, Nil)
+ assertEquals(locals(c, "t"), List(("this", 0)))
+ assertSameSummary(t, List(GETSTATIC, LDC, "print", -1, GOTO))
+ }
+
+ @Test
+ def booleanOrderingCompare(): Unit = {
+ val code =
+ """class C {
+ | def compare(x: Boolean, y: Boolean) = (x, y) match {
+ | case (false, true) => -1
+ | case (true, false) => 1
+ | case _ => 0
+ | }
+ |}
+ """.stripMargin
+ val List(c) = compileClasses(methodOptCompiler)(code)
+ assertNoInvoke(getSingleMethod(c, "compare"))
+ }
+
+ @Test
+ def t8790(): Unit = {
+ val code =
+ """class C {
+ | def t(x: Int, y: Int): String = (x, y) match {
+ | case (7, 8) => "a"
+ | case _ => "b"
+ | }
+ |}
+ """.stripMargin
+ val List(c) = compileClasses(methodOptCompiler)(code)
+
+ assertSameSummary(getSingleMethod(c, "t"), List(
+ BIPUSH, ILOAD, IF_ICMPNE,
+ BIPUSH, ILOAD, IF_ICMPNE,
+ LDC, ASTORE, GOTO,
+ -1, LDC, ASTORE,
+ -1, ALOAD, ARETURN))
+ }
+}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala
index f8e887426b..8dd23ec3ce 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala
@@ -9,26 +9,43 @@ import scala.tools.asm.Opcodes._
import org.junit.Assert._
import CodeGenTools._
+import scala.tools.asm.tree.ClassNode
import scala.tools.nsc.backend.jvm.BTypes.{MethodInlineInfo, InlineInfo}
import scala.tools.partest.ASMConverters
import ASMConverters._
-import scala.collection.convert.decorateAsScala._
+import scala.collection.JavaConverters._
+import scala.tools.testing.ClearAfterClass
-object ScalaInlineInfoTest {
- var compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:none")
+object ScalaInlineInfoTest extends ClearAfterClass.Clearable {
+ var compiler = newCompiler(extraArgs = "-Yopt:l:none")
def clear(): Unit = { compiler = null }
}
@RunWith(classOf[JUnit4])
-class ScalaInlineInfoTest {
- val compiler = newCompiler()
+class ScalaInlineInfoTest extends ClearAfterClass {
+ ClearAfterClass.stateToClear = ScalaInlineInfoTest
+ val compiler = ScalaInlineInfoTest.compiler
+
+ def inlineInfo(c: ClassNode): InlineInfo = c.attrs.asScala.collect({ case a: InlineInfoAttribute => a.inlineInfo }).head
+
+ def mapDiff[A, B](a: Map[A, B], b: Map[A, B]) = {
+ val r = new StringBuilder
+ for ((a, av) <- a) {
+ if (!b.contains(a)) r.append(s"missing in b: $a\n")
+ else if (av != b(a)) r.append(s"different for $a: $av != ${b(a)}\n")
+ }
+ for (b <- b.keys.toList diff a.keys.toList) {
+ r.append(s"missing in a: $b\n")
+ }
+ r.toString
+ }
@Test
def traitMembersInlineInfo(): Unit = {
val code =
"""trait T {
| def f1 = 1 // concrete method
- | private def f2 = 1 // implOnly method (does not end up in the interface)
+ | private def f2 = 1 // default method only (not in subclass)
| def f3 = {
| def nest = 0 // nested method (does not end up in the interface)
| nest
@@ -38,13 +55,13 @@ class ScalaInlineInfoTest {
| def f4 = super.toString // super accessor
|
| object O // module accessor (method is generated)
- | def f5 = {
+ | final def f5 = {
| object L { val x = 0 } // nested module (just flattened out)
| L.x
| }
|
| @noinline
- | def f6: Int // abstract method (not in impl class)
+ | def f6: Int // abstract method
|
| // fields
|
@@ -55,31 +72,111 @@ class ScalaInlineInfoTest {
|
| final val x5 = 0
|}
+ |class C extends T {
+ | def f6 = 0
+ | var x3 = 0
+ |}
""".stripMargin
- val cs @ List(t, tl, to, tCls) = compileClasses(compiler)(code)
- val List(info) = t.attrs.asScala.collect({ case a: InlineInfoAttribute => a.inlineInfo }).toList
- val expect = InlineInfo(
- None, // self type
+ val cs @ List(c, t, tl, to) = compileClasses(compiler)(code)
+ val infoT = inlineInfo(t)
+ val expectT = InlineInfo (
false, // final class
+ None, // not a sam
Map(
- ("O()LT$O$;", MethodInlineInfo(true, false,false,false)),
- ("T$$super$toString()Ljava/lang/String;",MethodInlineInfo(false,false,false,false)),
- ("T$_setter_$x1_$eq(I)V", MethodInlineInfo(false,false,false,false)),
- ("f1()I", MethodInlineInfo(false,true, false,false)),
- ("f3()I", MethodInlineInfo(false,true, false,false)),
- ("f4()Ljava/lang/String;", MethodInlineInfo(false,true, true, false)),
- ("f5()I", MethodInlineInfo(false,true, false,false)),
- ("f6()I", MethodInlineInfo(false,false,false,true )),
- ("x1()I", MethodInlineInfo(false,false,false,false)),
- ("x3()I", MethodInlineInfo(false,false,false,false)),
- ("x3_$eq(I)V", MethodInlineInfo(false,false,false,false)),
- ("x4()I", MethodInlineInfo(false,false,false,false)),
- ("x5()I", MethodInlineInfo(true, false,false,false)),
- ("y2()I", MethodInlineInfo(false,false,false,false)),
- ("y2_$eq(I)V", MethodInlineInfo(false,false,false,false))),
+ ("O()LT$O$;", MethodInlineInfo(true ,false,false)), // the accessor is abstract in bytecode, but still effectivelyFinal because there's no (late)DEFERRED flag, https://github.com/scala/scala-dev/issues/126
+ ("T$$super$toString()Ljava/lang/String;", MethodInlineInfo(true ,false,false)),
+ ("T$_setter_$x1_$eq(I)V", MethodInlineInfo(false,false,false)),
+ ("f1()I", MethodInlineInfo(false,false,false)),
+ ("f2()I", MethodInlineInfo(true, false,false)),
+ ("f3()I", MethodInlineInfo(false,false,false)),
+ ("f4()Ljava/lang/String;", MethodInlineInfo(false,true, false)),
+ ("f5()I", MethodInlineInfo(true ,false,false)),
+ ("f6()I", MethodInlineInfo(false,false,true )),
+ ("x1()I", MethodInlineInfo(false,false,false)),
+ ("y2()I", MethodInlineInfo(false,false,false)),
+ ("y2_$eq(I)V", MethodInlineInfo(false,false,false)),
+ ("x3()I", MethodInlineInfo(false,false,false)),
+ ("x3_$eq(I)V", MethodInlineInfo(false,false,false)),
+ ("x4()I", MethodInlineInfo(false,false,false)),
+ ("x5()I", MethodInlineInfo(true, false,false)),
+ ("L$lzycompute$1(Lscala/runtime/VolatileObjectRef;)LT$L$2$;", MethodInlineInfo(true, false,false)),
+ ("L$1(Lscala/runtime/VolatileObjectRef;)LT$L$2$;", MethodInlineInfo(true ,false,false)),
+ ("nest$1()I", MethodInlineInfo(true, false,false)),
+ ("$init$()V", MethodInlineInfo(false,false,false))),
None // warning
)
- assert(info == expect, info)
+
+ assert(infoT == expectT, mapDiff(expectT.methodInfos, infoT.methodInfos) + infoT)
+
+ val infoC = inlineInfo(c)
+ val expectC = InlineInfo(false, None, Map(
+ "O()LT$O$;" -> MethodInlineInfo(true ,false,false),
+ "O$lzycompute()LT$O$;" -> MethodInlineInfo(true, false,false),
+ "f6()I" -> MethodInlineInfo(false,false,false),
+ "x1()I" -> MethodInlineInfo(false,false,false),
+ "T$_setter_$x1_$eq(I)V" -> MethodInlineInfo(false,false,false),
+ "y2()I" -> MethodInlineInfo(false,false,false),
+ "y2_$eq(I)V" -> MethodInlineInfo(false,false,false),
+ "x3()I" -> MethodInlineInfo(false,false,false),
+ "x3_$eq(I)V" -> MethodInlineInfo(false,false,false),
+ "x4$lzycompute()I" -> MethodInlineInfo(true ,false,false),
+ "x4()I" -> MethodInlineInfo(false,false,false),
+ "x5()I" -> MethodInlineInfo(true ,false,false),
+ "T$$super$toString()Ljava/lang/String;" -> MethodInlineInfo(true ,false,false),
+ "<init>()V" -> MethodInlineInfo(false,false,false)),
+ None)
+
+ assert(infoC == expectC, mapDiff(expectC.methodInfos, infoC.methodInfos) + infoC)
+ }
+
+ @Test
+ def inlineInfoSam(): Unit = {
+ val code =
+ """trait C { // expected to be seen as sam: g(I)I
+ | def f = 0
+ | def g(x: Int): Int
+ | val foo = "hi"
+ |}
+ |abstract class D {
+ | val biz: Int
+ |}
+ |trait T { // expected to be seen as sam: h(Ljava/lang/String;)I
+ | def h(a: String): Int
+ |}
+ |trait E extends T { // expected to be seen as sam: h(Ljava/lang/String;)I
+ | def hihi(x: Int) = x
+ |}
+ |class F extends T {
+ | def h(a: String) = 0
+ |}
+ |trait U {
+ | def conc() = 10
+ | def nullary: Int
+ |}
+ """.stripMargin
+ val cs = compileClasses(compiler)(code)
+ val sams = cs.map(c => (c.name, inlineInfo(c).sam))
+ assertEquals(sams,
+ List(
+ ("C",Some("g(I)I")),
+ ("D",None),
+ ("E",Some("h(Ljava/lang/String;)I")),
+ ("F",None),
+ ("T",Some("h(Ljava/lang/String;)I")),
+ ("U",None)))
+
+ }
+
+ @Test
+ def lzyComputeInlineInfo(): Unit = {
+ val code = "class C { object O }"
+ val List(c, om) = compileClasses(compiler)(code)
+ val infoC = inlineInfo(c)
+ val expected = Map(
+ "<init>()V" -> MethodInlineInfo(false,false,false),
+ "O$lzycompute()LC$O$;" -> MethodInlineInfo(true,false,false),
+ "O()LC$O$;" -> MethodInlineInfo(true,false,false))
+ assert(infoC.methodInfos == expected, mapDiff(infoC.methodInfos, expected))
}
}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/SimplifyJumpsTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/SimplifyJumpsTest.scala
index a685ae7dd5..99acb318de 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/SimplifyJumpsTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/SimplifyJumpsTest.scala
@@ -96,10 +96,22 @@ class SimplifyJumpsTest {
instructionsFromMethod(method),
List(VarOp(ILOAD, 1), Jump(IFLT, Label(3))) ::: rest.tail )
- // no label allowed between begin and rest. if there's another label, then there could be a
- // branch that label. eliminating the GOTO would change the behavior.
- val nonOptMethod = genMethod()(begin ::: Label(22) :: rest: _*)
- assertFalse(LocalOptImpls.simplifyJumps(nonOptMethod))
+ // branch over goto is OK even if there's a label in between, if that label is not a jump target
+ val withNonJumpTargetLabel = genMethod()(begin ::: Label(22) :: rest: _*)
+ assertTrue(LocalOptImpls.simplifyJumps(withNonJumpTargetLabel))
+ assertSameCode(
+ instructionsFromMethod(withNonJumpTargetLabel),
+ List(VarOp(ILOAD, 1), Jump(IFLT, Label(3)), Label(22)) ::: rest.tail )
+
+ // if the Label(22) between IFGE and GOTO is the target of some jump, we cannot rewrite the IFGE
+ // and remove the GOTO: removing the GOTO would change semantics. However, the jump that targets
+ // Label(22) will be re-written (jump-chain collapsing), so in a second round, the IFGE is still
+ // rewritten to IFLT
+ val twoRounds = genMethod()(List(VarOp(ILOAD, 1), Jump(IFLE, Label(22))) ::: begin ::: Label(22) :: rest: _*)
+ assertTrue(LocalOptImpls.simplifyJumps(twoRounds))
+ assertSameCode(
+ instructionsFromMethod(twoRounds),
+ List(VarOp(ILOAD, 1), Jump(IFLE, Label(3)), VarOp(ILOAD, 1), Jump(IFLT, Label(3)), Label(22)) ::: rest.tail )
}
@Test
@@ -167,6 +179,9 @@ class SimplifyJumpsTest {
VarOp(ILOAD, 1),
Jump(IFGE, Label(target)),
+ VarOp(ILOAD, 1), // some code to prevent rewriting the conditional jump
+ Op(IRETURN),
+
Label(4),
Jump(GOTO, Label(3)),
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala
index 902af7b7fa..0021a1784d 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala
@@ -18,18 +18,14 @@ import scala.tools.testing.ClearAfterClass
object UnreachableCodeTest extends ClearAfterClass.Clearable {
// jvm-1.6 enables emitting stack map frames, which impacts the code generation wrt dead basic blocks,
// see comment in BCodeBodyBuilder
- var methodOptCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:l:method")
- var dceCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:unreachable-code")
- var noOptCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:l:none")
-
- // jvm-1.5 disables computing stack map frames, and it emits dead code as-is. note that this flag triggers a deprecation warning
- var noOptNoFramesCompiler = newCompiler(extraArgs = "-target:jvm-1.5 -Ybackend:GenBCode -Yopt:l:none -deprecation")
+ var methodOptCompiler = newCompiler(extraArgs = "-Yopt:l:method")
+ var dceCompiler = newCompiler(extraArgs = "-Yopt:unreachable-code")
+ var noOptCompiler = newCompiler(extraArgs = "-Yopt:l:none")
def clear(): Unit = {
methodOptCompiler = null
dceCompiler = null
noOptCompiler = null
- noOptNoFramesCompiler = null
}
}
@@ -40,11 +36,10 @@ class UnreachableCodeTest extends ClearAfterClass {
val methodOptCompiler = UnreachableCodeTest.methodOptCompiler
val dceCompiler = UnreachableCodeTest.dceCompiler
val noOptCompiler = UnreachableCodeTest.noOptCompiler
- val noOptNoFramesCompiler = UnreachableCodeTest.noOptNoFramesCompiler
def assertEliminateDead(code: (Instruction, Boolean)*): Unit = {
val method = genMethod()(code.map(_._1): _*)
- LocalOptImpls.removeUnreachableCodeImpl(method, "C")
+ dceCompiler.genBCode.bTypes.localOpt.removeUnreachableCodeImpl(method, "C")
val nonEliminated = instructionsFromMethod(method)
val expectedLive = code.filter(_._2).map(_._1).toList
assertSameCode(nonEliminated, expectedLive)
@@ -152,11 +147,6 @@ class UnreachableCodeTest extends ClearAfterClass {
// Finally, instructions in the dead basic blocks are replaced by ATHROW, as explained in
// a comment in BCodeBodyBuilder.
assertSameCode(noDce.dropNonOp, List(Op(ICONST_1), Op(IRETURN), Op(ATHROW), Op(ATHROW)))
-
- // when NOT computing stack map frames, ASM's ClassWriter does not replace dead code by NOP/ATHROW
- val warn = "target:jvm-1.5 is deprecated"
- val noDceNoFrames = singleMethodInstructions(noOptNoFramesCompiler)(code, allowMessage = _.msg contains warn)
- assertSameCode(noDceNoFrames.dropNonOp, List(Op(ICONST_1), Op(IRETURN), Op(ICONST_2), Op(IRETURN)))
}
@Test
@@ -225,4 +215,50 @@ class UnreachableCodeTest extends ClearAfterClass {
assertTrue(List(FrameEntry(F_FULL, List(INTEGER, DOUBLE, Label(3)), List("java/lang/Object", Label(4))), Label(3), Label(4)) ===
List(FrameEntry(F_FULL, List(INTEGER, DOUBLE, Label(1)), List("java/lang/Object", Label(3))), Label(1), Label(3)))
}
+
+ @Test
+ def loadNullNothingBytecode(): Unit = {
+ val code =
+ """class C {
+ | def nl: Null = null
+ | def nt: Nothing = throw new Error("")
+ | def cons(a: Any) = ()
+ |
+ | def t1 = cons(null)
+ | def t2 = cons(nl)
+ | def t3 = cons(throw new Error(""))
+ | def t4 = cons(nt)
+ |}
+ """.stripMargin
+ val List(c) = compileClasses(noOptCompiler)(code)
+
+ assertSameSummary(getSingleMethod(c, "nl"), List(ACONST_NULL, ARETURN))
+
+ assertSameSummary(getSingleMethod(c, "nt"), List(
+ NEW, DUP, LDC, "<init>", ATHROW))
+
+ assertSameSummary(getSingleMethod(c, "t1"), List(
+ ALOAD, ACONST_NULL, "cons", RETURN))
+
+ // GenBCode introduces POP; ACONST_NULL after loading an expression of type scala.runtime.Null$,
+ // see comment in BCodeBodyBuilder.adapt
+ assertSameSummary(getSingleMethod(c, "t2"), List(
+ ALOAD, ALOAD, "nl", POP, ACONST_NULL, "cons", RETURN))
+
+ // the bytecode generated by GenBCode is ... ATHROW; INVOKEVIRTUAL C.cons; RETURN
+ // the ASM classfile writer creates a new basic block (creates a label) right after the ATHROW
+ // and replaces all instructions by NOP*; ATHROW, see comment in BCodeBodyBuilder.adapt
+ // NOTE: DCE is enabled by default and gets rid of the redundant code (tested below)
+ assertSameSummary(getSingleMethod(c, "t3"), List(
+ ALOAD, NEW, DUP, LDC, "<init>", ATHROW, NOP, NOP, NOP, ATHROW))
+
+ // GenBCode introduces an ATHROW after the invocation of C.nt, see BCodeBodyBuilder.adapt
+ // NOTE: DCE is enabled by default and gets rid of the redundant code (tested below)
+ assertSameSummary(getSingleMethod(c, "t4"), List(
+ ALOAD, ALOAD, "nt", ATHROW, NOP, NOP, NOP, ATHROW))
+
+ val List(cDCE) = compileClasses(dceCompiler)(code)
+ assertSameSummary(getSingleMethod(cDCE, "t3"), List(ALOAD, NEW, DUP, LDC, "<init>", ATHROW))
+ assertSameSummary(getSingleMethod(cDCE, "t4"), List(ALOAD, ALOAD, "nt", ATHROW))
+ }
}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/UnusedLocalVariablesTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/UnusedLocalVariablesTest.scala
index 769736669b..4f71df1822 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/UnusedLocalVariablesTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/UnusedLocalVariablesTest.scala
@@ -15,7 +15,7 @@ import ASMConverters._
import scala.tools.testing.ClearAfterClass
object UnusedLocalVariablesTest extends ClearAfterClass.Clearable {
- var dceCompiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:unreachable-code")
+ var dceCompiler = newCompiler(extraArgs = "-Yopt:unreachable-code")
def clear(): Unit = { dceCompiler = null }
}
diff --git a/test/junit/scala/tools/nsc/classpath/AggregateFlatClassPathTest.scala b/test/junit/scala/tools/nsc/classpath/AggregateClassPathTest.scala
index 9a004d5e0e..a7aca31ee3 100644
--- a/test/junit/scala/tools/nsc/classpath/AggregateFlatClassPathTest.scala
+++ b/test/junit/scala/tools/nsc/classpath/AggregateClassPathTest.scala
@@ -10,6 +10,7 @@ import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import scala.reflect.io.VirtualFile
import scala.tools.nsc.io.AbstractFile
+import scala.tools.nsc.util.ClassPath
/**
* Tests whether AggregateFlatClassPath returns correct entries taken from
@@ -17,14 +18,14 @@ import scala.tools.nsc.io.AbstractFile
* (in the case of the repeated entry for a class or a source it returns the first one).
*/
@RunWith(classOf[JUnit4])
-class AggregateFlatClassPathTest {
+class AggregateClassPathTest {
- private class TestFlatClassPath extends FlatClassPath {
+ private abstract class TestClassPathBase extends ClassPath {
override def packages(inPackage: String): Seq[PackageEntry] = unsupported
override def sources(inPackage: String): Seq[SourceFileEntry] = unsupported
override def classes(inPackage: String): Seq[ClassFileEntry] = unsupported
- override def list(inPackage: String): FlatClassPathEntries = unsupported
+ override def list(inPackage: String): ClassPathEntries = unsupported
override def findClassFile(name: String): Option[AbstractFile] = unsupported
override def asClassPathStrings: Seq[String] = unsupported
@@ -32,7 +33,7 @@ class AggregateFlatClassPathTest {
override def asURLs: Seq[URL] = unsupported
}
- private case class TestClassPath(virtualPath: String, classesInPackage: EntryNamesInPackage*) extends TestFlatClassPath {
+ private case class TestClassPath(virtualPath: String, classesInPackage: EntryNamesInPackage*) extends TestClassPathBase {
override def classes(inPackage: String): Seq[ClassFileEntry] =
for {
@@ -43,10 +44,10 @@ class AggregateFlatClassPathTest {
override def sources(inPackage: String): Seq[SourceFileEntry] = Nil
// we'll ignore packages
- override def list(inPackage: String): FlatClassPathEntries = FlatClassPathEntries(Nil, classes(inPackage))
+ override def list(inPackage: String): ClassPathEntries = ClassPathEntries(Nil, classes(inPackage))
}
- private case class TestSourcePath(virtualPath: String, sourcesInPackage: EntryNamesInPackage*) extends TestFlatClassPath {
+ private case class TestSourcePath(virtualPath: String, sourcesInPackage: EntryNamesInPackage*) extends TestClassPathBase {
override def sources(inPackage: String): Seq[SourceFileEntry] =
for {
@@ -57,7 +58,7 @@ class AggregateFlatClassPathTest {
override def classes(inPackage: String): Seq[ClassFileEntry] = Nil
// we'll ignore packages
- override def list(inPackage: String): FlatClassPathEntries = FlatClassPathEntries(Nil, sources(inPackage))
+ override def list(inPackage: String): ClassPathEntries = ClassPathEntries(Nil, sources(inPackage))
}
private case class EntryNamesInPackage(inPackage: String)(val names: String*)
@@ -88,7 +89,7 @@ class AggregateFlatClassPathTest {
private def virtualFile(pathPrefix: String, inPackage: String, fileName: String, extension: String) = {
val packageDirs =
- if (inPackage == FlatClassPath.RootPackage) ""
+ if (inPackage == ClassPath.RootPackage) ""
else inPackage.split('.').mkString("/", "/", "")
new VirtualFile(fileName + extension, s"$pathPrefix$packageDirs/$fileName$extension")
}
@@ -101,12 +102,12 @@ class AggregateFlatClassPathTest {
TestSourcePath(dir2, EntryNamesInPackage(pkg3)("J", "K", "L"))
)
- AggregateFlatClassPath(partialClassPaths)
+ AggregateClassPath(partialClassPaths)
}
@Test
def testGettingPackages: Unit = {
- case class ClassPathWithPackages(packagesInPackage: EntryNamesInPackage*) extends TestFlatClassPath {
+ case class ClassPathWithPackages(packagesInPackage: EntryNamesInPackage*) extends TestClassPathBase {
override def packages(inPackage: String): Seq[PackageEntry] =
packagesInPackage.find(_.inPackage == inPackage).map(_.names).getOrElse(Nil) map PackageEntryImpl
}
@@ -115,7 +116,7 @@ class AggregateFlatClassPathTest {
ClassPathWithPackages(EntryNamesInPackage(pkg1)("pkg1.c", "pkg1.b", "pkg1.a"),
EntryNamesInPackage(pkg2)("pkg2.d", "pkg2.a", "pkg2.e"))
)
- val cp = AggregateFlatClassPath(partialClassPaths)
+ val cp = AggregateClassPath(partialClassPaths)
val packagesInPkg1 = Seq("pkg1.a", "pkg1.d", "pkg1.f", "pkg1.c", "pkg1.b")
assertEquals(packagesInPkg1, cp.packages(pkg1).map(_.name))
@@ -156,7 +157,7 @@ class AggregateFlatClassPathTest {
TestClassPath(dir4, EntryNamesInPackage(pkg2)("A", "H", "I")),
TestClassPath(dir2, EntryNamesInPackage(pkg3)("J", "K", "L"))
)
- val cp = AggregateFlatClassPath(partialClassPaths)
+ val cp = AggregateClassPath(partialClassPaths)
val sourcesInPkg1 = Seq(sourceFileEntry(dir2, pkg1, "C"),
sourceFileEntry(dir2, pkg1, "B"),
@@ -190,7 +191,7 @@ class AggregateFlatClassPathTest {
)
assertEquals(classesAndSourcesInPkg1, cp.list(pkg1).classesAndSources)
- assertEquals(FlatClassPathEntries(Nil, Nil), cp.list(nonexistingPkg))
+ assertEquals(ClassPathEntries(Nil, Nil), cp.list(nonexistingPkg))
}
@Test
diff --git a/test/junit/scala/tools/nsc/classpath/FlatClassPathResolverTest.scala b/test/junit/scala/tools/nsc/classpath/PathResolverBaseTest.scala
index 5dee488285..d3d4289d8b 100644
--- a/test/junit/scala/tools/nsc/classpath/FlatClassPathResolverTest.scala
+++ b/test/junit/scala/tools/nsc/classpath/PathResolverBaseTest.scala
@@ -9,20 +9,17 @@ import org.junit._
import org.junit.rules.TemporaryFolder
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
-import scala.annotation.tailrec
-import scala.tools.nsc.io.AbstractFile
import scala.tools.nsc.util.ClassPath
import scala.tools.nsc.Settings
-import scala.tools.util.FlatClassPathResolver
import scala.tools.util.PathResolver
@RunWith(classOf[JUnit4])
-class FlatClassPathResolverTest {
+class PathResolverBaseTest {
val tempDir = new TemporaryFolder()
- private val packagesToTest = List(FlatClassPath.RootPackage, "scala", "scala.reflect", "scala.reflect.io")
- private val classFilesToFind = List("scala.tools.util.FlatClassPathResolver",
+ private val packagesToTest = List(ClassPath.RootPackage, "scala", "scala.reflect", "scala.reflect.io")
+ private val classFilesToFind = List("scala.tools.util.PathResolver",
"scala.reflect.io.AbstractFile",
"scala.collection.immutable.List",
"scala.Option",
@@ -60,7 +57,7 @@ class FlatClassPathResolverTest {
def deleteTempDir: Unit = tempDir.delete()
private def createFlatClassPath(settings: Settings) =
- new FlatClassPathResolver(settings).result
+ new PathResolver(settings).result
@Test
def testEntriesFromListOperationAgainstSeparateMethods: Unit = {
@@ -70,7 +67,7 @@ class FlatClassPathResolverTest {
val packages = classPath.packages(inPackage)
val classes = classPath.classes(inPackage)
val sources = classPath.sources(inPackage)
- val FlatClassPathEntries(packagesFromList, classesAndSourcesFromList) = classPath.list(inPackage)
+ val ClassPathEntries(packagesFromList, classesAndSourcesFromList) = classPath.list(inPackage)
val packageNames = packages.map(_.name).sorted
val packageNamesFromList = packagesFromList.map(_.name).sorted
@@ -96,52 +93,6 @@ class FlatClassPathResolverTest {
}
@Test
- def testCreatedEntriesAgainstRecursiveClassPath: Unit = {
- val flatClassPath = createFlatClassPath(settings)
- val recursiveClassPath = new PathResolver(settings).result
-
- def compareEntriesInPackage(inPackage: String): Unit = {
-
- @tailrec
- def traverseToPackage(packageNameParts: Seq[String], cp: ClassPath[AbstractFile]): ClassPath[AbstractFile] = {
- packageNameParts match {
- case Nil => cp
- case h :: t =>
- cp.packages.find(_.name == h) match {
- case Some(nestedCp) => traverseToPackage(t, nestedCp)
- case _ => throw new Exception(s"There's no package $inPackage in recursive classpath - error when searching for '$h'")
- }
- }
- }
-
- val packageNameParts = if (inPackage == FlatClassPath.RootPackage) Nil else inPackage.split('.').toList
- val recursiveClassPathInPackage = traverseToPackage(packageNameParts, recursiveClassPath)
-
- val flatCpPackages = flatClassPath.packages(inPackage).map(_.name)
- val pkgPrefix = PackageNameUtils.packagePrefix(inPackage)
- val recursiveCpPackages = recursiveClassPathInPackage.packages.map(pkgPrefix + _.name)
- assertEquals(s"Packages in package '$inPackage' on flat cp should be the same as on the recursive cp",
- recursiveCpPackages, flatCpPackages)
-
- val flatCpSources = flatClassPath.sources(inPackage).map(_.name).sorted
- val recursiveCpSources = recursiveClassPathInPackage.classes
- .filter(_.source.nonEmpty)
- .map(_.name).sorted
- assertEquals(s"Source entries in package '$inPackage' on flat cp should be the same as on the recursive cp",
- recursiveCpSources, flatCpSources)
-
- val flatCpClasses = flatClassPath.classes(inPackage).map(_.name).sorted
- val recursiveCpClasses = recursiveClassPathInPackage.classes
- .filter(_.binary.nonEmpty)
- .map(_.name).sorted
- assertEquals(s"Class entries in package '$inPackage' on flat cp should be the same as on the recursive cp",
- recursiveCpClasses, flatCpClasses)
- }
-
- packagesToTest foreach compareEntriesInPackage
- }
-
- @Test
def testFindClassFile: Unit = {
val classPath = createFlatClassPath(settings)
classFilesToFind foreach { className =>
diff --git a/test/junit/scala/tools/nsc/settings/ScalaVersionTest.scala b/test/junit/scala/tools/nsc/settings/ScalaVersionTest.scala
index 77a2da828e..3717f80362 100644
--- a/test/junit/scala/tools/nsc/settings/ScalaVersionTest.scala
+++ b/test/junit/scala/tools/nsc/settings/ScalaVersionTest.scala
@@ -13,6 +13,57 @@ class ScalaVersionTest {
@Test def versionUnparse() {
val v = "2.11.3"
- assertEquals(ScalaVersion(v).unparse, v)
+ assertEquals(v, ScalaVersion(v).unparse)
+ assertEquals("2.11.3-RC4", ScalaVersion("2.11.3-rc4").unparse)
+ }
+
+ // SI-9167
+ @Test def `version parses with rigor`() {
+ import settings.{ SpecificScalaVersion => V }
+ import ScalaVersion._
+
+ // no-brainers
+ assertEquals(V(2,11,7,Final), ScalaVersion("2.11.7"))
+ assertEquals(V(2,11,7,Final), ScalaVersion("2.11.7-FINAL"))
+ assertEquals(V(2,11,7,Milestone(3)), ScalaVersion("2.11.7-M3"))
+ assertEquals(V(2,11,7,RC(3)), ScalaVersion("2.11.7-RC3"))
+ assertEquals(V(2,11,7,Development("devbuild")), ScalaVersion("2.11.7-devbuild"))
+
+ // partial-brainers
+ assertEquals(V(2,11,7,Milestone(3)), ScalaVersion("2.11.7-m3"))
+ assertEquals(V(2,11,7,RC(3)), ScalaVersion("2.11.7-rc3"))
+ assertEquals(V(2,11,7,Development("maybegood")), ScalaVersion("2.11.7-maybegood"))
+ assertEquals(V(2,11,7,Development("RCCola")), ScalaVersion("2.11.7-RCCola"))
+ assertEquals(V(2,11,7,Development("RC1.5")), ScalaVersion("2.11.7-RC1.5"))
+ assertEquals(V(2,11,7,Development("")), ScalaVersion("2.11.7-"))
+ assertEquals(V(2,11,7,Development("0.5")), ScalaVersion("2.11.7-0.5"))
+ assertEquals(V(2,11,7,Development("devbuild\nSI-9167")), ScalaVersion("2.11.7-devbuild\nSI-9167"))
+ assertEquals(V(2,11,7,Development("final")), ScalaVersion("2.11.7-final"))
+
+ // oh really
+ assertEquals(NoScalaVersion, ScalaVersion("none"))
+ assertEquals(AnyScalaVersion, ScalaVersion("any"))
+
+ assertThrows[NumberFormatException] { ScalaVersion("2.11.7.2") }
+ assertThrows[NumberFormatException] { ScalaVersion("2.11.7.beta") }
+ assertThrows[NumberFormatException] { ScalaVersion("2.x.7") }
+ assertThrows[NumberFormatException] { ScalaVersion("2.-11.7") }
+ assertThrows[NumberFormatException] { ScalaVersion("2. ") }
+ assertThrows[NumberFormatException] { ScalaVersion("2.1 .7") }
+ assertThrows[NumberFormatException] { ScalaVersion("2.") }
+ assertThrows[NumberFormatException] { ScalaVersion("2..") }
+ assertThrows[NumberFormatException] { ScalaVersion("2...") }
+ assertThrows[NumberFormatException] { ScalaVersion("2-") }
+ assertThrows[NumberFormatException] { ScalaVersion("2-.") } // scalacheck territory
+ assertThrows[NumberFormatException] { ScalaVersion("any.7") }
+
+ assertThrows[NumberFormatException] ( ScalaVersion("2.11-ok"), _ ==
+ "Bad version (2.11-ok) not major[.minor[.revision[-suffix]]]" )
+
+ }
+
+ // SI-9377
+ @Test def `missing version is as good as none`() {
+ assertEquals(NoScalaVersion, ScalaVersion(""))
}
}
diff --git a/test/junit/scala/tools/nsc/settings/SettingsTest.scala b/test/junit/scala/tools/nsc/settings/SettingsTest.scala
index 96f83c4c2f..01a2351011 100644
--- a/test/junit/scala/tools/nsc/settings/SettingsTest.scala
+++ b/test/junit/scala/tools/nsc/settings/SettingsTest.scala
@@ -26,16 +26,16 @@ class SettingsTest {
assertThrows[IllegalArgumentException](check("-Ytest-setting:rubbish"))
}
- @Test def userSettingsHavePrecedenceOverOptimize() {
+ @Test def userSettingsHavePrecedenceOverExperimental() {
def check(args: String*): MutableSettings#BooleanSetting = {
val s = new MutableSettings(msg => throw new IllegalArgumentException(msg))
val (ok, residual) = s.processArguments(args.toList, processAll = true)
assert(residual.isEmpty)
- s.inline // among -optimize
+ s.YmethodInfer // among -Xexperimental
}
- assertTrue(check("-optimise").value)
- assertFalse(check("-optimise", "-Yinline:false").value)
- assertFalse(check("-Yinline:false", "-optimise").value)
+ assertTrue(check("-Xexperimental").value)
+ assertFalse(check("-Xexperimental", "-Yinfer-argument-types:false").value)
+ assertFalse(check("-Yinfer-argument-types:false", "-Xexperimental").value)
}
// for the given args, select the desired setting
@@ -172,12 +172,12 @@ class SettingsTest {
assert(residual.isEmpty)
assertTrue(s.source.value == ScalaVersion(expected))
}
- check(expected = "2.11.0") // default
+ check(expected = "2.12.0") // default
check(expected = "2.11.0", "-Xsource:2.11")
check(expected = "2.10", "-Xsource:2.10.0")
check(expected = "2.12", "-Xsource:2.12")
assertThrows[IllegalArgumentException](check(expected = "2.11", "-Xsource"), _ == "-Xsource requires an argument, the syntax is -Xsource:<version>")
assertThrows[IllegalArgumentException](check(expected = "2.11", "-Xsource", "2.11"), _ == "-Xsource requires an argument, the syntax is -Xsource:<version>")
- assertThrows[IllegalArgumentException](check(expected = "2.11", "-Xsource:2.invalid"), _ contains "There was a problem parsing 2.invalid")
+ assertThrows[IllegalArgumentException](check(expected = "2.11", "-Xsource:2.invalid"), _ contains "Bad version (2.invalid)")
}
}
diff --git a/test/junit/scala/tools/nsc/symtab/FlagsTest.scala b/test/junit/scala/tools/nsc/symtab/FlagsTest.scala
index fc0e8b0f6b..96eae38011 100644
--- a/test/junit/scala/tools/nsc/symtab/FlagsTest.scala
+++ b/test/junit/scala/tools/nsc/symtab/FlagsTest.scala
@@ -33,7 +33,6 @@ class FlagsTest {
def testTimedFlags(): Unit = {
testLate(lateDEFERRED, _.isDeferred)
testLate(lateFINAL, _.isFinal)
- testLate(lateINTERFACE, _.isInterface)
testLate(lateMETHOD, _.isMethod)
testLate(lateMODULE, _.isModule)
testNot(PROTECTED | notPROTECTED, _.isProtected)
@@ -86,4 +85,11 @@ class FlagsTest {
assertEquals(withFlagMask(AllFlags)(sym.setFlag(lateFlags).flags), lateFlags | lateable)
}
+
+ @Test
+ def javaClassMirrorAnnotationFlag(): Unit = {
+ import scala.reflect.runtime.universe._
+ val dep = typeOf[java.lang.Deprecated].typeSymbol
+ assertTrue(dep.isJavaAnnotation && dep.isJava)
+ }
}
diff --git a/test/junit/scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala b/test/junit/scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala
index f0f20acf07..8cc7aefdd3 100644
--- a/test/junit/scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala
+++ b/test/junit/scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala
@@ -2,10 +2,7 @@ package scala.tools.nsc
package symtab
import scala.reflect.ClassTag
-import scala.reflect.internal.{Phase, NoPhase, SomePhase}
-import scala.tools.nsc.classpath.FlatClassPath
-import scala.tools.nsc.settings.ClassPathRepresentationType
-import scala.tools.util.FlatClassPathResolver
+import scala.reflect.internal.{NoPhase, Phase, SomePhase}
import scala.tools.util.PathResolver
import util.ClassPath
import io.AbstractFile
@@ -30,8 +27,7 @@ class SymbolTableForUnitTesting extends SymbolTable {
override def isCompilerUniverse: Boolean = true
- def classPath = platform.classPath
- def flatClassPath: FlatClassPath = platform.flatClassPath
+ def classPath: ClassPath = platform.classPath
object platform extends backend.Platform {
val symbolTable: SymbolTableForUnitTesting.this.type = SymbolTableForUnitTesting.this
@@ -39,22 +35,12 @@ class SymbolTableForUnitTesting extends SymbolTable {
def platformPhases: List[SubComponent] = Nil
- lazy val classPath: ClassPath[AbstractFile] = {
- assert(settings.YclasspathImpl.value == ClassPathRepresentationType.Recursive,
- "It's not possible to use the recursive classpath representation, when it's not the chosen classpath scanning method")
- new PathResolver(settings).result
- }
-
- private[nsc] lazy val flatClassPath: FlatClassPath = {
- assert(settings.YclasspathImpl.value == ClassPathRepresentationType.Flat,
- "It's not possible to use the flat classpath representation, when it's not the chosen classpath scanning method")
- new FlatClassPathResolver(settings).result
- }
+ private[nsc] lazy val classPath: ClassPath = new PathResolver(settings).result
def isMaybeBoxed(sym: Symbol): Boolean = ???
def needCompile(bin: AbstractFile, src: AbstractFile): Boolean = ???
def externalEquals: Symbol = ???
- def updateClassPath(subst: Map[ClassPath[AbstractFile], ClassPath[AbstractFile]]): Unit = ???
+ def updateClassPath(subst: Map[ClassPath, ClassPath]): Unit = ???
}
object loaders extends symtab.SymbolLoaders {
@@ -69,10 +55,7 @@ class SymbolTableForUnitTesting extends SymbolTable {
class GlobalMirror extends Roots(NoSymbol) {
val universe: SymbolTableForUnitTesting.this.type = SymbolTableForUnitTesting.this
- def rootLoader: LazyType = settings.YclasspathImpl.value match {
- case ClassPathRepresentationType.Flat => new loaders.PackageLoaderUsingFlatClassPath(FlatClassPath.RootPackage, flatClassPath)
- case ClassPathRepresentationType.Recursive => new loaders.PackageLoader(classPath)
- }
+ def rootLoader: LazyType = new loaders.PackageLoader(ClassPath.RootPackage, classPath)
override def toString = "compiler mirror"
}
@@ -119,7 +102,9 @@ class SymbolTableForUnitTesting extends SymbolTable {
}
phasesArray
}
- lazy val treeInfo: scala.reflect.internal.TreeInfo{val global: SymbolTableForUnitTesting.this.type} = ???
+ lazy val treeInfo = new scala.reflect.internal.TreeInfo {
+ val global: SymbolTableForUnitTesting.this.type = SymbolTableForUnitTesting.this
+ }
val currentFreshNameCreator = new reflect.internal.util.FreshNameCreator
diff --git a/test/junit/scala/tools/nsc/transform/delambdafy/DelambdafyTest.scala b/test/junit/scala/tools/nsc/transform/delambdafy/DelambdafyTest.scala
index 010078e28a..e4bf038f32 100644
--- a/test/junit/scala/tools/nsc/transform/delambdafy/DelambdafyTest.scala
+++ b/test/junit/scala/tools/nsc/transform/delambdafy/DelambdafyTest.scala
@@ -52,7 +52,7 @@ object Delambdafy {
val srcFile = makeSourceFile(codeForMultiOutput, "delambdafyTest.scala")
val outDir = AbstractFile.getDirectory(TempDir.createTempDir())
val outDirPath = outDir.canonicalPath
- val extraArgs = "-Ybackend:GenBCode -Ydelambdafy:method"
+ val extraArgs = "-Ydelambdafy:method"
val argsWithOutDir = extraArgs + s" -d $outDirPath -cp $outDirPath"
val compiler = newCompilerWithoutVirtualOutdir(extraArgs = argsWithOutDir)
compiler.settings.outputDirs.add(srcFile.file, outDir)
@@ -63,7 +63,7 @@ object Delambdafy {
outDir.delete()
classfiles
}
-
+
@Test
def shouldFindOutputFoldersForAllPromotedLambdasAsMethod(): Unit = {
val actual = compileToMultipleOutputWithDelamdbafyMethod()
diff --git a/test/junit/scala/tools/nsc/transform/patmat/PatmatBytecodeTest.scala b/test/junit/scala/tools/nsc/transform/patmat/PatmatBytecodeTest.scala
new file mode 100644
index 0000000000..ac558e2e21
--- /dev/null
+++ b/test/junit/scala/tools/nsc/transform/patmat/PatmatBytecodeTest.scala
@@ -0,0 +1,195 @@
+package scala.tools.nsc
+package transform.patmat
+
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.Test
+import scala.tools.asm.Opcodes._
+import org.junit.Assert._
+
+import scala.tools.nsc.backend.jvm.AsmUtils._
+import scala.tools.nsc.backend.jvm.CodeGenTools
+import scala.tools.testing.AssertUtil._
+
+import CodeGenTools._
+import scala.tools.partest.ASMConverters
+import ASMConverters._
+import scala.tools.testing.ClearAfterClass
+
+object PatmatBytecodeTest extends ClearAfterClass.Clearable {
+ var compiler = newCompiler()
+ var optCompiler = newCompiler(extraArgs = "-Yopt:l:project")
+ def clear(): Unit = { compiler = null; optCompiler = null }
+}
+
+@RunWith(classOf[JUnit4])
+class PatmatBytecodeTest extends ClearAfterClass {
+ ClearAfterClass.stateToClear = PatmatBytecodeTest
+
+ val compiler = PatmatBytecodeTest.compiler
+ val optCompiler = PatmatBytecodeTest.optCompiler
+
+ @Test
+ def t6956(): Unit = {
+ val code =
+ """class C {
+ | private[this] final val ONE = 1
+ |
+ | def s1(i: Byte): Int = i match {
+ | case ONE => 1
+ | case 2 => 2
+ | case 3 => 3
+ | case _ => 0
+ | }
+ |
+ | def s2(i: Byte): Int = i match {
+ | case 1 => 1
+ | case 2 => 2
+ | case 3 => 3
+ | case _ => 0
+ | }
+ |}
+ """.stripMargin
+
+ val List(c) = compileClasses(compiler)(code)
+ assert(getSingleMethod(c, "s1").instructions.count(_.opcode == TABLESWITCH) == 1, textify(c))
+ assert(getSingleMethod(c, "s2").instructions.count(_.opcode == TABLESWITCH) == 1, textify(c))
+ }
+
+ @Test
+ def t6955(): Unit = {
+ val code =
+ """class C {
+ | type Tag = Byte
+ |
+ | def s1(i: Tag): Int = i match { // notice type of i is Tag = Byte
+ | case 1 => 1
+ | case 2 => 2
+ | case 3 => 3
+ | case _ => 0
+ | }
+ |
+ | // this worked before, should keep working
+ | def s2(i: Byte): Int = i match {
+ | case 1 => 1
+ | case 2 => 2
+ | case 3 => 3
+ | case _ => 0
+ | }
+ |}
+ """.stripMargin
+
+ val List(c) = compileClasses(compiler)(code)
+ assert(getSingleMethod(c, "s1").instructions.count(_.opcode == TABLESWITCH) == 1, textify(c))
+ assert(getSingleMethod(c, "s2").instructions.count(_.opcode == TABLESWITCH) == 1, textify(c))
+ }
+
+ @Test
+ def optNoPrimitiveTypetest(): Unit = {
+ val code =
+ """case class Foo(x: Int, y: String)
+ |class C {
+ | def a = Foo(1, "a") match {
+ | case Foo(_: Int, y) => y
+ | }
+ |}
+ """.stripMargin
+ val c = compileClasses(optCompiler)(code).head
+
+ assertSameSummary(getSingleMethod(c, "a"), List(
+ NEW, DUP, ICONST_1, LDC, "<init>",
+ "y", ARETURN))
+ }
+
+ @Test
+ def optNoNullCheck(): Unit = {
+ val code =
+ """case class Foo(x: Any)
+ |class C {
+ | def a = (Foo(1): Any) match {
+ | case Foo(_: String) =>
+ | }
+ |}
+ """.stripMargin
+ val c = compileClasses(optCompiler)(code).head
+ assert(!getSingleMethod(c, "a").instructions.exists(i => i.opcode == IFNULL || i.opcode == IFNONNULL), textify(findAsmMethod(c, "a")))
+ }
+
+ @Test
+ def optNoLoacalForUnderscore(): Unit = {
+ val code =
+ """case class Foo(x: Any, y: String)
+ |class C {
+ | def a = (Foo(1, "a"): @unchecked) match {
+ | case Foo(_: String, y) => y
+ | }
+ |}
+ """.stripMargin
+ val c = compileClasses(optCompiler)(code).head
+ assertSameSummary(getSingleMethod(c, "a"), List(
+ NEW, DUP, ICONST_1, "boxToInteger", LDC, "<init>", ASTORE /*1*/,
+ ALOAD /*1*/, "y", ASTORE /*2*/,
+ ALOAD /*1*/, "x", INSTANCEOF, IFNE /*R*/,
+ NEW, DUP, ALOAD /*1*/, "<init>", ATHROW,
+ /*R*/ -1, ALOAD /*2*/, ARETURN))
+ }
+
+ @Test
+ def t6941(): Unit = {
+ val code =
+ """class C {
+ | def a(xs: List[Int]) = xs match {
+ | case x :: _ => x
+ | }
+ | def b(xs: List[Int]) = xs match {
+ | case xs: ::[Int] => xs.head
+ | }
+ |}
+ """.stripMargin
+ val c = compileClasses(optCompiler)(code, allowMessage = _.msg.contains("may not be exhaustive")).head
+
+ val expected = List(
+ ALOAD /*1*/ , INSTANCEOF /*::*/ , IFEQ /*A*/ ,
+ ALOAD, CHECKCAST /*::*/ , "head", "unboxToInt",
+ ISTORE, GOTO /*B*/ ,
+ -1 /*A*/ , NEW /*MatchError*/ , DUP, ALOAD /*1*/ , "<init>", ATHROW,
+ -1 /*B*/ , ILOAD, IRETURN)
+
+ assertSameSummary(getSingleMethod(c, "a"), expected)
+ assertSameSummary(getSingleMethod(c, "b"), expected)
+ }
+
+ @Test
+ def valPatterns(): Unit = {
+ val code =
+ """case class C(a: Any, b: Int) {
+ | def tplCall = ("hi", 3)
+ | @inline final def tplInline = (true, 'z')
+ |
+ | def t1 = { val (a, b) = (1, 2); a + b }
+ | def t2 = { val (a, _) = (1, 3); a }
+ | def t3 = { val (s, i) = tplCall; s.length + i }
+ | def t4 = { val (_, i) = tplCall; i }
+ | def t5 = { val (b, c) = tplInline; b || c == 'e' }
+ | def t6 = { val (_, c) = tplInline; c }
+ |
+ | def t7 = { val C(s: String, b) = this; s.length + b }
+ | def t8 = { val C(_, b) = this; b }
+ | def t9 = { val C(a, _) = C("hi", 23); a.toString }
+ |}
+ """.stripMargin
+ val List(c, cMod) = compileClasses(optCompiler)(code)
+ assertSameSummary(getSingleMethod(c, "t1"), List(ICONST_1, ICONST_2, IADD, IRETURN))
+ assertSameSummary(getSingleMethod(c, "t2"), List(ICONST_1, IRETURN))
+ assertInvokedMethods(getSingleMethod(c, "t3"), List("C.tplCall", "scala/Tuple2._1", "scala/Tuple2._2$mcI$sp", "scala/MatchError.<init>", "java/lang/String.length"))
+ assertInvokedMethods(getSingleMethod(c, "t4"), List("C.tplCall", "scala/Tuple2._2$mcI$sp", "scala/MatchError.<init>"))
+ assertNoInvoke(getSingleMethod(c, "t5"))
+ assertSameSummary(getSingleMethod(c, "t6"), List(BIPUSH, IRETURN))
+
+ // MatchError reachable because of the type pattern `s: String`
+ assertInvokedMethods(getSingleMethod(c, "t7"), List("C.a", "C.b", "scala/MatchError.<init>", "java/lang/String.length"))
+ assertSameSummary(getSingleMethod(c, "t8"), List(ALOAD, "b", IRETURN))
+ // C allocation not eliminated - constructor may have side-effects.
+ assertSameSummary(getSingleMethod(c, "t9"), List(NEW, DUP, LDC, BIPUSH, "<init>", "a", "toString", ARETURN))
+ }
+}
diff --git a/test/junit/scala/tools/nsc/util/ClassPathImplComparator.scala b/test/junit/scala/tools/nsc/util/ClassPathImplComparator.scala
deleted file mode 100644
index f2926e3e17..0000000000
--- a/test/junit/scala/tools/nsc/util/ClassPathImplComparator.scala
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (c) 2014 Contributor. All rights reserved.
- */
-package scala.tools.nsc.util
-
-import scala.reflect.io.AbstractFile
-import scala.tools.nsc.Settings
-import scala.tools.nsc.settings.ClassPathRepresentationType
-import scala.tools.util.PathResolverFactory
-
-/**
- * Simple application to compare efficiency of the recursive and the flat classpath representations
- */
-object ClassPathImplComparator {
-
- private class TestSettings extends Settings {
- val checkClasses = PathSetting("-checkClasses", "Specify names of classes which should be found separated with ;", "")
- val requiredIterations = IntSetting("-requiredIterations",
- "Repeat tests specified number of times (to check e.g. impact of caches)", 1, Some((1, Int.MaxValue)), (_: String) => None)
- val cpCreationRepetitions = IntSetting("-cpCreationRepetitions",
- "Repeat tests specified number of times (to check e.g. impact of caches)", 1, Some((1, Int.MaxValue)), (_: String) => None)
- val cpLookupRepetitions = IntSetting("-cpLookupRepetitions",
- "Repeat tests specified number of times (to check e.g. impact of caches)", 1, Some((1, Int.MaxValue)), (_: String) => None)
- }
-
- private class DurationStats(name: String) {
- private var sum = 0L
- private var iterations = 0
-
- def noteMeasuredTime(millis: Long): Unit = {
- sum += millis
- iterations += 1
- }
-
- def printResults(): Unit = {
- val avg = if (iterations == 0) 0 else sum.toDouble / iterations
- println(s"$name - total duration: $sum ms; iterations: $iterations; avg: $avg ms")
- }
- }
-
- private lazy val defaultClassesToFind = List(
- "scala.collection.immutable.List",
- "scala.Option",
- "scala.Int",
- "scala.collection.immutable.Vector",
- "scala.util.hashing.MurmurHash3"
- )
-
- private val oldCpCreationStats = new DurationStats("Old classpath - create")
- private val oldCpSearchingStats = new DurationStats("Old classpath - search")
-
- private val flatCpCreationStats = new DurationStats("Flat classpath - create")
- private val flatCpSearchingStats = new DurationStats("Flat classpath - search")
-
- def main(args: Array[String]): Unit = {
-
- if (args contains "-help")
- usage()
- else {
- val oldCpSettings = loadSettings(args.toList, ClassPathRepresentationType.Recursive)
- val flatCpSettings = loadSettings(args.toList, ClassPathRepresentationType.Flat)
-
- val classesToCheck = oldCpSettings.checkClasses.value
- val classesToFind =
- if (classesToCheck.isEmpty) defaultClassesToFind
- else classesToCheck.split(";").toList
-
- def doTest(classPath: => ClassFileLookup[AbstractFile], cpCreationStats: DurationStats, cpSearchingStats: DurationStats,
- cpCreationRepetitions: Int, cpLookupRepetitions: Int)= {
-
- def createClassPaths() = (1 to cpCreationRepetitions).map(_ => classPath).last
- def testClassLookup(cp: ClassFileLookup[AbstractFile]): Boolean = (1 to cpCreationRepetitions).foldLeft(true) {
- case (a, _) => a && checkExistenceOfClasses(classesToFind)(cp)
- }
-
- val cp = withMeasuredTime("Creating classpath", createClassPaths(), cpCreationStats)
- val result = withMeasuredTime("Searching for specified classes", testClassLookup(cp), cpSearchingStats)
- println(s"The end of the test case. All expected classes found = $result \n")
- }
-
- (1 to oldCpSettings.requiredIterations.value) foreach { iteration =>
- if (oldCpSettings.requiredIterations.value > 1)
- println(s"Iteration no $iteration")
-
- println("Recursive (old) classpath representation:")
- doTest(PathResolverFactory.create(oldCpSettings).result, oldCpCreationStats, oldCpSearchingStats,
- oldCpSettings.cpCreationRepetitions.value, oldCpSettings.cpLookupRepetitions.value)
-
- println("Flat classpath representation:")
- doTest(PathResolverFactory.create(flatCpSettings).result, flatCpCreationStats, flatCpSearchingStats,
- flatCpSettings.cpCreationRepetitions.value, flatCpSettings.cpLookupRepetitions.value)
- }
-
- if (oldCpSettings.requiredIterations.value > 1) {
- println("\nOld classpath - summary")
- oldCpCreationStats.printResults()
- oldCpSearchingStats.printResults()
-
- println("\nFlat classpath - summary")
- flatCpCreationStats.printResults()
- flatCpSearchingStats.printResults()
- }
- }
- }
-
- /**
- * Prints usage information
- */
- private def usage(): Unit =
- println("""Use classpath and sourcepath options like in the case of e.g. 'scala' command.
- | There are also two additional options:
- | -checkClasses <semicolon separated class names> Specify names of classes which should be found
- | -requiredIterations <int value> Repeat tests specified count of times (to check e.g. impact of caches)
- | Note: Option -YclasspathImpl will be set automatically for each case.
- """.stripMargin.trim)
-
- private def loadSettings(args: List[String], implType: String) = {
- val settings = new TestSettings()
- settings.processArguments(args, processAll = true)
- settings.YclasspathImpl.value = implType
- if (settings.classpath.isDefault)
- settings.classpath.value = sys.props("java.class.path")
- settings
- }
-
- private def withMeasuredTime[T](operationName: String, f: => T, durationStats: DurationStats): T = {
- val startTime = System.currentTimeMillis()
- val res = f
- val elapsed = System.currentTimeMillis() - startTime
- durationStats.noteMeasuredTime(elapsed)
- println(s"$operationName - elapsed $elapsed ms")
- res
- }
-
- private def checkExistenceOfClasses(classesToCheck: Seq[String])(classPath: ClassFileLookup[AbstractFile]): Boolean =
- classesToCheck.foldLeft(true) {
- case (res, classToCheck) =>
- val found = classPath.findClass(classToCheck).isDefined
- if (!found)
- println(s"Class $classToCheck not found") // of course in this case the measured time will be affected by IO operation
- found
- }
-}
diff --git a/test/junit/scala/util/SystemPropertiesTest.scala b/test/junit/scala/util/SystemPropertiesTest.scala
new file mode 100644
index 0000000000..38e830eb88
--- /dev/null
+++ b/test/junit/scala/util/SystemPropertiesTest.scala
@@ -0,0 +1,27 @@
+package scala.util
+
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.Test
+import org.junit.Assert._
+
+@RunWith(classOf[JUnit4])
+class SystemPropertiesTest {
+ @Test
+ def filterAll(): Unit = {
+ val isEmpty = sys.props.filter(_ => false).size == 0
+ assertTrue("A filter matching nothing should produce an empty result", isEmpty)
+ }
+
+ @Test
+ def filterNone(): Unit = {
+ val isUnchanged = sys.props.filter(_ => true) == sys.props
+ assertTrue("A filter matching everything should not change the result", isUnchanged)
+ }
+
+ @Test
+ def empty(): Unit = {
+ val hasSize0 = sys.props.empty.size == 0
+ assertTrue("SystemProperties.empty should have size of 0", hasSize0)
+ }
+}
diff --git a/test/junit/scala/util/control/ExceptionTest.scala b/test/junit/scala/util/control/ExceptionTest.scala
new file mode 100644
index 0000000000..5211d31839
--- /dev/null
+++ b/test/junit/scala/util/control/ExceptionTest.scala
@@ -0,0 +1,42 @@
+/* __ *\
+** ________ ___ / / ___ Scala API **
+** / __/ __// _ | / / / _ | (c) 2016-2016, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ **
+** /____/\___/_/ |_/____/_/ | | **
+** |/ **
+\* */
+
+package scala.util
+
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.Test
+import org.junit.Assert._
+
+import scala.collection.mutable.ListBuffer
+
+import scala.util.control.Exception._
+
+@RunWith(classOf[JUnit4])
+class ExceptionTest {
+
+ @Test
+ def andFinally(): Unit = {
+
+ locally {
+ val audit = ListBuffer[Int]()
+ val katch = nonFatalCatch[Unit].andFinally(audit append 1)
+ val result = katch(10)
+ assertEquals(result, 10)
+ assertEquals(audit.toList, 1 :: Nil)
+ }
+
+ locally {
+ val audit = ListBuffer[Int]()
+ val katch = nonFatalCatch[Unit].andFinally(audit append 1).andFinally(audit append 2)
+ val result = katch(20)
+ assertEquals(result, 20)
+ assertEquals(audit.toList, 1 :: 2 :: Nil)
+ }
+ }
+} \ No newline at end of file