Scala macros, creating functions with parameters

687 views
Skip to first unread message

Kostas kougios

unread,
Apr 22, 2015, 5:34:04 PM4/22/15
to scala...@googlegroups.com
Hi, I am trying to create a macro to simulate proxying of traits where each method actually calls a function. i.e. for trait

trait MyTrait {
   def x(i:Int):Int
}

I can get an instance of it via ProxyMacro.proxy[MyTrait]( args => ... do something with method call args)

I've done this so far:

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
}
"""
}
}
}


But I am getting this compilation error:

Error:(23, 24) Can't unquote List[List[c.universe.Symbol]], consider using ... or providing an implicit instance of Liftable[List[List[c.universe.Symbol]]]
            q""" def $termName $params = null.asInstanceOf[${method.returnType}] """
                                ^

"..." gives me a similar error too

Since $params is already the method params, shouldn't that work? Any ideas how to fix it?

Thanks

Jason Zaugg

unread,
Apr 22, 2015, 7:55:32 PM4/22/15
to Kostas kougios, scala-user
On Thu, Apr 23, 2015 at 7:34 AM, Kostas kougios <kostas....@googlemail.com> wrote:
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.

-jason

Konstantinos Kougios

unread,
Apr 23, 2015, 4:37:35 PM4/23/15
to Jason Zaugg, scala-user
Thanks Jason.

I made just a bit of progress :)

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
          }
      """
      }
   }
}



    Now the conversion of the proxied method parameters to paramVars
    doesn't work. I assume my code that creates paramVars is totally
    wrong and I am getting a:

Error:scalac: Error: Could not find proxy for x: String in List(value x, method get, trait TheTrait$1, <$anon: Function0>, value <local ProxyMacroTest>, class ProxyMacroTest, package macrotests, package <root>) (currentOwner= method get )
java.lang.IllegalArgumentException: Could not find proxy for x: String in List(value x, method get, trait TheTrait$1, <$anon: Function0>, value <local ProxyMacroTest>, class ProxyMacroTest, package macrotests, package <root>) (currentOwner= method get )
   
So final step: how to convert the params to accessors for the values of the params?

Thanks

Kostas kougios

unread,
Apr 24, 2015, 4:34:56 PM4/24/15
to scala...@googlegroups.com
I am stuck with this, I cant access the values of the method arguments:


            val params = method.paramLists.map(_.map(s => internal.valDef(s)))

            q""" def $termName (...$params) = {
                val valuesInAList= List( $params ????? )
               }"""

Any ideas?
Reply all
Reply to author
Forward
0 new messages