summaryrefslogtreecommitdiff
path: root/test/junit/scala/tools/nsc/backend/jvm/IndySammyTest.scala
blob: 1ad02c10cf00f82e7e615122b552696b68e4f81e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package scala.tools.nsc
package backend.jvm

import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4

import scala.tools.asm.Opcodes._
import scala.tools.nsc.reporters.StoreReporter
import scala.tools.partest.ASMConverters._
import scala.tools.testing.BytecodeTesting
import scala.tools.testing.BytecodeTesting._


@RunWith(classOf[JUnit4])
class IndySammyTest extends BytecodeTesting {
  import 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 List(funClass, vcClass, vcCompanion) = compileClasses(s"${classPrologue(from, to)}")
    val c = compileClass(s"class C { ${lamDef(from, to, body)}; ${appDef(arg)} }", allowMessage = allowMessage)

    val applySig = getAsmMethod(funClass, "apply").desc
    val anonfun = getMethod(c, "$anonfun$lam$1")
    val lamInsn = getInstructions(c, "lam").dropNonOp
    val applyInvoke = getMethod(c, "app")

    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 = compileClasses("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 = compileAsmMethods("def foo = () => () => () => 42")
    methodNodes.forall(m => !m.name.contains("anonfun") || (m.access & ACC_STATIC) == ACC_STATIC)
  }
}