Assaf Assaf - 1 month ago 13
Scala Question

Scala Compiler Plugin Deconstruction

I've been trying to write a Scala (2.10.0) compiler plugin that analyzes some parts of a traversed code.

This is what I originally had:

class MyPlugin (val global: Global) extends Plugin {
import global._
val name = "myPlugin"
val components = List[PluginComponent](MyComponent)

private object MyComponent extends PluginComponent {
val global: MyPlugin.this.global.type = MyPlugin.this.global
val runsAfter = List ("refchecks")
val phaseName = "codeAnalysis"

def newPhase (_prev: Phase) = new AnalysisPhase (_prev)

class AnalysisPhase (prev: Phase) extends StdPhase (prev) {
override def name = phaseName

def apply (unit: CompilationUnit) {
codeTraverser traverse unit.body
printLinesToFile(counters.map{case (k,v) => k + "\t" + v},out)
}

def codeTraverser = new ForeachTreeTraverser (tree => /* Analyze tree */)
}
}
}


This code works as expected, however I don't like it because I cannot decouple the code traverser method from this object. I would like to write a separate
CodeTraverser
class that will perform the analysis on a given Tree. This, among other things may help me test this code better.

The main problem is that
unit.body
is of an internal Tree type inside
scala.reflect.internal.Trees
. If I could work with
scala.reflect.api.Trees#Tree
instead of the internal version I could decouple the traverser functionality and even test it quite easily.

I've tried to find a way to convert between the two, but to no avail. Is it even possible? From looking at their source code, many things looks too similar for this to be impossible.

Answer

You're probably struggling with the cake pattern that the compiler is implemented with and a lot of path-dependency that comes with it. I've gone through this some time ago when I was writing some really beefy macro and wanted to refactor a bunch of functions out of macro implementation into separate utility class. I found this to be quite an annoying issue.

Here's how I would implement your Traverser in a separate class:

class MyPluginUtils[G <: Global with Singleton](global: G) {
  import global._

  class AnalyzingTraverser extends ForeachTreeTraverser(tree => /* analyze */)
}

Now, inside your plugin you have to use it like this:

val utils = new MyPluginUtils[global.type](global)
import utils.{global => _, _}

val traverser = new AnalyzingTraverser

As you can see, it's not the most intuitive thing in the world (i.e. this is confusing as hell), but this is the best that I could come up with that actually worked, and I tried a lot of things before finally settling on this one. I would be really happy to see some nicer way to do this.

AFAIK, such extensibility is one of the general problems with the cake pattern (as used in scalac implementation). I've seen other people also complain about this.