import scala.language.experimental.macros
import scala.reflect.macros.whitebox.Context
object ProxyMacro
{
type Implementor = Array[Any] => Any
def proxy[T](implementor: Implementor): T = macro impl[T]
def impl[T: c.WeakTypeTag](c: Context)(implementor: c.Expr[Implementor]): c.Expr[T] = {
import c.universe._
val tpe = weakTypeOf[T]
val decls = tpe.decls.map { decl =>
val termName = decl.name.toTermName
val method = decl.asMethod
val params = method.paramLists
println("params:" + params)
println("param types:" + params.flatten.map(_.getClass))
q""" def $termName $params = null.asInstanceOf[${method.returnType}] """
}
c.Expr[T] {
q"""
new $tpe {
..$decls
}
"""
}
}
}
Hi, I am trying to create a macro to simulate proxying of traits where each method actually calls a function. i.e. for trait
First of all, the syntax for constructing/matching multiple lists of parameters is ...:
scala> import reflect.runtime.universe._
import reflect.runtime.universe._
scala> val tree = q"def foo(a: Int)(b: Int) = 42"
tree: reflect.runtime.universe.DefDef = def foo(a: Int)(b: Int) = 42
scala> val q"def $m(...$params) = ${_}" = tree
m: reflect.runtime.universe.TermName = foo
params: List[List[reflect.runtime.universe.ValDef]] = List(List(val a: Int = _), List(val b: Int = _))
scala> q"def $m(...$params) = 42"
res1: reflect.runtime.universe.DefDef = def foo(a: Int)(b: Int) = 42
See the section on Applications in the quasiquote documentation.
But even once you’ve done that, you can’t just splice the symbols of some other methods parameters into a new method. You should create new ValDef trees for each parameter. When you macro expansion is typechecked, a new symbol will be assigned for your parameters.
Make sure you think about how you want to deal with polymorphic methods, and methods with repeated and by-name arguments.
You might want to peruse the source code for ScalaMock which has a macro that does something similar.
package macrotests import scala.language.experimental.macros import scala.reflect.macros.whitebox.Context /** * A demo macro that proxies traits and calls a function for each method of a trait * * @author kostas.kougios */ object ProxyMacro { type Implementor = (String, Any) => Any
def proxy[T](implementor: Implementor): T = macro impl[T] def impl[T: c.WeakTypeTag](c: Context)(implementor: c.Expr[Implementor]): c.Expr[T] = { import c.universe._ val tpe = weakTypeOf[T] val decls = tpe.decls.map { decl => val termName = decl.name.toTermName val
method = decl.asMethod
val params = method.paramLists.map(_.map(s => internal.valDef(s)))
val paramVars = method.paramLists.map(_.map { s =>
internal.captureVariable(s)
internal.referenceCapturedVariable(s)
}).flatten
q""" def $termName (...$params) = {
$implementor (${termName.toString},List(..$paramVars) )
null.asInstanceOf[${method.returnType}] }""" } c.Expr[T] { q""" new $tpe { ..$decls } """ } } }