summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/compiler/scala/tools/reflect/UniversalFn.scala57
-rw-r--r--src/compiler/scala/tools/reflect/package.scala15
-rw-r--r--test/files/run/mock.check3
-rw-r--r--test/files/run/mock.scala29
4 files changed, 103 insertions, 1 deletions
diff --git a/src/compiler/scala/tools/reflect/UniversalFn.scala b/src/compiler/scala/tools/reflect/UniversalFn.scala
new file mode 100644
index 0000000000..7fce4ea61c
--- /dev/null
+++ b/src/compiler/scala/tools/reflect/UniversalFn.scala
@@ -0,0 +1,57 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2010 LAMP/EPFL
+ * @author Paul Phillips
+ */
+
+package scala.tools
+package reflect
+
+import java.lang.reflect.Method
+import java.{ lang => jl }
+
+/** For certain reflection tasks it is convenient to treat all methods
+ * as having the same signature: (Seq[AnyRef]) => AnyRef
+ *
+ * That is the "universal signature" and UniversalFn exists to provide
+ * it without abandoning the information we had before we needed it.
+ * One place this is used: closures can pose as arbitrary interfaces,
+ * and this is how we route the arguments from the actual method
+ * invocation (which targets a proxy object) to the original closure.
+ */
+class UniversalFn private (val closure: AnyRef, val method: Method) extends (Seq[AnyRef] => AnyRef) {
+ universal =>
+
+ /** Given an interface type argument, creates a proxy object of the
+ * type of the interface which implements all its methods by routing
+ * them to this universal function. Will throw an exception in the
+ * face of any bad data.
+ */
+ def as[T: Manifest] : T = {
+ val clazz = manifest[T].erasure
+ require(clazz.isInterface, "Type argument must be an interface.")
+
+ val interfaceMethods = clazz.getDeclaredMethods.toSet
+ val proxy = Mock.fromInterfaces(clazz) {
+ case Invoked(_, m, args) if interfaceMethods(m) => universal(args)
+ }
+ proxy.asInstanceOf[T]
+ }
+
+ def apply(xs: Seq[AnyRef]): AnyRef = method.invoke(closure, xs: _*)
+}
+
+object UniversalFn {
+ /** We use a private constructor so we can enforce some rules: we don't want
+ * universal functions to stack up, and right now we will only allow objects
+ * which appear to be closures (there's no reason not to eventually lift
+ * this restriction, but it should be harder to shoot your foot first.)
+ */
+ def apply(closure: AnyRef): UniversalFn = closure match {
+ case x: UniversalFn => x
+ case _ =>
+ val m = uniqueApply(closure) getOrElse {
+ throw new IllegalArgumentException("Argument must have exactly one non-bridge apply method.")
+ }
+ new UniversalFn(closure, m)
+ }
+}
diff --git a/src/compiler/scala/tools/reflect/package.scala b/src/compiler/scala/tools/reflect/package.scala
index cf3d6f9ab2..b4d93cddaa 100644
--- a/src/compiler/scala/tools/reflect/package.scala
+++ b/src/compiler/scala/tools/reflect/package.scala
@@ -14,10 +14,23 @@ package object reflect {
if (cl == null) Nil
else cl.getInterfaces.toList ++ allInterfaces(cl.getSuperclass) distinct
+ def methodsNamed(target: AnyRef, name: String): List[Method] =
+ target.getClass.getMethods filter (x => x.getName == name) toList
+
+ /** If there is a single non-bridge apply method in the given instance,
+ * return it: otherwise None.
+ */
+ def uniqueApply(target: AnyRef) = {
+ methodsNamed(target, "apply") filterNot (_.isBridge) match {
+ case List(x) => Some(x)
+ case _ => None
+ }
+ }
+
def zeroOfClass(clazz: Class[_]) = zeroOf(Manifest.classType(clazz))
def zeroOf[T](implicit m: Manifest[T]): AnyRef = {
if (m == manifest[Boolean] || m == manifest[jl.Boolean]) false: jl.Boolean
- else if (m == manifest[Unit] || m == manifest[jl.Void]) scala.runtime.BoxedUnit.UNIT
+ else if (m == manifest[Unit] || m == manifest[jl.Void] || m == manifest[scala.runtime.BoxedUnit]) scala.runtime.BoxedUnit.UNIT
else if (m == manifest[Char] || m == manifest[jl.Character]) 0.toChar: jl.Character
else if (m == manifest[Byte] || m == manifest[jl.Byte]) 0.toByte: jl.Byte
else if (m == manifest[Short] || m == manifest[jl.Short]) 0.toShort: jl.Short
diff --git a/test/files/run/mock.check b/test/files/run/mock.check
new file mode 100644
index 0000000000..967c4e20bb
--- /dev/null
+++ b/test/files/run/mock.check
@@ -0,0 +1,3 @@
+Hi, thanks for calling: that makes 1 times.
+Hi, thanks for calling: that makes 2 times.
+Hi, thanks for calling: that makes 3 times.
diff --git a/test/files/run/mock.scala b/test/files/run/mock.scala
new file mode 100644
index 0000000000..e414e51e2d
--- /dev/null
+++ b/test/files/run/mock.scala
@@ -0,0 +1,29 @@
+import scala.tools.reflect._
+import java.util.concurrent.Callable
+import java.io.Closeable
+
+object Test {
+ // It'd be really nice about now if functions had a common parent.
+ implicit def interfaceify(x: AnyRef): UniversalFn = UniversalFn(x)
+
+ def runner(x: Runnable) = x.run()
+ def caller[T](x: Callable[T]): T = x.call()
+ def closer(x: Closeable) = x.close()
+
+ def main(args: Array[String]): Unit = {
+ var counter = 0
+ val closure = () => {
+ counter += 1
+ println("Hi, thanks for calling: that makes " + counter + " times.")
+ counter
+ }
+
+ val int1 = closure.as[Runnable]
+ val int2 = closure.as[Callable[Int]]
+ val int3 = closure.as[Closeable]
+
+ runner(int1)
+ caller(int2)
+ closer(int3)
+ }
+}