Skip to content

Commit cf7b589

Browse files
liufengyunallanrenucci
authored andcommitted
Add documentation for compiler plugins (#4658)
1 parent a6b6690 commit cf7b589

File tree

5 files changed

+133
-43
lines changed

5 files changed

+133
-43
lines changed
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
---
2+
layout: doc-page
3+
title: "Changes in Compiler Plugins"
4+
---
5+
6+
Compiler plugins are supported by Dotty since 0.9. There are two notable changes
7+
compared to `scalac`:
8+
9+
- No support for analyzer plugins
10+
- Added support for research plugins
11+
12+
[Analyzer plugins][1] in `scalac` run during type checking and may influence
13+
normal type checking. This is a very powerful feature but for production usages,
14+
a predictable and consistent type checker is more important.
15+
16+
For experimentation and research, Dotty introduces _research plugin_. Research plugins
17+
are more powerful than `scalac` analyzer plugins as they let plugin authors customize
18+
the whole compiler pipeline. One can easily replace the standard typer by a custom one or
19+
create a parser for a domain-specific language. However, research plugins are only
20+
enabled for nightly or snaphot releases of Dotty.
21+
22+
Common plugins that add new phases to the compiler pipeline are called
23+
_standard plugins_ in Dotty. In terms of features, they are similar to
24+
`scalac` plugins, despite minor changes in the API.
25+
26+
## Using Compiler Plugins
27+
28+
Both standard and research plugins can be used with `dotc` by adding the `-Xplugin:` option:
29+
30+
```shell
31+
dotc -Xplugin:pluginA.jar -Xplugin:pluginB.jar Test.scala
32+
```
33+
34+
The compiler will examine the jar provided, and look for a property file named
35+
`plugin.properties` in the root directory of the jar. The property file specifies
36+
the fully qualified plugin class name. The format of a property file is as follow:
37+
38+
```properties
39+
pluginClass=dividezero.DivideZero
40+
```
41+
42+
This is different from `scalac` plugins that required a `scalac-plugin.xml` file.
43+
44+
Starting from 1.1.5, `sbt` also supports Dotty compiler plugins. Please refer to the
45+
`sbt` [documentation][2] for more information.
46+
47+
## Writing a Standard Compiler Plugin
48+
49+
Here is the source code for a simple compiler plugin that reports integer divisions by
50+
zero as errors.
51+
52+
```scala
53+
package dividezero
54+
55+
import dotty.tools.dotc.ast.Trees._
56+
import dotty.tools.dotc.ast.tpd
57+
import dotty.tools.dotc.core.Constants.Constant
58+
import dotty.tools.dotc.core.Contexts.Context
59+
import dotty.tools.dotc.core.Decorators._
60+
import dotty.tools.dotc.core.StdNames._
61+
import dotty.tools.dotc.core.Symbols._
62+
import dotty.tools.dotc.plugins.{PluginPhase, StandardPlugin}
63+
import dotty.tools.dotc.transform.{LinkAll, Pickler}
64+
65+
class DivideZero extends StandardPlugin {
66+
val name: String = "divideZero"
67+
override val description: String = "divide zero check"
68+
69+
def init(options: List[String]): List[PluginPhase] = (new DivideZeroPhase) :: Nil
70+
}
71+
72+
class DivideZeroPhase extends PluginPhase {
73+
import tpd._
74+
75+
val phaseName = "divideZero"
76+
77+
override val runsAfter = Set(Pickler.name)
78+
override val runsBefore = Set(LinkAll.name)
79+
80+
override def transformApply(tree: Apply)(implicit ctx: Context): Tree = {
81+
tree match {
82+
case Apply(Select(rcvr, nme.DIV), List(Literal(Constant(0))))
83+
if rcvr.tpe <:< defn.IntType =>
84+
ctx.error("dividing by zero", tree.pos)
85+
case _ =>
86+
()
87+
}
88+
tree
89+
}
90+
}
91+
```
92+
93+
The plugin main class (`DivideZero`) must extend the trait `StandardPlugin`
94+
and implement the method `init` that takes the plugin's options as argument
95+
and returns a list of `PluginPhase`s to be inserted into the compilation pipeline.
96+
97+
Our plugin adds one compiler phase to the pipeline. A compiler phase must extend
98+
the `PluginPhase` trait. In order to specify when the phase is executed, we also
99+
need to specify a `runsBefore` and `runsAfter` constraints that are list of phase
100+
names.
101+
102+
We can now transform trees by by overriding methods like `transformXXX`.
103+
104+
## Writing a Research Compiler Plugin
105+
106+
Here is a template for research plugins.
107+
108+
```scala
109+
import dotty.tools.dotc.core.Contexts.Context
110+
import dotty.tools.dotc.core.Phases.Phase
111+
import dotty.tools.dotc.plugins.ResearchPlugin
112+
113+
class DummyResearchPlugin extends ResearchPlugin {
114+
val name: String = "dummy"
115+
override val description: String = "dummy research plugin"
116+
117+
def init(options: List[String], phases: List[List[Phase]])(implicit ctx: Context): List[List[Phase]] =
118+
phases
119+
}
120+
```
121+
122+
A research plugin must extend the trait `ResearchPlugin` and implement the
123+
method `init` that takes the plugin's options as argument as well as the compiler
124+
pipeline in the form of a list of compiler phases. The method can replace, remove
125+
or add any phases to the pipeline and return the updated pipeline.
126+
127+
128+
[1]: https://github.com/scala/scala/blob/2.13.x/src/compiler/scala/tools/nsc/typechecker/AnalyzerPlugins.scala
129+
[2]: https://www.scala-sbt.org/1.x/docs/Compiler-Plugins.html

docs/sidebar.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ sidebar:
7979
url: docs/reference/changed/pattern-matching.html
8080
- title: Eta Expansion
8181
url: docs/reference/changed/eta-expansion.html
82+
- title: Compiler Plugins
83+
url: docs/reference/changed/compiler-plugins.html
8284
- title: Dropped Features
8385
subsection:
8486
- title: DelayedInit

sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/plugin/DivideZero.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class DivideZero extends PluginPhase with StandardPlugin {
2222
override val runsAfter = Set(Pickler.name)
2323
override val runsBefore = Set(LinkAll.name)
2424

25-
override def init(options: List[String]): List[PluginPhase] = this :: Nil
25+
def init(options: List[String]): List[PluginPhase] = this :: Nil
2626

2727
private def isNumericDivide(sym: Symbol)(implicit ctx: Context): Boolean = {
2828
def test(tpe: String): Boolean =

tests/plugins/neg/divideZero-research/plugin_1.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class DivideZero extends MiniPhase with ResearchPlugin {
1616

1717
val phaseName = name
1818

19-
override def init(options: List[String], phases: List[List[Phase]])(implicit ctx: Context): List[List[Phase]] = {
19+
def init(options: List[String], phases: List[List[Phase]])(implicit ctx: Context): List[List[Phase]] = {
2020
val (before, after) = phases.span(ps => !ps.exists(_.phaseName == "pickler"))
2121
before ++ (List(this) :: after)
2222
}

tests/plugins/pos/divideZero/plugin_1.scala

Lines changed: 0 additions & 41 deletions
This file was deleted.

0 commit comments

Comments
 (0)