Wrap method calls recursively with compiler plugins / macros

ANNOTATION

I have an API that looks something like this:

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 type parameter here
    l
  }

}

case class TypeConstructor[E](elem: E)

trait SpecialElement

ExternalApi(which is actually external to my lib, so without changing this) has a series of calls that I would like to automatically wrap with calls MyApi.process, using the metadataone obtained from the final type T.

To illustrate, calls that need to be wrapped can take any form, including nested calls, and calls in other types of AST subtree (e.g., Blocks), for example.

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

Since the compiler plugins process the corresponding structures in the AST recursively "free", I decided to go with them.

- , , 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, , ?


  • , . .
  • . , .
  • : def process[T <: TypeConstructor[_ <: SpecialElement] : TypeTag](l: T): T, . , , , TypeTag[T], ..
+4

Source: https://habr.com/ru/post/1692441/


All Articles