Changes in Compiler Plugins

Compiler plugins are supported by Dotty (and Scala 3) since 0.9. There are two notable changes compared to scalac:

  • No support for analyzer plugins
  • Added support for research plugins

Analyzer plugins in scalac run during type checking and may influence normal type checking. This is a very powerful feature but for production usages, a predictable and consistent type checker is more important.

For experimentation and research, Scala 3 introduces research plugin. Research plugins are more powerful than scalac analyzer plugins as they let plugin authors customize the whole compiler pipeline. One can easily replace the standard typer by a custom one or create a parser for a domain-specific language. However, research plugins are only enabled for nightly or snaphot releases of Scala 3.

Common plugins that add new phases to the compiler pipeline are called standard plugins in Scala 3. In terms of features, they are similar to scalac plugins, despite minor changes in the API.

Using Compiler Plugins

Both standard and research plugins can be used with scalac by adding the -Xplugin: option:

scalac -Xplugin:pluginA.jar -Xplugin:pluginB.jar Test.scala

The compiler will examine the jar provided, and look for a property file named in the root directory of the jar. The property file specifies the fully qualified plugin class name. The format of a property file is as follows:


This is different from scalac plugins that required a scalac-plugin.xml file.

Starting from 1.1.5, sbt also supports Scala 3 compiler plugins. Please refer to the sbt documentation for more information.

Writing a Standard Compiler Plugin

Here is the source code for a simple compiler plugin that reports integer divisions by zero as errors.

package dividezero

import{PluginPhase, StandardPlugin}
import{Pickler, Staging}

class DivideZero extends StandardPlugin:
  val name: String = "divideZero"
  override val description: String = "divide zero check"

  def init(options: List[String]): List[PluginPhase] =
    (new DivideZeroPhase) :: Nil

class DivideZeroPhase extends PluginPhase:
  import tpd.*

  val phaseName = "divideZero"

  override val runsAfter = Set(
  override val runsBefore = Set(

  override def transformApply(tree: Apply)(implicit ctx: Context): Tree =
    tree match
      case Apply(Select(rcvr, nme.DIV), List(Literal(Constant(0))))
      if rcvr.tpe <:< defn.IntType =>
        report.error("dividing by zero", tree.pos)
      case _ =>
end DivideZeroPhase

The plugin main class (DivideZero) must extend the trait StandardPlugin and implement the method init that takes the plugin's options as argument and returns a list of PluginPhases to be inserted into the compilation pipeline.

Our plugin adds one compiler phase to the pipeline. A compiler phase must extend the PluginPhase trait. In order to specify when the phase is executed, we also need to specify a runsBefore and runsAfter constraints that are list of phase names.

We can now transform trees by overriding methods like transformXXX.

Writing a Research Compiler Plugin

Here is a template for research plugins.


class DummyResearchPlugin extends ResearchPlugin:
  val name: String = "dummy"
  override val description: String = "dummy research plugin"

  def init(options: List[String], phases: List[List[Phase]])(implicit ctx: Context): List[List[Phase]] =
end DummyResearchPlugin

A research plugin must extend the trait ResearchPlugin and implement the method init that takes the plugin's options as argument as well as the compiler pipeline in the form of a list of compiler phases. The method can replace, remove or add any phases to the pipeline and return the updated pipeline.

Generated byscaladoc
Copyright (c) 2002-2022, LAMP/EPFL
Social links
Back to top
In this article