【问题标题】:Def Macro, pass parameter from a valueDef 宏,从一个值传递参数
【发布时间】:2020-10-31 07:46:36
【问题描述】:

我有一个工作宏,即:

object Main extends App {

  println("Testing assert macro...")
  val result = Asserts.assert(false, "abc")

}

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

object Asserts {
  val assertionsEnabled: Boolean = true

  def assert(cond: Boolean, msg: String): Unit = macro assertImpl

  def assertImpl(c: Context)(cond: c.Expr[Boolean], msg: c.Expr[String]) : c.Expr[Unit] = {
    import c.universe._
    cond.tree match {
      case Literal(Constant(cond: Boolean)) =>
        if (!cond) c.abort(c.enclosingPosition, "Fix the code, whatever.") else c.Expr(q"()")
    }
  }
}

但问题是: 我怎样才能使它工作?:

  val cond: Boolean = false
  val result = Asserts.assert(cond, "abc")

现在我有一个错误:

Error:(8, 30) 宏扩展期间异常:scala.MatchError: Main.this.cond(类 scala.reflect.internal.Trees$Select)在 io.baku.macrosy.Asserts$.assertImpl(Asserts.scala:13) val 结果 = Asserts.assert(cond, "abc")

【问题讨论】:

    标签: scala scala-macros


    【解决方案1】:

    简短的回答很简单,你做不到。 falsecond in 的运行时值

    val cond: Boolean = false
    

    编译器在扩展宏时无法在编译时访问它。

    您尝试匹配 Literal 但树/Expr cond 不是文字。

    所以Asserts.assert(false, "abc") 可以工作,但Asserts.assert(cond, "abc") 不行。

    长答案是有几个技巧。

    第一个技巧是你可以将你的项目拆分成三个子项目commoncoremacros 并使用c.eval 而不是匹配Literal(Constant)(如果将cond 放在core 而不是common,这将不起作用):

    常见

    object App {
      val cond: Boolean = false
    }
    

    核心(取决于common

    import App.cond
    
    object Main {
      def main(args: Array[String]): Unit = {
        println("Testing assert macro...")
        val result = Asserts.assert(cond, "abc") 
        //scalac: macro expansion has failed: Fix the code, whatever.
      }
    }
    

    (取决于通用

    import scala.language.experimental.macros
    import scala.reflect.macros.blackbox
    
    object Asserts {
      def assert(cond: Boolean, msg: String): Unit = macro assertImpl
    
      def assertImpl(c: blackbox.Context)(cond: c.Expr[Boolean], msg: c.Expr[String]) : c.Expr[Unit] = {
        import c.universe._
        val condEvaluated = c.eval(c.Expr[Boolean](c.untypecheck(cond.tree)))
        if (!condEvaluated) c.abort(c.enclosingPosition, "Fix the code, whatever.") else c.Expr(q"()")
      }
    }
    

    Scala: what can code in Context.eval reference?

    第二个技巧是,如果你在core中保留cond,那么你可以尝试找到cond的定义树并且得到它的右手边:

    核心(取决于

    object Main {
      def main(args: Array[String]): Unit = {
        println("Testing assert macro...")
        val cond: Boolean = false
        val result = Asserts.assert(cond, "abc")
        //scalac: macro expansion has failed: Fix the code, whatever.
      }
    }
    

    import scala.language.experimental.macros
    import scala.reflect.macros.blackbox
    
    object Asserts {    
      def assert(cond: Boolean, msg: String): Unit = macro assertImpl
    
      def assertImpl(c: blackbox.Context)(cond: c.Expr[Boolean], msg: c.Expr[String]) : c.Expr[Unit] = {
        import c.universe._
    
        var condValue: Option[Boolean] = None
    
        val traverser = new Traverser {
          override def traverse(tree: Tree): Unit = tree match {
            case q"$_ val cond: $_ = $expr" if tree.symbol == cond.tree.symbol => 
              expr match {
                case Literal(Constant(cond: Boolean)) =>
                  condValue = Some(cond)
              }
            case _ => super.traverse(tree)
          }
        }
    
        c.enclosingRun.units.foreach(unit => traverser.traverse(unit.body))
    
        condValue.map(cond =>
          if (!cond) c.abort(c.enclosingPosition, "Fix the code, whatever.") else c.Expr(q"()")
        ).getOrElse(
          c.abort(c.enclosingPosition, "can't find cond")
        )
      }
    }
    

    Creating a method definition tree from a method symbol and a body

    Scala macro how to convert a MethodSymbol to DefDef with parameter default values?

    How to get the runtime value of parameter passed to a Scala macro?

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-09-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-05-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多