【问题标题】:Scala Compiler Plugin - Determine whether method is overriddenScala Compiler Plugin - 确定方法是否被覆盖
【发布时间】:2020-10-27 17:58:51
【问题描述】:

我正在编写一个带有组件的 Scala 编译器插件,该组件在需要知道方法被覆盖的“jvm”阶段之后执行。这是我正在处理的一个测试用例的提炼版本:

abstract class GenericController[T] {

    def getPath():String

    def save(entity:T):String = "parent"
}

class StatusController extends GenericController[String] {

    override def getPath():String = "/statuses"

    override def save(entity:String):String = "child"
}

在遍历 AST 时,我会遍历类和方法,确保只处理未被覆盖的方法。应用于StatusController.getPath 方法的符号的scala.reflect.internal.Symbols.MethodSymbol.overrides() 方法将包括GenericController.getPath 的方法。

但是,MethodSymbol.overrides() 无法为 StatusController.save 符号返回 GenericController.save 方法。似乎泛型类型使这种行为复杂化,是否应该在另一个编译器阶段之后完成?

【问题讨论】:

    标签: scala scalac scala-compiler


    【解决方案1】:

    让我们定义测试编译器插件。

    core/src/main/scala/Main.scala

    abstract class GenericController[T] {
      def getPath():String
      def save(entity:T):String = "parent"
    }
    
    class StatusController extends GenericController[String] {
      override def getPath():String = "/statuses"
      override def save(entity:String):String = "child"
    }
    

    plugin/src/main/resources/scalac-plugin.xml

    <plugin>
        <name>MyCompilerPlugin</name>
        <classname>compilerPlugin.MyCompilerPlugin</classname>
    </plugin>
    

    plugin/src/main/scala/compilerPlugin/MyCompilerPlugin.scala

    package compilerPlugin
    
    import scala.tools.nsc.{Global, Phase}
    import scala.tools.nsc.plugins.{Plugin, PluginComponent}
    
    class MyCompilerPlugin(val global: Global) extends Plugin {
      import global._
    
      val name = "MyCompilerPlugin"
      val description = "My compiler plugin"
      val components: List[PluginComponent] = List[PluginComponent](MyComponent)
    
      private object MyComponent extends PluginComponent {
        val global: MyCompilerPlugin.this.global.type = MyCompilerPlugin.this.global
        val runsAfter: List[String] = List[String](/*"typer"*/"jvm")
        val phaseName: String = MyCompilerPlugin.this.name
        def newPhase(_prev: Phase) = new MyPhase(_prev)
    
        class MyPhase(prev: Phase) extends StdPhase(prev) {
          override def name: String = MyCompilerPlugin.this.name
    
          def apply(unit: CompilationUnit): Unit = {
            for (tree@q"$mods def $tname[..$tparams](...$paramss): $tpt = $expr" <- unit.body) {
              println(s"tree=$tree, symbol=${tree.symbol.fullName}, symbol.overrides=${tree.symbol.overrides.map(_.fullName)}")
            }
          }
        }
      }
    }
    

    build.sbt

    ThisBuild / name := "compiler-plugin-demo"
    
    lazy val commonSettings = Seq(
      scalaVersion := "2.13.3",
      version := "1.0",
    )
    
    lazy val plugin = project
      .settings(
        commonSettings,
        libraryDependencies ++= Seq(
          scalaOrganization.value % "scala-reflect" % scalaVersion.value,
          scalaOrganization.value % "scala-compiler" % scalaVersion.value,
        )
      )
    
    lazy val core = project
      .settings(
        commonSettings,
        scalacOptions ++= Seq(
          "-Xplugin:plugin/target/scala-2.13/plugin_2.13-1.0.jar",
          "-Xplugin-require:MyCompilerPlugin"
        )
      )
    

    如果你在typer 阶段之后运行(sbt reload; clean; plugin/package; core/compile)这个编译器插件,它会打印出来

    tree=def <init>(): GenericController[T] = {
      GenericController.super.<init>();
      ()
    }, symbol=GenericController.<init>, symbol.overrides=List()
    tree=def getPath(): String, symbol=GenericController.getPath, symbol.overrides=List()
    tree=def save(entity: T): String = "parent", symbol=GenericController.save, symbol.overrides=List()
    tree=def <init>(): StatusController = {
      StatusController.super.<init>();
      ()
    }, symbol=StatusController.<init>, symbol.overrides=List()
    tree=override def getPath(): String = "/statuses", symbol=StatusController.getPath, symbol.overrides=List(GenericController.getPath)
    tree=override def save(entity: String): String = "child", symbol=StatusController.save, symbol.overrides=List(GenericController.save)
    

    但如果你在jvm 阶段之后运行它,它会打印出来

    tree=def getPath(): String, symbol=GenericController.getPath, symbol.overrides=List()
    tree=def save(entity: Object): String = "parent", symbol=GenericController.save, symbol.overrides=List()
    tree=def <init>(): GenericController = {
      GenericController.super.<init>();
      ()
    }, symbol=GenericController.<init>, symbol.overrides=List()
    tree=override def getPath(): String = "/statuses", symbol=StatusController.getPath, symbol.overrides=List(GenericController.getPath)
    tree=override def save(entity: String): String = "child", symbol=StatusController.save, symbol.overrides=List()
    tree=override <bridge> <artifact> def save(entity: Object): String = StatusController.this.save(entity.$asInstanceOf[String]()), symbol=StatusController.save, symbol.overrides=List(GenericController.save)
    tree=def <init>(): StatusController = {
      StatusController.super.<init>();
      ()
    }, symbol=StatusController.<init>, symbol.overrides=List()
    

    如您所见,在typer 阶段之后,方法符号StatusController.getPath 覆盖方法符号GenericController.getPathStatusController.save 覆盖GenericController.save。在jvm 阶段之后StatusController.getPath 再次覆盖GenericController.getPath 但它是桥StatusController.save 而不是普通的StatusController.save 覆盖GenericController.save。所以我猜你把桥方法的符号和普通方法的符号混淆了。

    【讨论】:

    • 感谢您的努力。我很感激!我正在学习编译器插件。
    • @user328 欢迎。这能回答你的问题吗?
    • @user328 你设法修复你的编译器插件了吗?
    猜你喜欢
    • 2014-07-29
    • 2011-05-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-01
    • 1970-01-01
    相关资源
    最近更新 更多