Reputation: 11
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
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