Skip to content

Instantly share code, notes, and snippets.

@tarao
Last active August 29, 2015 14:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tarao/1936b2083d459f4356bf to your computer and use it in GitHub Desktop.
Save tarao/1936b2083d459f4356bf to your computer and use it in GitHub Desktop.
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])
}
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")
}
}
}
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