Skip to content

add documentation for compiler plugin #4658

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jul 4, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 129 additions & 0 deletions docs/docs/reference/changed/compiler-plugins.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
---
layout: doc-page
title: "Changes in Compiler Plugins"
---

Compiler plugins are supported by Dotty since 0.9. There are two notable changes
compared to `scalac`:

- No support for analyzer plugins
- Added support for research plugins

[Analyzer plugins][1] 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, Dotty 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 Dotty.

Common plugins that add new phases to the compiler pipeline are called
_standard plugins_ in Dotty. 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 `dotc` by adding the `-Xplugin:` option:

```shell
dotc -Xplugin:pluginA.jar -Xplugin:pluginB.jar Test.scala
```

The compiler will examine the jar provided, and look for a property file named
`plugin.properties` 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 follow:

```properties
pluginClass=dividezero.DivideZero
```

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

Starting from 1.1.5, `sbt` also supports Dotty compiler plugins. Please refer to the
`sbt` [documentation][2] 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.

```scala
package dividezero

import dotty.tools.dotc.ast.Trees._
import dotty.tools.dotc.ast.tpd
import dotty.tools.dotc.core.Constants.Constant
import dotty.tools.dotc.core.Contexts.Context
import dotty.tools.dotc.core.Decorators._
import dotty.tools.dotc.core.StdNames._
import dotty.tools.dotc.core.Symbols._
import dotty.tools.dotc.plugins.{PluginPhase, StandardPlugin}
import dotty.tools.dotc.transform.{LinkAll, Pickler}

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(Pickler.name)
override val runsBefore = Set(LinkAll.name)

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 =>
ctx.error("dividing by zero", tree.pos)
case _ =>
()
}
tree
}
}
```

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 `PluginPhase`s 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 by overriding methods like `transformXXX`.

## Writing a Research Compiler Plugin

Here is a template for research plugins.

```scala
import dotty.tools.dotc.core.Contexts.Context
import dotty.tools.dotc.core.Phases.Phase
import dotty.tools.dotc.plugins.ResearchPlugin

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]] =
phases
}
```

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.


[1]: https://github.com/scala/scala/blob/2.13.x/src/compiler/scala/tools/nsc/typechecker/AnalyzerPlugins.scala
[2]: https://www.scala-sbt.org/1.x/docs/Compiler-Plugins.html
2 changes: 2 additions & 0 deletions docs/sidebar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ sidebar:
url: docs/reference/changed/pattern-matching.html
- title: Eta Expansion
url: docs/reference/changed/eta-expansion.html
- title: Compiler Plugins
url: docs/reference/changed/compiler-plugins.html
- title: Dropped Features
subsection:
- title: DelayedInit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class DivideZero extends PluginPhase with StandardPlugin {
override val runsAfter = Set(Pickler.name)
override val runsBefore = Set(LinkAll.name)

override def init(options: List[String]): List[PluginPhase] = this :: Nil
def init(options: List[String]): List[PluginPhase] = this :: Nil

private def isNumericDivide(sym: Symbol)(implicit ctx: Context): Boolean = {
def test(tpe: String): Boolean =
Expand Down
2 changes: 1 addition & 1 deletion tests/plugins/neg/divideZero-research/plugin_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class DivideZero extends MiniPhase with ResearchPlugin {

val phaseName = name

override def init(options: List[String], phases: List[List[Phase]])(implicit ctx: Context): List[List[Phase]] = {
def init(options: List[String], phases: List[List[Phase]])(implicit ctx: Context): List[List[Phase]] = {
val (before, after) = phases.span(ps => !ps.exists(_.phaseName == "pickler"))
before ++ (List(this) :: after)
}
Expand Down
41 changes: 0 additions & 41 deletions tests/plugins/pos/divideZero/plugin_1.scala

This file was deleted.