0

我正在尝试创建一个只能应用于某种类型的注释宏。当我运行我的测试时,当注释仅应用于顶级对象时,我看到一个未找到类型的错误。

我的宏代码:

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. 有没有人能在这里帮助阐明一下?该宏似乎在测试上下文之外工作,但我真的很想了解发生了什么,以便我可以编写一些适当的测试。

4

1 回答 1

1

尝试

toolbox.compile {
  toolbox.parse {
    """
      |import macrotests.DoSomethingToLabelled
      |
      |@DoSomethingToLabelled
      |object LabelledWithHmm extends macrotests.Labelled[String] {
      |  override val label = "hmm"
      |}
      |""".stripMargin
  }
}

甚至

toolbox.compile {
  toolbox.parse {
    """
      |import macrotests.DoSomethingToLabelled
      |
      |@DoSomethingToLabelled
      |object LabelledWithHmm extends _root_.macrotests.Labelled[String] {
      |  override val label = "hmm"
      |}
      |""".stripMargin
  }
}

顺便问一下,为什么需要工具箱?为什么不直接写

@DoSomethingToLabelled
object LabelledWithHmm extends Labelled[String] {
  override val label = "hmm"
}

在测试中?然后代码编译的事实将在编译时而不是在运行时使用工具箱进行检查。

https://github.com/scala/bug/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+%28toolbox+%26%26+%28import+%7C%7C+package%29%29

https://github.com/scala/bug/issues/6393

@xeno-by 说:看来我们注定要失败了。

问题是 Scala 反射和反射编译器(它是底层工具箱)使用与 vanilla scalac 不同的类文件加载模型。Vanilla 编译器将其类路径作为文件系统上的目录/jar 列表,因此它可以详尽地枚举类路径上的包。反射编译器适用于任意类加载器,而类加载器没有枚举包的概念。

结果,当反射编译器看到“math”具有“import scala.; import java.lang”。在词法上下文中导入,它不知道“math”是代表 root.math、scala.math 还是 java.lang.math。所以它必须推测并临时为 root.math 创建一个包,这最终是一个错误的选择。

我们可能支持“重载”包的概念,这样编译器就不必推测并且可以存储所有可能的选项,但这需要重新设计反射,可能还需要重新设计类型器。

于 2019-08-16T12:07:01.267 回答