diff options
Diffstat (limited to 'test/junit')
6 files changed, 426 insertions, 149 deletions
diff --git a/test/junit/scala/StringContextTest.scala b/test/junit/scala/StringContextTest.scala index 7e9e775d58..900852fcc6 100644 --- a/test/junit/scala/StringContextTest.scala +++ b/test/junit/scala/StringContextTest.scala @@ -1,6 +1,8 @@ package scala +import language.implicitConversions + import org.junit.Test import org.junit.Assert._ import org.junit.runner.RunWith @@ -84,4 +86,162 @@ class StringContextTest { // 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" -> "3.400000e+00", + f"${3.4}%e" -> "3.400000e+00", + f"${3.4f : java.lang.Float}%e" -> "3.400000e+00", + f"${3.4 : java.lang.Double}%e" -> "3.400000e+00", + + f"${BigDecimal(3.4)}%e" -> "3.400000e+00", + + f"${new java.math.BigDecimal(3.4)}%e" -> "3.400000e+00", + + f"${3}%e" -> "3.000000e+00", + f"${3L}%e" -> "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%n" -> + """| mind + |------ + |matter + |""".stripMargin, + 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" -> "3.14,3.140000", + + f"z" -> "z" + ) + + for ((f, s) <- ss) assertEquals(s, f) + } } diff --git a/test/junit/scala/collection/mutable/OpenHashMapTest.scala b/test/junit/scala/collection/mutable/OpenHashMapTest.scala new file mode 100644 index 0000000000..9b5c20e01a --- /dev/null +++ b/test/junit/scala/collection/mutable/OpenHashMapTest.scala @@ -0,0 +1,42 @@ +package scala.collection.mutable + +import org.junit.Test +import org.junit.Assert._ +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +/** Tests for [[OpenHashMap]]. */ +@RunWith(classOf[JUnit4]) +class OpenHashMapTest { + /** Test that an [[OpenHashMap]] correctly maintains its internal `deleted` count. */ + @Test + def maintainsDeletedCount { + val m = OpenHashMap.empty[Int, Int] + + // Reflect to get the private `deleted` field's value, which should be zero. + + /* TODO Doesn't work, due to SI-9306. + import scala.reflect.runtime.{universe => ru} + + val mirror = ru.runtimeMirror(m.getClass.getClassLoader) + val mapType = ru.typeOf[OpenHashMap[Int, Int]] + val termSym = mapType.decls + .filterNot { s => s.isMethod } + .filter { s => s.fullName.endsWith("deleted") } + .head.asTerm + + val fieldMirror = mirror.reflect(m).reflectField(termSym) + */ + // Use Java reflection instead for now. + val field = m.getClass.getDeclaredField("deleted") + field.setAccessible(true) + + m.put(0, 0) + m.remove(0) + assertEquals(1, field.getInt(m)) + + m.put(0, 0) // Add an entry with the same key + // TODO assertEquals(0, fieldMirror.get.asInstanceOf[Int]) + assertEquals(0, field.getInt(m)) + } +} diff --git a/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala b/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala index 769236ae49..1f2ec274d3 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala @@ -169,6 +169,9 @@ object CodeGenTools { 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. diff --git a/test/junit/scala/tools/nsc/backend/jvm/DefaultMethodTest.scala b/test/junit/scala/tools/nsc/backend/jvm/DefaultMethodTest.scala index f9a55bb26e..e57e95bac4 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/DefaultMethodTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/DefaultMethodTest.scala @@ -16,8 +16,8 @@ object DefaultMethodTest extends ClearAfterClass.Clearable { } class DefaultMethodTest extends ClearAfterClass { - ClearAfterClass.stateToClear = DirectCompileTest - val compiler = DirectCompileTest.compiler + ClearAfterClass.stateToClear = DefaultMethodTest + val compiler = DefaultMethodTest.compiler @Test def defaultMethodsViaGenBCode(): Unit = { 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 efd88f10c3..3857d3e8ce 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala @@ -50,9 +50,6 @@ class CallGraphTest extends ClearAfterClass { compileClasses(compiler)(code, allowMessage = allowMessage).map(c => byteCodeRepository.classNode(c.name).get) } - def getMethods(c: ClassNode, p: String => Boolean) = c.methods.iterator.asScala.filter(m => p(m.name)).toList.sortBy(_.name) - def getMethod(c: ClassNode, name: String) = getMethods(c, _ == name).head - def callsInMethod(methodNode: MethodNode): List[MethodInsnNode] = methodNode.instructions.iterator.asScala.collect({ case call: MethodInsnNode => call }).toList @@ -121,10 +118,10 @@ class CallGraphTest extends ClearAfterClass { val List(cCls, cMod, dCls, testCls) = compile(code, checkMsg) assert(msgCount == 6, msgCount) - val List(cf1, cf2, cf3, cf4, cf5, cf6, cf7) = getMethods(cCls, _.startsWith("f")) - val List(df1, df3) = getMethods(dCls, _.startsWith("f")) - val g1 = getMethod(cMod, "g1") - val List(t1, t2) = getMethods(testCls, _.startsWith("t")) + 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) @@ -160,7 +157,7 @@ class CallGraphTest extends ClearAfterClass { |} """.stripMargin val List(c) = compile(code) - val m = getMethod(c, "m") + 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") @@ -168,12 +165,6 @@ class CallGraphTest extends ClearAfterClass { checkCallsite(fn, m, forNameMeth, classTp, safeToInline = false, atInline = false, atNoInline = false) } - /** - * NOTE: if this test fails for you when running within the IDE, it's probably because you're - * using 2.12.0-M2 for compilining within the IDE, which doesn't add SAM information to the - * InlineInfo attribute. So the InlineInfo in the classfile for Function1 doesn't say that - * it's a SAM type. The test passes when running with ant (which does a full bootstrap). - */ @Test def checkArgInfos(): Unit = { val code = 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 1108a37266..1e01627969 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.immutable.IntMap -import scala.collection.mutable.ListBuffer -import scala.reflect.internal.util.{NoPosition, 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 @@ -69,6 +63,7 @@ class InlinerTest extends ClearAfterClass { val compiler = InlinerTest.compiler import compiler.genBCode.bTypes._ import compiler.genBCode.bTypes.backendUtils._ + import inlinerHeuristics._ def compile(scalaCode: String, javaCode: List[(String, String)] = Nil, allowMessage: StoreReporter#Info => Boolean = _ => false): List[ClassNode] = { InlinerTest.notPerRun.foreach(_.clear()) @@ -88,44 +83,25 @@ class InlinerTest extends ClearAfterClass { assert(callsite.callee.get.callee == callee, callsite.callee.get.callee.name) } - def makeInlineRequest( callsiteInstruction: MethodInsnNode, callsiteMethod: MethodNode, callsiteClass: ClassBType, - callee: MethodNode, calleeDeclarationClass: ClassBType, - callsiteStackHeight: Int, receiverKnownNotNull: Boolean, - post: List[inlinerHeuristics.PostInlineRequest] = Nil) = inlinerHeuristics.InlineRequest( - callsite = callGraph.Callsite( - callsiteInstruction = callsiteInstruction, - callsiteMethod = callsiteMethod, - callsiteClass = callsiteClass, - callee = Right(callGraph.Callee(callee = callee, calleeDeclarationClass = calleeDeclarationClass, safeToInline = true, safeToRewrite = false, annotatedInline = false, annotatedNoInline = false, samParamTypes = IntMap.empty, calleeInfoWarning = None)), - argInfos = IntMap.empty, - callsiteStackHeight = callsiteStackHeight, - receiverKnownNotNull = receiverKnownNotNull, - callsitePosition = NoPosition), - post = post) - - // inline first invocation of f into g in class C - def inlineTest(code: String, mod: ClassNode => Unit = _ => ()): (MethodNode, List[CannotInlineWarning]) = { - val List(cls) = compile(code) - mod(cls) - val clsBType = classBTypeFromParsedClassfile(cls.name) - - 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() - - val analyzer = new AsmAnalyzer(g, clsBType.internalName) - - val request = makeInlineRequest( - callsiteInstruction = fCall, - callsiteMethod = g, - callsiteClass = clsBType, - callee = f, - calleeDeclarationClass = clsBType, - callsiteStackHeight = analyzer.frameAt(fCall).getStackSize, - receiverKnownNotNull = true - ) - - val r = inliner.inline(request) - (g, r) + def getCallsite(method: MethodNode, calleeName: String) = callGraph.callsites(method).valuesIterator.find(_.callee.get.callee.name == calleeName).get + + 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) + } + + def canInlineTest(code: String, mod: ClassNode => Unit = _ => ()): Option[OptimizerWarning] = { + val cs = gMethAndFCallsite(code, mod)._2 + inliner.earlyCanInlineCheck(cs) orElse inliner.canInlineBody(cs) + } + + def inlineTest(code: String, mod: ClassNode => Unit = _ => ()): MethodNode = { + val (gMethod, fCall) = gMethAndFCallsite(code, mod) + inliner.inline(InlineRequest(fCall, Nil)) + gMethod } @Test @@ -137,7 +113,7 @@ class InlinerTest extends ClearAfterClass { |} """.stripMargin - val (g, _) = inlineTest(code) + val g = inlineTest(code) val gConv = convertMethod(g) assertSameCode(gConv.instructions.dropNonOp, @@ -171,7 +147,7 @@ class InlinerTest extends ClearAfterClass { // See also discussion around ATHROW in BCodeBodyBuilder - val (g, _) = inlineTest(code) + 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 ??? @@ -192,11 +168,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.length == 1 && can.head.isInstanceOf[SynchronizedMethod], can) + assert(can.nonEmpty && can.get.isInstanceOf[SynchronizedMethod], can) } @Test @@ -207,7 +183,7 @@ class InlinerTest extends ClearAfterClass { | def g = f + 1 |} """.stripMargin - val (_, r) = inlineTest(code) + val r = canInlineTest(code) assert(r.isEmpty, r) } @@ -221,8 +197,8 @@ class InlinerTest extends ClearAfterClass { | def g = println(f) |} """.stripMargin - val (_, r) = inlineTest(code) - assert(r.length == 1 && r.head.isInstanceOf[MethodWithHandlerCalledOnNonEmptyStack], r) + val r = canInlineTest(code) + assert(r.nonEmpty && r.get.isInstanceOf[MethodWithHandlerCalledOnNonEmptyStack], r) } @Test @@ -242,31 +218,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 request = makeInlineRequest( - callsiteInstruction = gCall, - callsiteMethod = h, - callsiteClass = dTp, - callee = g, - calleeDeclarationClass = cTp, - callsiteStackHeight = analyzer.frameAt(gCall).getStackSize, - receiverKnownNotNull = true - ) - - val r = inliner.inline(request) - - assert(r.length == 1 && r.head.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 @@ -403,27 +358,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 request = makeInlineRequest( - callsiteInstruction = callsiteIns, - callsiteMethod = f, - callsiteClass = clsBType, - callee = lowestOneBitMethod, - calleeDeclarationClass = integerClassBType, - callsiteStackHeight = analyzer.frameAt(callsiteIns).getStackSize, - receiverKnownNotNull = false - ) - - val r = inliner.inline(request) - assert(r.isEmpty, r) - val ins = instructionsFromMethod(f) + val fMeth = findAsmMethod(c, "f") + val call = getCallsite(fMeth, "lowestOneBit") + + 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) @@ -1065,49 +1007,124 @@ class InlinerTest extends ClearAfterClass { | 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 + } - val cTp = classBTypeFromParsedClassfile(c.name) - - val f = c.methods.asScala.find(_.name == "f").get - val g = c.methods.asScala.find(_.name == "g").get - val h = c.methods.asScala.find(_.name == "h").get - - val gCall = h.instructions.iterator.asScala.collect({ - case m: MethodInsnNode if m.name == "g" => m - }).next() - val fCall = g.instructions.iterator.asScala.collect({ - case m: MethodInsnNode if m.name == "f" => m - }).next() - - val analyzer = new AsmAnalyzer(h, cTp.internalName) - - val request = makeInlineRequest( - callsiteInstruction = gCall, - callsiteMethod = h, - callsiteClass = cTp, - callee = g, - calleeDeclarationClass = cTp, - callsiteStackHeight = analyzer.frameAt(gCall).getStackSize, - receiverKnownNotNull = false, - post = List(inlinerHeuristics.PostInlineRequest(fCall, Nil)) - ) - - val r = inliner.inline(request) - assertNoInvoke(getSingleMethod(c, "h")) // no invoke in h: first g is inlined, then the inlined call to f is also inlined - assertInvoke(getSingleMethod(c, "g"), "C", "f") // g itself still has the call to f - assert(r.isEmpty, r) + @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")) } - /** - * NOTE: if this test fails for you when running within the IDE, it's probably because you're - * using 2.12.0-M2 for compilining within the IDE, which doesn't add SAM information to the - * InlineInfo attribute. So the InlineInfo in the classfile for Function1 doesn't say that - * it's a SAM type. The test passes when running with ant (which does a full bootstrap). - */ @Test def inlineHigherOrder(): Unit = { val code = @@ -1134,4 +1151,68 @@ class InlinerTest extends ClearAfterClass { 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")) + } } |