daltontf1212
daltontf1212

Reputation: 11

Scala Compiler Plugin - Determine whether method is overridden

I am writing a Scala Compiler Plugin with a component the executes after the "jvm" phase where there is need to know that a method is overridden. Here is a distilled version of a test case I am working on:

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"
}

When traversing the AST, I iterate over the classes and the methods ensuring that I only process the methods that are not overridden. The scala.reflect.internal.Symbols.MethodSymbol.overrides() method applied to the symbol for the StatusController.getPath method will include the one for GenericController.getPath.

However, the MethodSymbol.overrides() fails to return the GenericController.save method for the StatusController.save symbol. Seem like the generic type complicates this behavior, Should this be done after another compiler phase?

Upvotes: 1

Views: 102

Answers (1)

Dmytro Mitin
Dmytro Mitin

Reputation: 51693

Let's define test compiler plugin.

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"
    )
  )

If you run (sbt reload; clean; plugin/package; core/compile) this compiler plugin after typer phase it prints

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)

but if you run it after jvm phase it prints

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()

So as you can see, after typer phase, method symbol StatusController.getPath overrides method symbol GenericController.getPath and StatusController.save overrides GenericController.save. And after jvm phase StatusController.getPath again overrides GenericController.getPath but it's bridge StatusController.save instead of ordinary StatusController.save that overrides GenericController.save. So I guess you confused symbol of bridge method with symbol of ordinary method.

Upvotes: 1

Related Questions