使用编译器插件/宏递归包装方法调用

时间:2018-01-19 22:40:03

标签: scala scala-macros scala-compiler scala-quasiquotes scala-macro-paradise

OUTLINE

我的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实际外部到我的lib,所以没有修改)有一系列调用,我想用MyApi.process个调用自动换行,metadata参数派生自最终类型T

为了说明,要被包装的调用可以具有任何形式,包括嵌套调用,以及其他AST子树类型(例如Block s)内的调用,例如, :

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. 鉴于所需的样板量,我已经建立了一个完整的示例项目。 It's available here
  2. 答案必须定义编译器插件。如果您能够使用宏覆盖所有相关的呼叫,那也没关系。
  3. 最初包装的电话是:def process[T <: TypeConstructor[_ <: SpecialElement] : TypeTag](l: T): T,这里的设置实际上是一种解决方法。因此,如果您能够生成此类型的包装调用,即一个包含运行时TypeTag[T],那么也可以

0 个答案:

没有答案