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).
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 ) }
}
}
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))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With