Is there already or is it possible to have a Scala macro that gives me access to the text of the source? For instance I would like to write code like this:
val list = List(1, 2, 3)
val (text, sum) = (list.sum).withSource{(source, sum) => (source, sum)}
// would return ("list.sum", 6)
(list.sum).withSource{(source, sum) => println(s"$source: $sum"}
// prints list.sum: 6
Do you really want a source code or Tree
is enough?
For Tree
you could use prefix
of Context
like this:
import scala.language.experimental.macros
import reflect.macros.Context
implicit class WithSourceHelper[T](source: T) {
def withSource[R](f: (String, T) => R): R = macro withSourceImpl[T, R]
}
def withSourceImpl[T, R](c: Context)(f: c.Expr[(String, T) => R]): c.Expr[R] = {
import c.universe.{reify, Apply}
val source = c.prefix.tree match {
case Apply(_, List(s)) => s
case _ => c.abort(c.enclosingPosition, "can't find source")
}
reify{ f.splice.apply(c.literal(source.toString).splice, c.Expr[T](source).splice) }
}
Usage:
scala> val (x, y) = (1, 2)
x: Int = 1
y: Int = 2
scala> {x + y}.withSource{ (s, r) => s"$s = $r" }
res15: String = x.+(y) = 3
scala> val list = List(1, 2, 3)
list: List[Int] = List(1, 2, 3)
scala> val (text, sum) = (list.sum).withSource{(source, sum) => (source, sum)}
text: String = list.sum[Int](math.this.Numeric.IntIsIntegral)
sum: Int = 6
scala> (list.sum).withSource{(source, sum) => println(s"$source: $sum")}
$line38.$read.$iw.$iw.$iw.list.sum[Int](math.this.Numeric.IntIsIntegral): 6
I was not able to reuse withSource
directly to just print the source and value and return the value. The withSource
macro cannot be utilized from the same object itself (so I cannot just add my slightly modified version of withSource right in that file) and I am not able to call withSource
from a subclass of WithSourceHelper
, limiting reuse through inheritance.
In case anyone is interested, here is a complement to Senia's answer to just log the value with the source and return the value so the rest of the computation can occur.
def logValueImpl[T](c: Context): c.Expr[T] = {
import c.universe._
val source = c.prefix.tree match {
case Apply(_, List(s)) => s
case _ => c.abort(c.enclosingPosition, "can't find source")
}
val freshName = newTermName(c.fresh("logValue$"))
val valDef = ValDef(Modifiers(), freshName, TypeTree(source.tpe), source)
val ident = Ident(freshName)
val print = reify{
println(c.literal(show(source)).splice + ": " + c.Expr[T](ident).splice) }
c.Expr[T](Block(List(valDef, print.tree), ident))
}
I then define it as an implicit conversion on def p = macro Debug.logValueImpl[T]
. I can then use like this:
List(1, 2, 3).reverse.p.head
// prints: immutable.this.List.apply[Int](1, 2, 3).reverse: List(3, 2, 1)
The funny part is that I can apply it twice:
List(1, 2, 3).reverse.p.p
And it will show me what the logValueImpl
macro did:
{
val logValue$7: List[Int] = immutable.this.List.apply[Int](1, 2, 3).reverse;
Predef.println("immutable.this.List.apply[Int](1, 2, 3).reverse: ".+(logValue$7));
logValue$7
}
It seems to work with other macros as well:
f"float ${1.3f}%3.2f; str ${"foo".reverse}%s%n".p`
//prints:
{
val arg$1: Float = 1.3;
val arg$2: Any = scala.this.Predef.augmentString("foo").reverse;
scala.this.Predef.augmentString("float %3.2f; str %s%%n").format(arg$1, arg$2)
}: float 1.30; str oof%n
Even more interestingly if I used showRaw
instead of show
I can even see the tree of the expanded macro, which may come handy to figure out how to write other macros.
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