scalascala-macrosscala-2.13scala-2

Execute splice() for varargs in macro method in Scala 2.13


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?


Solution

  • In Scala 2 it's easier to work with Trees q"..." (and splicing like $, ..$, ...$) rather than Exprs (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 Exprs/.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).