diff options
author | Adriaan Moors <adriaan.moors@typesafe.com> | 2016-03-31 16:06:56 -0700 |
---|---|---|
committer | Adriaan Moors <adriaan.moors@typesafe.com> | 2016-03-31 16:06:56 -0700 |
commit | 5654ebddb63d078f9f79dcf84fbb8489030136f6 (patch) | |
tree | d4bfc365cdf8e4993113275136f14803ceab3abd /test/junit | |
parent | 4fc7d5517c7152d43745960efde5042febe29422 (diff) | |
parent | 5e5ab186fe5b8cf047fd3da58da29dbc8f9fbd71 (diff) | |
download | scala-5654ebddb63d078f9f79dcf84fbb8489030136f6.tar.gz scala-5654ebddb63d078f9f79dcf84fbb8489030136f6.tar.bz2 scala-5654ebddb63d078f9f79dcf84fbb8489030136f6.zip |
Merge pull request #4971 from adriaanm/genbcode-delambdafy
Unify treatment of built-in functions and SAMs
Diffstat (limited to 'test/junit')
4 files changed, 188 insertions, 9 deletions
diff --git a/test/junit/scala/tools/nsc/backend/jvm/IndyLambdaTest.scala b/test/junit/scala/tools/nsc/backend/jvm/IndyLambdaTest.scala index 758566fe53..d29f6b0a13 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/IndyLambdaTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/IndyLambdaTest.scala @@ -31,25 +31,44 @@ class IndyLambdaTest extends ClearAfterClass { 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("(Ljava/lang/Object;)Ljava/lang/Object;", implMethodDescriptorFor("(x: Int) => new Object")) - assertEquals("(Ljava/lang/Object;)Ljava/lang/Object;", implMethodDescriptorFor("(x: Object) => 0")) + 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("(Ljava/lang/String;)Ljava/lang/String;", implMethodDescriptorFor("(x: String) => x")) + assertEquals(s"($str)$str", implMethodDescriptorFor("(x: String) => x")) // 2b. Testing 2a. in combination with 1. - assertEquals("(Ljava/lang/Object;)Ljava/lang/String;", implMethodDescriptorFor("(x: Int) => \"\"")) - assertEquals("(Ljava/lang/String;)Ljava/lang/Object;", implMethodDescriptorFor("(x: String) => 0")) + 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/opt/CallGraphTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala index 6e1ac3ba9f..b37b5efa7e 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala @@ -174,7 +174,7 @@ class CallGraphTest extends ClearAfterClass { | def t2(i: Int, f: Int => Int, z: Int) = h(f) + i - z | def t3(f: Int => Int) = h(x => f(x + 1)) |} - |abstract class D { + |trait D { | def iAmASam(x: Int): Int | def selfSamCall = iAmASam(10) |} 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 0ba0ecca4c..10ab006017 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala @@ -100,7 +100,7 @@ class ScalaInlineInfoTest extends ClearAfterClass { @Test def inlineInfoSam(): Unit = { val code = - """abstract class C { + """trait C { // expected to be seen as sam: g(I)I | def f = 0 | def g(x: Int): Int | val foo = "hi" @@ -108,10 +108,10 @@ class ScalaInlineInfoTest extends ClearAfterClass { |abstract class D { | val biz: Int |} - |trait T { + |trait T { // expected to be seen as sam: h(Ljava/lang/String;)I | def h(a: String): Int |} - |abstract class E extends T { + |trait E extends T { // expected to be seen as sam: h(Ljava/lang/String;)I | def hihi(x: Int) = x |} |class F extends T { |