summaryrefslogtreecommitdiff
path: root/test/junit
diff options
context:
space:
mode:
authorAdriaan Moors <adriaan.moors@typesafe.com>2016-03-21 08:47:45 -0700
committerAdriaan Moors <adriaan.moors@typesafe.com>2016-03-26 22:54:12 -0700
commit2aa8eba5007f0e0eda3a2ef3fdffa1f468dc1fa4 (patch)
treea552aa90cac3d45cbcff3827407c5b3549f81298 /test/junit
parentf922f367d58b3ba6bbb4cb0864ce82c5cd6f7966 (diff)
downloadscala-2aa8eba5007f0e0eda3a2ef3fdffa1f468dc1fa4.tar.gz
scala-2aa8eba5007f0e0eda3a2ef3fdffa1f468dc1fa4.tar.bz2
scala-2aa8eba5007f0e0eda3a2ef3fdffa1f468dc1fa4.zip
Test bytecode emitted for indy sammy
Test that SAM conversion happens after implicit view application A function node is first type checked, and parameter types are inferred, regardless of whether the expected function type is one of our built-in FunctionN classes, or a user-defined Single Abstract Method type. `typedFunction` always assigns a built-in `FunctionN` type to the tree, though. Next, if the expected type is a (polymorphic) SAM type, this creates a tension between the tree's type and the expect type. This gap is closed by the adapt method, by applying one of the implicit conversion in the spec in order (e.g., numeric widening, implicit view application, and now, also SAM conversion) Thus, `adaptToSam` will assign the expected SAM type to the `Function` tree. (This may require some type inference.) The back-end will emit the right invokedynamic instruction that uses Java's LambdaMetaFactory to spin up a class that implements the target method (whether it's defined in FunctionN or some other Java functional interface).
Diffstat (limited to 'test/junit')
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/IndySammyTest.scala130
1 files changed, 130 insertions, 0 deletions
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..6f213e6625
--- /dev/null
+++ b/test/junit/scala/tools/nsc/backend/jvm/IndySammyTest.scala
@@ -0,0 +1,130 @@
+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)
+ )
+ }
+
+ // 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))()
+
+}