Home > Software design >  Execute splice() for varargs in macro method in Scala 2.13
Execute splice() for varargs in macro method in Scala 2.13

Time:09-28

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

  • Related