我正在尝试创建一个只能应用于某种类型的注释宏。当我运行我的测试时,当注释仅应用于顶级对象时,我看到一个未找到类型的错误。
我的宏代码:
trait Labelled[T] {
def label: T
}
@compileTimeOnly("DoSomethingToLabelled requires the macro paradise plugin")
class DoSomethingToLabelled extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro DoSomethingToLabelled.impl
}
object DoSomethingToLabelled {
def impl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
annottees.map(_.tree).head match {
case expr @ ModuleDef(mods: Modifiers, name: TermName, impl: Template) =>
println(showRaw(impl.parents))
val parentTypes = impl.parents.map(c.typecheck(_, c.TYPEmode))
if (parentTypes.exists(_.tpe <:< typeOf[Labelled[_]])) {
c.Expr[Any](expr)
} else {
c.abort(c.enclosingPosition, s"DoSomethingToLabelled can only be applied to a Labelled. Received types: $parentTypes")
}
}
}
}
我的测试代码:
class DoSomethingToLabelledSpec extends Specification {
private def classPathUrls(cl: ClassLoader): List[String] = cl match {
case null => Nil
case u: java.net.URLClassLoader => u.getURLs.toList.map(systemPath) ++ classPathUrls(cl.getParent)
case _ => classPathUrls(cl.getParent)
}
private def systemPath(url: URL): String = {
Paths.get(url.toURI).toString
}
private def paradiseJarLocation: String = {
val classPath = classPathUrls(getClass.getClassLoader)
classPath.find(_.contains("paradise")).getOrElse {
throw new RuntimeException(s"Could not find macro paradise on the classpath: ${classPath.mkString(";")}")
}
}
lazy val toolbox = runtimeMirror(getClass.getClassLoader)
.mkToolBox(options = s"-Xplugin:$paradiseJarLocation -Xplugin-require:macroparadise")
"The DoSomethingToLabelled annotation macro" should {
"be applicable for nested object definitions extending Labelled" in {
toolbox.compile {
toolbox.parse {
"""
|import macrotests.Labelled
|import macrotests.DoSomethingToLabelled
|
|object Stuff {
| @DoSomethingToLabelled
| object LabelledWithHmm extends Labelled[String] {
| override val label = "hmm"
| }
|}
|""".stripMargin
}
} should not (throwAn[Exception])
}
"be applicable for top level object definitions extending Labelled" in {
toolbox.compile {
toolbox.parse {
"""
|import macrotests.Labelled
|import macrotests.DoSomethingToLabelled
|
|@DoSomethingToLabelled
|object LabelledWithHmm extends Labelled[String] {
| override val label = "hmm"
|}
|""".stripMargin
}
} should not (throwAn[Exception])
}
}
}
我的测试日志是:
sbt:macro-type-extraction> test
[info] Compiling 1 Scala source to C:\Users\WilliamCarter\workspace\macro-type-extraction\target\scala-2.11\classes ...
[info] Done compiling.
List(AppliedTypeTree(Ident(TypeName("Labelled")), List(Ident(TypeName("String")))))
List(AppliedTypeTree(Ident(TypeName("Labelled")), List(Ident(TypeName("String")))))
[info] DoSomethingToLabelledSpec
[info] The DoSomethingToLabelled annotation macro should
[info] + be applicable for nested object definitions extending Labelled
[error] scala.tools.reflect.ToolBoxError: reflective compilation has failed:
[error]
[error] exception during macro expansion:
[error] scala.reflect.macros.TypecheckException: not found: type Labelled
[error] at scala.reflect.macros.contexts.Typers$$anonfun$typecheck$2$$anonfun$apply$1.apply(Typers.scala:34)
[error] at scala.reflect.macros.contexts.Typers$$anonfun$typecheck$2$$anonfun$apply$1.apply(Typers.scala:28)
[error] at scala.reflect.macros.contexts.Typers$$anonfun$3.apply(Typers.scala:24)
[error] at scala.reflect.macros.contexts.Typers$$anonfun$3.apply(Typers.scala:24)
[error] at scala.reflect.macros.contexts.Typers$$anonfun$withContext$1$1.apply(Typers.scala:25)
[error] at scala.reflect.macros.contexts.Typers$$anonfun$withContext$1$1.apply(Typers.scala:25)
[error] at scala.reflect.macros.contexts.Typers$$anonfun$1.apply(Typers.scala:23)
[error] at scala.reflect.macros.contexts.Typers$$anonfun$1.apply(Typers.scala:23)
[error] at scala.reflect.macros.contexts.Typers$class.withContext$1(Typers.scala:25)
[error] at scala.reflect.macros.contexts.Typers$$anonfun$typecheck$2.apply(Typers.scala:28)
[error] at scala.reflect.macros.contexts.Typers$$anonfun$typecheck$2.apply(Typers.scala:28)
[error] at scala.reflect.macros.contexts.Typers$class.withWrapping$1(Typers.scala:26)
[error] at scala.reflect.macros.contexts.Typers$class.typecheck(Typers.scala:28)
[error] at scala.reflect.macros.contexts.Context.typecheck(Context.scala:6)
[error] at scala.reflect.macros.contexts.Context.typecheck(Context.scala:6)
[error] at macrotests.DoSomethingToLabelled$$anonfun$2.apply(DoSomethingToLabelled.scala:19)
[error] at macrotests.DoSomethingToLabelled$$anonfun$2.apply(DoSomethingToLabelled.scala:19)
[error] at scala.collection.immutable.List.map(List.scala:284)
[error] at macrotests.DoSomethingToLabelled$.impl(DoSomethingToLabelled.scala:19)
我的调试打印告诉我提取的父类型在每个测试中都是相同的,但由于某种原因,顶级对象无法解析它TypeName("Labelled")
实际上是一个macrotests.Labelled
. 有没有人能在这里帮助阐明一下?该宏似乎在测试上下文之外工作,但我真的很想了解发生了什么,以便我可以编写一些适当的测试。