I'm writing a macro that can log a short message during the compilation, using pattern matching and constant type feature of 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 <: EmitMsg.EmitLevel]: EmitMsg[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]"
}
}
}
When being tested, I found that the macro works most of the time when being invoked directly:
EmitMsg.emit["ABC", EmitMsg.Error]
EmitMsg.emit["ABC", EmitMsg.Warning]
...
(this generates proper compilation messages):
[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
... but rarely works if being part of the implicit macro pattern (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
*/
So my questions are:
Is calling c.info/warning/error the correct way of logging in compile-time?
If so, why they never works as part the implicit macro pattern?
Thanks a lot for your suggestions!
UPDATE 1 Just found a solution for one case (Error) at 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))
()
This is a convoluted hack, it works by adding an @implicitNotFound annotation on the definition of the implicit function. I'm not aware of any similar solution for Warning and Info cases so far. Nevertheless, a simpler solution is always preferred
Just make
c
ablackbox.Context
rather thanwhitebox.Context
if you don't want to postpone compile errors during implicit resolution.