在 Scala 2.13 中如何在宏中可靠地记录信息/警告/错误?

问题描述

我正在编写一个宏,可以在编译过程中记录一条短消息,使用 scala 2.13 的模式匹配和常量类型特性:

class EmitMsg[T,SS <: EmitMsg.EmitLevel] {}

object EmitMsg {

  trait EmitLevel

  trait Info extends EmitLevel
  trait Warning extends EmitLevel
  trait Error extends EmitLevel
  trait Abort extends EmitLevel

  def create[A,SS <: EmitMsg.EmitLevel]: EmitMsg[A,SS] = new EmitMsg[A,SS]

  implicit def emit[A,SS] = macro Macros.emit[A,SS]

  final class Macros(val c: whiteBox.Context) {
    val u = c.universe
    import u._

    def outer: EmitMsg.type = EmitMsg.this

    def emit[A: c.WeakTypeTag,LL: c.WeakTypeTag]: c.Tree = {

      val aa: Type = weakTypeOf[A]
      val v = aa match {
        case v: u.ConstantType => v.value.value
        case _ =>
          throw new UnsupportedOperationException(
            s"type $aa is not a constant"
          )
      }
      val ss = "" + v
      val ll: Type = weakTypeOf[LL]

      // if inherited from multiple traits,take the most serIoUs one
      if (ll <:< weakTypeOf[Abort]) {
        c.abort(c.enclosingPosition,ss)
      } else if (ll <:< typeOf[Error]) {
        c.error(c.enclosingPosition,ss)
      } else if (ll <:< typeOf[Warning]) {
        c.warning(c.enclosingPosition,ss)
      } else if (ll <:< typeOf[Info]) {
        c.info(c.enclosingPosition,ss,force = true)
      } else {
        throw new UnsupportedOperationException(
          s"type $ll is not an EmitLevel"
        )
      }

      q"$liftOuter.create[$aa,$ll]"
    }
  }
}

经过测试,发现直接调用该宏大部分时间都可以工作:

EmitMsg.emit["ABC",EmitMsg.Error]
EmitMsg.emit["ABC",EmitMsg.Warning]
...

(这会生成正确的编译消息):


[Error] /home/peng/git/shapesafe/macro/src/test/scala/org/shapesafe/m/EmitMsgSpec.scala:19: ABC
[Warn] /home/peng/git/shapesafe/macro/src/test/scala/org/shapesafe/m/EmitMsgSpec.scala:20: ABC
one warning found

...但如果是隐式宏模式 (https://docs.scala-lang.org/overviews/macros/implicits.html) 的一部分,则很少起作用:

  it("can emit error") 

    type TT = EmitMsg["ABC",EmitMsg.Error]
    implicitly[TT] //(EmitMsg.emit)
  }
/*
 Generates the following Message:

[Error] /home/peng/git/shapesafe/macro/src/test/scala/org/shapesafe/m/EmitMsgSpec.scala:15: Could not find implicit value for parameter e: TT
*/

  it("can emit warning") {

    type TT = EmitMsg["ABC",EmitMsg.Warning]
    implicitly[TT] //(EmitMsg.emit)
  }
/*
Doesn't do a thing
*/

  it("can emit info") {

    type TT = EmitMsg["ABC",EmitMsg.Info]
    implicitly[TT] //(EmitMsg.emit)
  }
/*
Doesn't do a thing
*/

所以我的问题是:

  • 调用 c.info/warning/error 是否是编译时登录的正确方式?

  • 如果是这样,为什么它们从不作为隐式宏模式的一部分工作?

非常感谢您的建议!

UPDATE 1 刚刚在 https://github.com/fthomas/singleton-ops/blob/204195838ada34de7e453401fb06810ace2c99b0/src/main/scala/singleton/ops/impl/GeneralMacros.scala#L102

找到了一个案例(错误)的解决方
    val tree0 =
      c.typecheck(
        q"""
          new _root_.scala.annotation.implicitNotFound("dummy")
        """,silent = false
      )

    class SubstMessage extends Transformer {
      val global = c.universe.asInstanceOf[scala.tools.nsc.Global]

      override def transform(tree: Tree): Tree = {
        super.transform {
          tree match {
            case Literal(Constant("dummy")) => Literal(Constant(msg))
            case t => t
          }
        }
      }
    }

    val tree = new SubstMessage().transform(tree0)

    annotatedSym.setAnnotations(Annotation(tree))
    ()

这是一个令人费解的技巧,它通过在隐式函数的定义上添加@implicitNotFound 注释来工作。到目前为止,我不知道警告和信息案例有任何类似的解决方案。尽管如此,总是首选更简单的解决方

解决方法

如果您不想在隐式解析期间推迟编译错误,只需将 c 设为 blackbox.Context 而不是 whitebox.Context

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...