Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I get a scala macro to replace a method call

How do I get a scala macro to replace a method call?

My goal is to create a trait called ToStringAdder. Supposing I have an object x with this trait, then when I call x.add(any) I want the macro to actually call x.add(any, string) where the string is a string representation of the AST. (This is so that I can have nice tostring when 'any' is a function).

  • I've written the macros given in the documentation http://docs.scala-lang.org/overviews/macros/overview.html,
  • Read http://docs.scala-lang.org/sips/pending/self-cleaning-macros.html
  • Read the pdfs http://scalamacros.org/paperstalks/2013-04-22-LetOurPowersCombine.pdf.
  • I've looked at the code in https://github.com/retronym/macrocosm/blob/master/src/main/scala/com/github/retronym/macrocosm/Macrocosm.scala
  • And the code in https://github.com/pniederw/expecty/tree/master/src/main/scala/org/expecty, but I'm sad to say I didn't Grok it all
  • I have had a look at the scaladoc, but...woo..that's complex... I suspect that I would need some significant time to get up to speed with how the compiler works.

With the exception of Expecty all of these examples I have seen are effectively using static method calls: the object that the macro is called on is not used. Expecty has the following method, which gives me a clue about how to detect the 'implicit this', but I couldn't find a way to reference it in a reify call.

 private[this] def recordAllValues(expr: Tree): Tree = expr match {
    case New(_) => expr // only record after ctor call
    case Literal(_) => expr // don't record
    // don't record value of implicit "this" added by compiler; couldn't find a better way to detect implicit "this" than via point
    case Select(x@This(_), y) if getPosition(expr).point == getPosition(x).point => expr
    case _ => recordValue(recordSubValues(expr), expr)
  }

So how do I go about replacing the call to the object that the macro has been called on. The code I have at the moment is below, and it is the code in the reify call that needs to be sorted

trait ToStringAdder {
  def add(param: Any): Any = macro ToStringAdder.toStringAndValueImpl
  def add(param: Any, toStringBasedOnAST: String): Any ; //This is the actual method I want the above method call to be replaced by
}

object ToStringAdder {
  def toStringAndValueImpl(c: Context)(param: c.Expr[Any]): c.Expr[Unit] = {
    import c.universe._
    val paramRep = show(param.tree)
    val paramRepTree = Literal(Constant(paramRep))
    val paramRepExpr = c.Expr[String](paramRepTree)
    //need to put something here 
     reify { c.someMethodCall("something to represent the method any", param.splice, paramRepExpr.splice ) }
  }
}
like image 984
Stave Escura Avatar asked May 07 '13 09:05

Stave Escura


1 Answers

You could get Tree for your ToStringAdder instance as c.prefix.

Try this:

reify { c.Expr[ToStringAdder](c.prefix.tree).splice.add(param.splice, c.literal(paramRep).splice) }

Proof it works:

scala> :paste
// Entering paste mode (ctrl-D to finish)

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

trait ToStringAdder {
  def add(param: Any): Any = macro ToStringAdder.toStringAndValueImpl
  def add(param: Any, toStringBasedOnAST: String): Any ; //This is the actual method I want the above method call to be replaced by
}

object ToStringAdder {
  def toStringAndValueImpl(c: Context)(param: c.Expr[Any]): c.Expr[Any] = {
    import c.universe._
    val paramRep = show(param.tree)
    reify { (c.Expr[ToStringAdder](c.prefix.tree)).splice.add(param.splice, c.literal(paramRep).splice) }
  }
}


// Exiting paste mode, now interpreting.

import scala.language.experimental.macros
import reflect.macros.Context
defined trait ToStringAdder
defined module ToStringAdder

scala> class ToStringAdder1 extends ToStringAdder {
     |   def add(param: Any, toStringBasedOnAST: String): Any = s"param: $param \ntoStringBasedOnAST: $toStringBasedOnAST"
     | }
defined class ToStringAdder1

scala> new ToStringAdder1().add( (i: Int) => i*2 )
res0: Any =
param: <function1>
toStringBasedOnAST: ((i: Int) => i.*(2))
like image 50
senia Avatar answered Sep 28 '22 08:09

senia