8000 SI-7965 Support calls to MethodHandle.{invoke,invokeExact} · retronym/scala@cbd5bb3 · GitHub
[go: up one dir, main page]

Skip to content

Commit cbd5bb3

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` if the enclosing expression is a cast or if the application appears in the statement position of a block. 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 = lookupBar.invokeExact(O, 42).asInstanceOf[Int] () } } 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 cbd5bb3

File tree

5 files changed

+81
-0
lines changed

5 files changed

+81
-0
lines changed

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3281,6 +3281,26 @@ 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 surrounding `asInstanceOf` or position
3291+
// in surrounding block
3292+
val args1 = typedArgs(args, forArgMode(fun, mode))
3293+
val pts = args1.map(_.tpe.deconst)
3294+
val clone = fun.symbol.cloneSymbol
3295+
val cloneParams = pts map (pt => clone.newValueParameter(currentUnit.freshTermName()).setInfo(pt))
3296+
val resultType = context.tree.collect {
3297+
case TypeApply(sel @ Select(`tree`, nme.asInstanceOf_), target :: Nil) => typedType(target).tpe
3298+
case Block(stats, _) if stats contains tree => UnitTpe
3299+
}.headOption.getOrElse(ObjectTpe)
3300+
clone.modifyInfo(mt => copyMethodType(mt, cloneParams, resultType))
3301+
val fun1 = fun.setSymbol(clone).setType(clone.info)
3302+
doTypedApply(tree, fun1, args1, mode, resultType).setType(resultType)
3303+
32843304
case mt @ MethodType(params, _) =>
32853305
val paramTypes = mt.paramTypes
32863306
// 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).asInstanceOf[String]
23+
assert(fooResult == "foo")
24+
25+
val barResult = lookup("bar", Array(classOf[Int]), classOf[Int]).invokeExact(O, 42).asInstanceOf[Int]
26+
assert(barResult == -42)
27+
28+
val box = new Box(null)
29+
lookup("baz", Array(classOf[Box]), Void.TYPE).invokeExact(O, box).asInstanceOf[Unit]
30+
assert(box.a == "present")
31+
32+
// Application in statement position of a block also infers return type of Unit,
33+
// as done in Java.
34+
lookup("baz", Array(classOf[Box]), Void.TYPE).invokeExact(O, box)
35+
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)
0