Assaf Israel
Assaf Israel

Reputation: 478

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.

Upvotes: 0

Views: 174

Answers (2)

som-snytt
som-snytt

Reputation: 39587

The component (the SubComponent or PluginComponent) must be created with the global member initialized early (that is, as an early definition).

Don't forget to review the one-question faq. I may go set google calendar to remind me to do that every Monday morning.

For an example, see the continuations plugin.

The component is defined with a utility class mixed in.

The utility class follows the usual cake recipe. (Leave it as an abstract dependency and let the compiler ensure that everything was mixed correctly.)

Here is a recent edit showing more early definitions, as a demonstration that this usage is not anomalous.

  val anfPhase = new {
    val global = SelectiveCPSPlugin.this.global
    val cpsEnabled = pluginEnabled
    override val enabled = cpsEnabled
  } with SelectiveANFTransform {
    val runsAfter = List("pickler")
  }

(In future, they plan to deprecate early definitions in favor of parameterized traits when they are available in the language.)

More generally, global, i.e., "the compiler", is routinely instantiated for testing the compiler itself. I haven't seen it mocked, but computeInternalPhases is the template method for picking what phases are assembled modulo plugins.

There is a current effort to reduce internal dependencies, for the purpose of testing, as a window onto the difficulties involved.

Upvotes: 1

ghik
ghik

Reputation: 10764

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.

Upvotes: 4

Related Questions