8

Is there a convenient way to turn a MethodSymbol into the left-hand side of a method definition tree (i.e., a DefDef) in Scala 2.10?

For example, suppose I want to create a macro that will take an instance of a trait and wrap all of that trait's methods with some debugging functionality. I can write the following:

import scala.language.experimental.macros
import scala.reflect.macros.Context

object WrapperExample {
  def wrap[A](a: A): A = macro wrap_impl[A]

  def wrap_impl[A: c.WeakTypeTag](c: Context)(a: c.Expr[A]) = {
    import c.universe._

    val wrapped = weakTypeOf[A]
    val f = Select(reify(Predef).tree, "println")

    val methods = wrapped.declarations.collect {
      case m: MethodSymbol if !m.isConstructor => DefDef(
        Modifiers(Flag.OVERRIDE),
        m.name,
        Nil, Nil,
        TypeTree(),
        Block(
          Apply(f, c.literal("Calling: " + m.name.decoded).tree :: Nil),
          Select(a.tree, m.name)
        )
      )
    }.toList

  //...
}

I've elided the boring business of sticking these methods in a new anonymous class that implements the trait and then instantiating that class—you can find a complete working example here if you're interested.

Now I can write this, for example:

scala> trait X { def foo = 1; def bar = 'a }
defined trait X

scala> val x = new X {}
x: X = $anon$1@15dd533

scala> val w: X = WrapperExample.wrap[X](x)
w: X = $1$$1@27c3a4a3

scala> w.foo
Calling: foo
res0: Int = 1

scala> w.bar
Calling: bar
res1: Symbol = 'a

So it works, but only in very simple cases—it won't if the trait has methods with parameter lists, with access modifiers, annotations, etc.

What I really want is a function that will take a method symbol and a tree for the new body and return a DefDef. I've started writing one by hand, but it involves a lot of fiddly stuff like this:

List(if (method.isImplicit) Some(Flag.IMPLICIT) else None, ...)

Which is annoying, verbose, and error-prone. Am I missing some nicer way to do this in the new Reflection API?

Travis Brown
  • 138,631
  • 12
  • 375
  • 680

2 Answers2

4

To the best of my knowledge, there's no standard way to go from a symbol to a defining tree.

Your best bet would probably be to iterate through c.enclosingRun.units, recursing into each of the unit.body trees as you go. If you see a DefDef, which has a symbol equal to your symbol, then you've reached your destination. upd. Don't forget to duplicate the defining tree before reusing it!

This technique is far from being the most convenient thing in the world, but it should work.

Eugene Burmako
  • 13,028
  • 1
  • 46
  • 59
  • Thanks (and +1), but what if the trait I'm looking to wrap is from a library? In that case I'm stuck with the approach above, right? – Travis Brown Dec 07 '12 at 18:51
  • 1
    Ah I see what you mean. You can use this API: https://github.com/scalamacros/kepler/blob/0acb8a30c379f268e8a3e1340504530493a1a1dc/src/reflect/scala/reflect/api/Trees.scala#L2480. We deprecated it in 2.10.1, but you can take a look at how it's implemented: https://github.com/scalamacros/kepler/blob/0acb8a30c379f268e8a3e1340504530493a1a1dc/src/reflect/scala/reflect/internal/Trees.scala#L975. – Eugene Burmako Dec 07 '12 at 19:55
2

You may try the following. This works with multiple parameters and curried functions and type parameters ;)

val methods = wrapped.declarations.collect {
  case m: MethodSymbol if !m.isConstructor => DefDef(
    Modifiers(Flag.OVERRIDE),
    m.name,
    m.typeParams.map(TypeDef(_)),
    m.paramss.map(_.map(ValDef(_))),
    TypeTree(m.returnType),
    Block(
      Apply(f, c.literal("Calling: " + m.name.decoded).tree :: Nil),
      m.paramss.foldLeft(Select(a.tree.duplicate, m.name): Tree)((prev, params) =>
        Apply(prev, params.map(p => Ident(p.name)))
      )
    )
  )
}.toList
Leo
  • 757
  • 5
  • 11
  • There's also `DefDef(sym, tree)` which does the same, but as it's been mentioned below this method has just been deprecated along with `TypeDef(sym)` and `ValDef(sym)`. upd. It doesn't do the same, because your code seems to omit existing modifiers. – Eugene Burmako Dec 08 '12 at 07:17
  • Also see my comments to the OP's gist: https://gist.github.com/4234441. Your code has the same subtle problem as that code does. – Eugene Burmako Dec 08 '12 at 07:20
  • Thanks Eugene. I updated my answer accordingly. This may explain some issues I have on some of my macros with nasty compiler crashes. – Leo Dec 08 '12 at 11:29