summaryrefslogblamecommitdiff
path: root/test/junit/scala/tools/nsc/backend/jvm/IndySammyTest.scala
blob: 6f213e66250429caea2799c1d6cfd83fd63e7a7e (plain) (tree)

































































































































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

}