如何获取传递给Scala宏的参数的运行时值?

时间:2019-12-18 07:39:43

标签: scala macros scala-macros scala-reflect scala-quasiquotes

我表面上看有一个简单的宏问题,我一直在努力奋战了好几个小时,没有运气。也许有更多经验的人可以提供帮助。

我有以下宏:

import scala.language.experimental.macros
import scala.reflect.macros.blackbox.Context

object MacroObject {
  def run(s: String): Unit =
    macro runImpl

  def runImpl(c: Context)(s: c.Tree): c.Tree = {
    import c.universe._
    println(s)    // <-- I need the macro to know the value of s at compile time
    q"()"
  }
}

问题是这样的:我想让宏知道传递给它的值s-不是s的AST,而是s本身。具体来说,我希望它具有以下行为:

def runTheMacro(str: String): Unit = MacroObject.run(str)

final val HardCodedString1 = "Hello, world!"
runTheMacro(HardCodedString1)    // the macro should print "Hello, world!"
                                 // to the console during macro expansion

final val HardCodedString2 = "So long!"
runTheMacro(HardCodedString2)    // the macro should print "So long!"
                                 // to the console during macro expansion

保证,将唯一传递给runTheMacro的字符串是硬编码的常量值(即在编译时已知)。

这有可能吗?怎么做到的?

-

编辑:还有以下限制:

  1. 它必须是黑盒宏。
  2. 宏签名必须使用c.Tree s,而不是c.Expr[_] s(旧版代码;不能更改该部分)
  3. 如果需要,我确实可以在宏中使用toolbox
import scala.reflect.runtime.currentMirror
import scala.tools.reflect.ToolBox
private val toolbox = currentMirror.mkToolBox()

/** Evaluate the given code fragment at compile time. */
private def eval[A](code: String): A = {
  import scala.reflect.runtime.{universe => u}
  val uTree: u.Tree = toolbox.parse(code)
  toolbox.eval(uTree).asInstanceOf[A]
}

1 个答案:

答案 0 :(得分:0)

您的eval是运行时反射的eval,编译时宏的eval将是c.eval

"Hello, world!" in

final val HardCodedString1 = "Hello, world!"
runTheMacro(HardCodedString1) 

HardCodedString1的运行时值。

您不能在编译时访问运行时值。

在编译时,字符串HardCodedString1的树对val树的右侧一无所知。

Scala: what can code in Context.eval reference?

如果您确实需要在程序树中使用运行时值,则必须将其编译推迟到运行时

import scala.reflect.runtime.currentMirror
import scala.reflect.runtime.universe._
import scala.tools.reflect.ToolBox

object MacroObject {
  val toolbox = currentMirror.mkToolBox()

  def run(s: String): Unit = {
    toolbox.eval(q"""
      println($s)
      ()
    """)
  }
}

runTheMacro(HardCodedString1)//Hello, world!
runTheMacro(HardCodedString2)//So long!

或者,在编译时,您可以以某种方式找到封闭类的树,并在其中寻找val树,并在其右边找到

def runImpl(c: blackbox.Context)(s: c.Tree): c.Tree = {
  import c.universe._

  var rhs: Tree = null

  val traverser = new Traverser {
    override def traverse(tree: Tree): Unit = {
      tree match {
        case q"$mods val $tname: $tpt = $expr" if tname == TermName("HardCodedString1") =>
          rhs = expr
        case _ => ()
      }
      super.traverse(tree)
    }
  }

  traverser.traverse(c.enclosingClass) // deprecated

  val rhsStr =
    if (rhs != null) c.eval[String](c.Expr(c.untypecheck(rhs.duplicate)))
    else c.abort(c.enclosingPosition, "no val HardCodedString1 defined")

  println(rhsStr)

  q"()"
}

runTheMacro(HardCodedString1)//Warning:scalac: Hello, world!

或针对所有此类变量

def runImpl(c: blackbox.Context)(s: c.Tree): c.Tree = {
  import c.universe._

  val sEvaluated =
    try {
      c.eval[String](c.Expr(c.untypecheck(s.duplicate)))
    } catch {
      case e: IllegalArgumentException if e.getMessage.startsWith("Could not find proxy") =>
        s match {
          case q"$sName" =>
            var rhs: Tree = null

            val traverser = new Traverser {
              override def traverse(tree: Tree): Unit = {
                tree match {
                  case q"$mods val $tname: $tpt = $expr" if tname == sName =>
                    rhs = expr
                  case _ => ()
                }
                super.traverse(tree)
              }
            }

            traverser.traverse(c.enclosingClass)

            if (rhs != null) c.eval[String](c.Expr(c.untypecheck(rhs.duplicate)))
            else c.abort(c.enclosingPosition, s"no val $sName defined")

          case _ => c.abort(c.enclosingPosition, s"unsupported tree $s")
        }

    }

  println(sEvaluated)

  q"()"
}

MacroObject.run(HardCodedString1) //Warning:scalac: Hello, world!
MacroObject.run(HardCodedString2) //Warning:scalac: So long!

runTheMacro在这种情况下将不起作用:Error: no val str defined。 要使其正常工作,您也可以使其成为一个宏

def runTheMacro(str: String): Unit = macro runTheMacroImpl

def runTheMacroImpl(c: blackbox.Context)(str: c.Tree): c.Tree = {
  import c.universe._
  q"MacroObject.run($str)"
}

runTheMacro(HardCodedString1) //Warning:scalac: Hello, world!
runTheMacro(HardCodedString2) //Warning:scalac: So long!
相关问题