I want to execute splice()
for each argument of my varargs:
import scala.reflect.macros.blackbox
object LoggerMacro {
def log(context: blackbox.Context)
(message: context.Expr[String], arguments: context.Expr[Any]*)
: context.universe.Expr[Unit] = context.universe.reify {
println(message.splice) // Works :)
for (argument <- arguments) {
println(argument.splice) // Fails :(
}
}
}
However, I receive the following error message:
LoggerMacro.scala:9:24
the splice cannot be resolved statically, which means there is a cross-stage evaluation involved.
cross-stage evaluations need to be invoked explicitly, so we're showing you this error.
if you're sure this is not an oversight, add scala-compiler.jar to the classpath,
import `scala.tools.reflect.Eval` and call `<your expr>.eval` instead.
println(argument.splice)
Unfortunately, when I add scala-compiler
as dependency and import scala.tools.reflect.Eval
, there is still no callable eval
method on my expr argument
.
How can I access my arguments receiving as varargs?
CodePudding user response:
In Scala 2 it's easier to work with Tree
s q"..."
(and splicing like $
, ..$
, ...$
) rather than Expr
s (and .splice
). So try
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
object Macro {
def logMacro(message: String, arguments: Any*): Unit = macro log
def log(c: blackbox.Context)(message: c.Tree, arguments: c.Tree*): c.Tree = {
import c.universe._
q"""
_root_.scala.Predef.println($message)
for (argument <- _root_.scala.collection.immutable.Seq.apply(..$arguments)) {
_root_.scala.Predef.println(argument)
}
"""
}
}
Macro.logMacro("a", 1, 2, 3)
// scalacOptions = "-Ymacro-debug-lite"
//{
// _root_.scala.Predef.println("a");
// _root_.scala.collection.immutable.Seq.apply(1, 2, 3).foreach(((argument) => _root_.scala.Predef.println(argument)))
//}
//a
//1
//2
//3
But if you prefer Expr
s/.splice
then you can write yourself a helper transforming Seq[Expr[A]]
into Expr[Seq[A]]
.
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
object Macro {
def logMacro(message: String, arguments: Any*): Unit = macro log
def log(c: blackbox.Context)(message: c.Expr[String], arguments: c.Expr[Any]*): c.Expr[Unit] = {
import c.universe._
def exprsToExpr[A: WeakTypeTag](exprs: Seq[Expr[A]]): Expr[Seq[A]] =
exprs.foldRight(reify { Seq.empty[A] }) { (expr, acc) => reify {
expr.splice : acc.splice
}}
reify {
println(message.splice)
for (argument <- exprsToExpr(arguments).splice) {
println(argument)
}
}
}
}
Macro.logMacro("a", 1, 2, 3)
// scalacOptions = "-Ymacro-debug-lite"
//{
// Predef.println("a");
// {
// final <synthetic> <artifact> val rassoc$1 = 1;
// {
// final <synthetic> <artifact> val rassoc$1 = 2;
// {
// final <synthetic> <artifact> val rassoc$1 = 3;
// `package`.Seq.empty[Any].$plus$colon(rassoc$1)
//}.$plus$colon(rassoc$1)
//}.$plus$colon(rassoc$1)
//}.foreach(((argument) => Predef.println(argument)))
//}
//a
//1
//2
//3
The tree is slightly different but runtime result is the same.
You couldn't do argument.splice
because argument
is not an Expr
, it's a local variable defined here, in a scope quoted with reify
. Trying to splice it is "cross-stage evaluation", you were trying to splice not a value from the current stage (Expr[A]
) but from the next stage (A
).
For the same reason .eval
(calculating an Expr[A]
into A
) didn't work.
Difference between .splice
and .eval
is that the former just inserts a tree (into a tree) while the latter calculates it (if possible).