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))()
}