Skip to content

Commit c1d092a

Browse files
committed
SI-7965 Support calls to MethodHandle.{invoke,invokeExact}
These methods are "signature polymorphic", which means that compiler should not: 1. adapt the arguments to `Object` 2. wrap the repeated parameters in an array 3. adapt the result type to `Object`, but instead treat it as it it already conforms to the expected type. Dispiritingly, my initial attempt to implement this touched the type checker, uncurry, erasure, and the backend. However, I realized we could centralize handling of this in the typer if at each application we substituted the signature polymorphic symbol with a clone that carried its implied signature, which is derived from the types of the arguments (typechecked without an expected type) and position within and enclosing cast or block. The test case requires Java 7+ to compile so is currently embedded in a conditionally compiled block of code in a run test. We ought to create a partest category for modern JVMs so we can write such tests in a more natural style. Here's how this looks in bytecode. Note the `bipush` / `istore` before/after the invocation of `invokeExact`, and the descriptor `(LO$;I)I`. ``` % cat sandbox/poly-sig.scala && qscala Test && echo ':javap Test$#main' | qscala import java.lang.invoke._ object O { def bar(x: Int): Int = -x } object Test { def main(args: Array[String]): Unit = { def lookup(name: String, params: Array[Class[_]], ret: Class[_]) = { val lookup = java.lang.invoke.MethodHandles.lookup val mt = MethodType.methodType(ret, params) lookup.findVirtual(O.getClass, name, mt) } def lookupBar = lookup("bar", Array(classOf[Int]), classOf[Int]) val barResult: Int = lookupBar.invokeExact(O, 42) () } } scala> :javap Test$#main public void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC Code: stack=3, locals=3, args_size=2 0: aload_0 1: invokespecial #18 // Method lookupBar$1:()Ljava/lang/invoke/MethodHandle; 4: getstatic #23 // Field O$.MODULE$:LO$; 7: bipush 42 9: invokevirtual #29 // Method java/lang/invoke/MethodHandle.invokeExact:(LO$;I)I 12: istore_2 13: return LocalVariableTable: Start Length Slot Name Signature 0 14 0 this LTest$; 0 14 1 args [Ljava/lang/String; 13 0 2 barResult I LineNumberTable: line 16: 0 } ``` I've run this test across our active JVMs: ``` % for v in 1.6 1.7 1.8; do java_use $v; pt --terse test/files/run/t7965.scala || break; done java version "1.6.0_65" Java(TM) SE Runtime Environment (build 1.6.0_65-b14-466.1-11M4716) Java HotSpot(TM) 64-Bit Server VM (build 20.65-b04-466.1, mixed mode) Selected 1 tests drawn from specified tests . 1/1 passed (elapsed time: 00:00:02) Test Run PASSED java version "1.7.0_71" Java(TM) SE Runtime Environment (build 1.7.0_71-b14) Java HotSpot(TM) 64-Bit Server VM (build 24.71-b01, mixed mode) Selected 1 tests drawn from specified tests . 1/1 passed (elapsed time: 00:00:07) Test Run PASSED java version "1.8.0_25" Java(TM) SE Runtime Environment (build 1.8.0_25-b17) Java HotSpot(TM) 64-Bit Server VM (build 25.25-b02, mixed mode) Selected 1 tests drawn from specified tests . 1/1 passed (elapsed time: 00:00:05) Test Run PASSED ```
1 parent 495fdb3 commit c1d092a

File tree

5 files changed

+77
-0
lines changed

5 files changed

+77
-0
lines changed

src/compiler/scala/tools/nsc/typechecker/Typers.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3281,6 +3281,22 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
32813281
}
32823282
handleOverloaded
32833283

3284+
case _ if isPolymorphicSignature(fun.symbol) =>
3285+
// Mimic's Java's treatment of polymorphic signatures as described in
3286+
// https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.3
3287+
//
3288+
// One can think of these methods as being infinitely overloaded. We create
3289+
// a ficticious new cloned method symbol for each call site that takes on a signature
3290+
// governed by a) the argument types and b) the expected type
3291+
val args1 = typedArgs(args, forArgMode(fun, mode))
3292+
val pts = args1.map(_.tpe.deconst)
3293+
val clone = fun.symbol.cloneSymbol
3294+
val cloneParams = pts map (pt => clone.newValueParameter(currentUnit.freshTermName()).setInfo(pt))
3295+
val resultType = if (isFullyDefined(pt)) pt else ObjectTpe
3296+
clone.modifyInfo(mt => copyMethodType(mt, cloneParams, resultType))
3297+
val fun1 = fun.setSymbol(clone).setType(clone.info)
3298+
doTypedApply(tree, fun1, args1, mode, resultType).setType(resultType)
3299+
32843300
case mt @ MethodType(params, _) =>
32853301
val paramTypes = mt.paramTypes
32863302
// repeat vararg as often as needed, remove by-name

src/reflect/scala/reflect/internal/Definitions.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,8 @@ trait Definitions extends api.StandardDefinitions {
514514
lazy val ScalaSignatureAnnotation = requiredClass[scala.reflect.ScalaSignature]
515515
lazy val ScalaLongSignatureAnnotation = requiredClass[scala.reflect.ScalaLongSignature]
516516

517+
lazy val MethodHandle = getClassIfDefined("java.lang.invoke.MethodHandle")
518+
517519
// Option classes
518520
lazy val OptionClass: ClassSymbol = requiredClass[Option[_]]
519521
lazy val OptionModule: ModuleSymbol = requiredModule[scala.Option.type]
@@ -1508,6 +1510,9 @@ trait Definitions extends api.StandardDefinitions {
15081510

15091511
lazy val PartialManifestClass = getTypeMember(ReflectPackage, tpnme.ClassManifest)
15101512
lazy val ManifestSymbols = Set[Symbol](PartialManifestClass, FullManifestClass, OptManifestClass)
1513+
1514+
def isPolymorphicSignature(sym: Symbol) = PolySigMethods(sym)
1515+
private lazy val PolySigMethods: Set[Symbol] = Set[Symbol](MethodHandle.info.decl(sn.Invoke), MethodHandle.info.decl(sn.InvokeExact)).filter(_.exists)
15111516
}
15121517
}
15131518
}

src/reflect/scala/reflect/internal/StdNames.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1147,6 +1147,7 @@ trait StdNames {
11471147
final val GetClassLoader: TermName = newTermName("getClassLoader")
11481148
final val GetMethod: TermName = newTermName("getMethod")
11491149
final val Invoke: TermName = newTermName("invoke")
1150+
final val InvokeExact: TermName = newTermName("invokeExact")
11501151

11511152
val Boxed = immutable.Map[TypeName, TypeName](
11521153
tpnme.Boolean -> BoxedBoolean,

src/reflect/scala/reflect/runtime/JavaUniverseForce.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,7 @@ trait JavaUniverseForce { self: runtime.JavaUniverse =>
310310
definitions.QuasiquoteClass_api_unapply
311311
definitions.ScalaSignatureAnnotation
312312
definitions.ScalaLongSignatureAnnotation
313+
definitions.MethodHandle
313314
definitions.OptionClass
314315
definitions.OptionModule
315316
definitions.SomeClass

test/files/run/t7965.scala

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Test that scala doesn't apply boxing or varargs conversions to the
2+
// @PolymorphicSignature magical methods, MethodHandle#{invoke, invokeExact}
3+
object Test {
4+
val code = """
5+
6+
object O {
7+
private def foo = "foo"
8+
private def bar(x: Int): Int = -x
9+
private def baz(x: Box): Unit = x.a = "present"
10+
val lookup = java.lang.invoke.MethodHandles.lookup
11+
}
12+
13+
import java.lang.invoke._
14+
class Box(var a: Any)
15+
16+
object Test {
17+
def main(args: Array[String]): Unit = {
18+
def lookup(name: String, params: Array[Class[_]], ret: Class[_]) = {
19+
val mt = MethodType.methodType(ret, params)
20+
O.lookup.findVirtual(O.getClass, name, mt)
21+
}
22+
val fooResult = (lookup("foo", Array(), classOf[String]).invokeExact(O): Int)
23+
assert(fooResult == "foo")
24+
25+
val barResult = (lookup("bar", Array(classOf[Int]), classOf[Int]).invokeExact(O, 42): Int)
26+
assert(barResult == -42)
27+
28+
val box = new Box(null)
29+
(lookup("baz", Array(classOf[Box]), Void.TYPE).invokeExact(O, box) : Unit)
30+
assert(box.a == "present")
31+
32+
// Note: Application in statement position in a block in Java also infers return type of Unit,
33+
// but we don't support that, ascribe the type to Unit as above.
34+
// as done in Java.
35+
// lookup("baz", Array(classOf[Box]), Void.TYPE).invokeExact(O, box)
36+
()
37+
}
38+
}
39+
40+
"""
41+
def main(args: Array[String]): Unit = {
42+
if (util.Properties.isJavaAtLeast("1.7")) test()
43+
}
44+
45+
def test() {
46+
import scala.reflect.runtime._
47+
import scala.tools.reflect.ToolBox
48+
49+
val m = currentMirror
50+
val tb = m.mkToolBox()
51+
import tb._
52+
eval(parse(code))
53+
}
54+
}

0 commit comments

Comments
 (0)