diff options
3 files changed, 90 insertions, 18 deletions
diff --git a/src/partest-extras/scala/tools/partest/ASMConverters.scala b/src/partest-extras/scala/tools/partest/ASMConverters.scala index d990160ce8..a3d849a9c1 100644 --- a/src/partest-extras/scala/tools/partest/ASMConverters.scala +++ b/src/partest-extras/scala/tools/partest/ASMConverters.scala @@ -43,6 +43,23 @@ object ASMConverters { case i: Invoke => i.name case i => i.opcode } + + def summaryText: String = { + def comment(i: Instruction) = i match { + case j: Jump => s" /*${j.label.offset}*/" + case l: Label => s" /*${l.offset}*/" + case _ => "" + } + dropNonOp.map({ + case i: Invoke => s""""${i.name}"""" + case ins => opcodeToString(ins.opcode, ins.opcode) + comment(ins) + }).mkString("List(", ", ", ")") + } + } + + def opcodeToString(op: Int, default: Any = "?"): String = { + import scala.tools.asm.util.Printer.OPCODES + if (OPCODES.isDefinedAt(op)) OPCODES(op) else default.toString } sealed abstract class Instruction extends Product { @@ -50,12 +67,9 @@ object ASMConverters { // toString such that the first field, "opcode: Int", is printed textually. final override def toString() = { - import scala.tools.asm.util.Printer.OPCODES - def opString(op: Int) = if (OPCODES.isDefinedAt(op)) OPCODES(op) else "?" val printOpcode = opcode != -1 - productPrefix + ( - if (printOpcode) Iterator(opString(opcode)) ++ productIterator.drop(1) + if (printOpcode) Iterator(opcodeToString(opcode)) ++ productIterator.drop(1) else productIterator ).mkString("(", ", ", ")") } diff --git a/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala b/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala index 0d353e930e..fe43ed2f6a 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala @@ -164,8 +164,18 @@ 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) @@ -181,6 +191,21 @@ object CodeGenTools { }, 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 getSingleMethod(classNode: ClassNode, name: String): Method = convertMethod(classNode.methods.asScala.toList.find(_.name == name).get) diff --git a/test/junit/scala/tools/nsc/transform/patmat/PatmatBytecodeTest.scala b/test/junit/scala/tools/nsc/transform/patmat/PatmatBytecodeTest.scala index 3fc3144eb2..ac558e2e21 100644 --- a/test/junit/scala/tools/nsc/transform/patmat/PatmatBytecodeTest.scala +++ b/test/junit/scala/tools/nsc/transform/patmat/PatmatBytecodeTest.scala @@ -18,7 +18,7 @@ import scala.tools.testing.ClearAfterClass object PatmatBytecodeTest extends ClearAfterClass.Clearable { var compiler = newCompiler() - var optCompiler = newCompiler(extraArgs = "-Yopt:l:method") + var optCompiler = newCompiler(extraArgs = "-Yopt:l:project") def clear(): Unit = { compiler = null; optCompiler = null } } @@ -96,10 +96,9 @@ class PatmatBytecodeTest extends ClearAfterClass { """.stripMargin val c = compileClasses(optCompiler)(code).head - assertEquals(textify(findAsmMethod(c, "a")), getSingleMethod(c, "a").instructions.summary, - List( - NEW, DUP, ICONST_1, LDC, "<init>", - "y", ARETURN)) + assertSameSummary(getSingleMethod(c, "a"), List( + NEW, DUP, ICONST_1, LDC, "<init>", + "y", ARETURN)) } @Test @@ -127,12 +126,12 @@ class PatmatBytecodeTest extends ClearAfterClass { |} """.stripMargin val c = compileClasses(optCompiler)(code).head - assertEquals(textify(findAsmMethod(c, "a")), getSingleMethod(c, "a").instructions.summary, - 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)) + 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 @@ -156,7 +155,41 @@ class PatmatBytecodeTest extends ClearAfterClass { -1 /*A*/ , NEW /*MatchError*/ , DUP, ALOAD /*1*/ , "<init>", ATHROW, -1 /*B*/ , ILOAD, IRETURN) - assertEquals(textify(findAsmMethod(c, "a")), getSingleMethod(c, "a").instructions.summary, expected) - assertEquals(textify(findAsmMethod(c, "b")), getSingleMethod(c, "b").instructions.summary, expected) + 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)) } } |