Macro to access source code at runtime

Is there already or is it possible to have a Scala macro that gives me access to the source text? For example, 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 
+8
macros scala
source share
2 answers

Do you really want the source code or Tree be enough?

For Tree you can use prefix for Context as follows:

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

Using:

 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 
+8
source share

I was not able to reuse withSource to just print the source and value and return the value. The withSource macro cannot be used from the same object (therefore, I cannot just add my slightly modified version of withSource in this file), and I cannot call withSource from a subclass of WithSourceHelper , restricting reuse through inheritance.

In case anyone is interested, here is an addition to Senya's answer, just to register the value with the source and return the value so that the rest of the calculations can happen.

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

Then I define it as an implicit conversion to def p = macro Debug.logValueImpl[T] . Then I can use it 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 thing is that I can apply it twice:

 List(1, 2, 3).reverse.pp 

And he will show me what the logValueImpl macro logValueImpl :

 { 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 too:

 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 interesting, if I used showRaw instead of show , I even see an extended macro tree, which may be useful for determining how to write other macros.

+1
source share

All Articles