Last active
August 29, 2015 14:27
-
-
Save tarao/1936b2083d459f4356bf to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.github.tarao | |
package gradual | |
import scala.language.dynamics | |
import scala.language.experimental.macros | |
import scala.reflect.runtime.{universe => ru} | |
import ru.{Type, TypeTag, TermName, MethodSymbol, typeOf} | |
class Dyn(value: Any, t: Type) extends Dynamic { | |
def as[T: TypeTag](): T = { | |
if (!(t <:< typeOf[T])) | |
throw new ClassCastException(s"$t cannot be cast to ${typeOf[T]}") | |
value.asInstanceOf[T] | |
} | |
// TODO curried methods? | |
def applyDynamic(method: String)(arguments: Any*): Dyn = | |
macro MacroTreeBuilder.invoke | |
def selectDynamic(field: String): Dyn = | |
invokeDynamic(field, Seq.empty, Seq.empty) | |
private def checkParameterTypes( | |
method: MethodSymbol, | |
argumentTypes: Seq[Type] | |
): Boolean = { | |
// TODO handle type parameters | |
// TODO define and use <~< (consistent subtype relation) | |
(argumentTypes, method.paramLists.head).zipped.forall(_ <:< _.info) | |
} | |
private def findMethod( | |
method: String, argumentTypes: Seq[Type] | |
): Option[MethodSymbol] = { | |
// TODO select most specific one for overloaded methods | |
val sym = t.member(TermName(method)) | |
if (sym.isMethod && checkParameterTypes(sym.asMethod, argumentTypes)) | |
Some(sym.asMethod) | |
else None | |
} | |
private def invokeMethod( | |
method: MethodSymbol, | |
arguments: Seq[Any] | |
): (Any, Type) = { | |
val m = ru.runtimeMirror(getClass.getClassLoader) | |
val im = m.reflect(value) | |
(im.reflectMethod(method)(arguments: _*), method.returnType) | |
} | |
private[gradual] def invokeDynamic( | |
method: String, | |
arguments: Seq[Any], | |
argumentTypes: Seq[Type] | |
): Dyn = { | |
val (returnValue, returnType) = | |
invokeMethod(findMethod(method, argumentTypes).getOrElse { | |
throw new NoSuchMethodError( | |
s"$t.$method(${argumentTypes.mkString(",")})" | |
) | |
}, arguments) | |
new Dyn(returnValue, returnType) | |
} | |
} | |
object Dyn { | |
import scala.language.implicitConversions | |
implicit def dynFromAny[T: TypeTag](value: T): Dyn = new Dyn(value, typeOf[T]) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.github.tarao | |
package gradual | |
import org.scalatest.{FunSpec, Matchers, OptionValues, Inside, Inspectors} | |
class A | |
class DynSpec extends FunSpec with Matchers | |
with OptionValues with Inside with Inspectors { | |
describe("Dyn") { | |
it("can be converted from any object") { | |
val x: Dyn = "foo" | |
val y: Dyn = new A | |
} | |
it("can be converted to any type") { | |
val x: Dyn = "foo" | |
val y: Dyn = new A | |
val s: String = x.as[String] | |
val a: A = y.as[A] | |
} | |
it("should fail to be converted to an incompatible type") { | |
val x: Dyn = "foo" | |
val y: Dyn = new A | |
val z: Dyn = List(1, 2, 3) | |
a[ClassCastException] should be thrownBy x.as[Int] | |
a[ClassCastException] should be thrownBy y.as[String] | |
a[ClassCastException] should be thrownBy z.as[List[String]] | |
} | |
it("can call a method dynamically") { | |
val s: Dyn = "foo" | |
val len = s.length | |
len shouldBe a[Dyn] | |
len.as[Int] shouldBe 3 | |
val char = s.charAt(1) | |
char shouldBe a[Dyn] | |
char.as[Char] shouldBe 'o' | |
// (wip) not implemented yet | |
// val l: Dyn = Seq(1, 2, 3) | |
// val sqrs = l.map { (x: Int) => x * x } | |
// sqrs shouldBe a[Dyn] | |
// sqrs.as[Seq[Int]] shouldBe Seq(1, 4, 9) | |
} | |
it("should fail to invoke an undefined method") { | |
val s: Dyn = "foo" | |
a[NoSuchMethodError] should be thrownBy s.hoge(1, 2, "foo") | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.github.tarao | |
package gradual | |
import scala.reflect.macros.blackbox.Context | |
private[gradual] class MacroTreeBuilder(val c: Context) { | |
import c.universe._ | |
def invoke(method: c.Expr[String])(arguments: c.Expr[Any]*): c.Expr[Dyn] = { | |
val argumentTypes = arguments.map { arg => | |
q"scala.reflect.runtime.universe.typeOf[${arg.actualType}]" | |
} | |
c.Expr(q"""${c.prefix}.invokeDynamic( | |
$method, | |
Seq(..$arguments), | |
Seq(..$argumentTypes) | |
)""") | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment