4

大纲

我有一个看起来像这样的 API:

package com.example

object ExternalApi {

  def create[T <: SpecialElement](elem: T): TypeConstructor[T] =
    TypeConstructor(elem)

  def create1[T <: SpecialElement](elem: T): TypeConstructor[T] =
    TypeConstructor(elem)

  def create2[T <: SpecialElement](elem: T): TypeConstructor[T] =
    TypeConstructor(elem)
  //...

}

object MyApi {

  def process[T <: TypeConstructor[_ <: SpecialElement]](
      l: T,
      metadata: List[String]): T = {
    println("I've been called!")
    //do some interesting stuff with the List's type parameter here
    l
  }

}

case class TypeConstructor[E](elem: E)

trait SpecialElement

ExternalApi实际上在我的库外部,所以不要修改它)有一系列调用,我想用MyApi.process调用自动包装,metadata参数派生自最终类型的T.

为了说明,要包装的调用可以有任何形式,包括嵌套调用和其他 AST 子树类型(例如Blocks)中的调用,例如:

package com.example.test

import com.example.{ExternalApi, SpecialElement}

object ApiPluginTest extends App {
  //one possible form
  val targetList = ExternalApi.create(Blah("name"))

  //and another
  ExternalApi.create2(ExternalApi.create1(Blah("sth")).elem)

  //and yet another
  val t = {

    val sub1 = ExternalApi.create(Blah("anything"))

    val sub2 = ExternalApi.create1(sub1.elem)

    sub2
  }

}

case class Blah(name: String) extends SpecialElement

由于编译器插件“免费”递归地处理 AST 中的匹配结构,因此我决定使用它们。

但是,由于我需要匹配特定的类型签名,因此插件遵循该typer阶段。

这是 的代码PluginComponent

package com.example.plugin

import com.example.{SpecialElement, TypeConstructor}

import scala.tools.nsc.Global
import scala.tools.nsc.plugins.PluginComponent
import scala.tools.nsc.transform.Transform

class WrapInApiCallComponent(val global: Global)
    extends PluginComponent
    with Transform {
  protected def newTransformer(unit: global.CompilationUnit) =
    WrapInApiCallTransformer

  val runsAfter: List[String] = List("typer") //since we need the type
  val phaseName: String       = WrapInApiCallComponent.Name

  import global._

  object WrapInApiCallTransformer extends Transformer {
    override def transform(tree: global.Tree) = {
      val transformed = super.transform(tree)
      transformed match {
        case call @ Apply(_, _) =>
          if (call.tpe != null && call.tpe.finalResultType <:< typeOf[
                TypeConstructor[_ <: SpecialElement]]) {
            println(s"Found relevant call $call")

            val typeArguments = call.tpe.typeArgs.map(_.toString).toList

            val listSymbOf = symbolOf[List.type]
            val wrappedFuncSecondArgument =
              q"$listSymbOf.apply(..$typeArguments)"

            val apiObjSymbol = symbolOf[com.example.MyApi.type]

            val wrappedCall =
              q"$apiObjSymbol.process[${call.tpe.finalResultType}]($call, $wrappedFuncSecondArgument)"

            //explicit typing, otherwise later phases throw NPEs etc.
            val ret = typer.typed(wrappedCall)
            println(showRaw(ret))
            println("----")
            ret
          } else {
            call
          }
        case _ => transformed
      }
    }
  }
}

object WrapInApiCallComponent {
  val Name = "api_embed_component"
}

这似乎可以正确解析标识符以及类型,输出例如:

Apply(TypeApply(Select(TypeTree().setOriginal(Ident(com.example.MyApi)), TermName("process")), List(TypeTree())), List(Apply(TypeApply(Select(Select(Select(Ident(com), com.example), com.example.MyApi), TermName("create")), List(TypeTree())), List(Apply(Select(Ident(com.example.test.Blah), TermName("apply")), List(Literal(Constant("name")))))), Apply(TypeApply(Select(TypeTree().setOriginal(Ident(scala.collection.immutable.List)), TermName("apply")), List(TypeTree())), List(Literal(Constant("com.example.test.Blah"))))))

不幸的是,我在编译过程中遇到错误,开始于:

scala.reflect.internal.FatalError: 
[error] 
[error]   Unexpected tree in genLoad: com.example.MyApi.type/class scala.reflect.internal.Trees$TypeTree at: RangePosition([projectpath]/testPluginAutoWrap/compiler_plugin_test/src/main/scala/com/example/test/ApiPluginTest.scala, 108, 112, 112)
[error]      while compiling: [projectpath]/testPluginAutoWrap/compiler_plugin_test/src/main/scala/com/example/test/ApiPluginTest.scala
[error]         during phase: jvm
[error]      library version: version 2.12.4
[error]     compiler version: version 2.12.4
[error]   reconstructed args: -Xlog-implicits -classpath [classpath here]
[error] 
[error]   last tree to typer: TypeTree(class String)
[error]        tree position: line 23 of [projectpath]/testPluginAutoWrap/compiler_plugin_test/src/main/scala/com/example/test/ApiPluginTest.scala

问题

看起来我在用类型定义搞砸了,但它是什么?

具体来说:

在上述要求的限制下,如何正确地用一个调用来包装每个ExternalApi.createX调用?MyApi.process


笔记

  1. 鉴于所需的样板数量,我已经建立了一个完整的示例项目。它在这里可用
  2. 答案不必定义编译器插件。如果您能够使用宏覆盖所有相关调用,那也很好。
  3. 最初包装的调用类似于: def process[T <: TypeConstructor[_ <: SpecialElement] : TypeTag](l: T): T,这里的设置实际上是一种解决方法。因此,如果您能够生成这种类型的包装调用,即包含运行时的调用TypeTag[T],那也很好
4

0 回答 0