编译器插件的变化
从 Dotty 0.9 开始,Scala 3 支持编译器插件。与 Scala 2 相比,有两个显著的更改
- 不支持分析器插件
- 添加了对研究插件的支持
分析器插件 在 Scala 2 中的类型检查期间运行,并且可能会影响正常的类型检查。这是一个非常强大的功能,但对于生产用途,可预测且一致的类型检查器更为重要。
为了进行实验和研究,Scala 3 引入了研究插件。研究插件比 Scala 2 分析器插件更强大,因为它们允许插件作者自定义整个编译器管道。可以轻松地用自定义类型检查器替换标准类型检查器,或为特定领域语言创建解析器。但是,研究插件仅在使用-experimental
编译器标志或在 Scala 3 的夜间/快照版本中启用。
在 Scala 3 中,向编译器管道添加新阶段的通用插件称为标准插件。在功能方面,它们类似于scalac
插件,尽管 API 有细微的更改。
使用编译器插件
在 Scala 3 中,标准插件和研究插件都可以通过添加 -Xplugin:
选项与 scalac
一起使用。
scalac -Xplugin:pluginA.jar -Xplugin:pluginB.jar Test.scala
编译器会检查提供的 jar 文件,并在 jar 文件的根目录中查找名为 plugin.properties
的属性文件。属性文件指定了完全限定的插件类名。属性文件的格式如下:
pluginClass=dividezero.DivideZero
这与需要 scalac-plugin.xml
文件的 Scala 2 插件不同。
从 1.1.5 版本开始,sbt
也支持 Scala 3 编译器插件。有关更多信息,请参阅 sbt
文档。
编写标准编译器插件
以下是一个简单编译器插件的源代码,该插件将整数除以零报告为错误。
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.{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(Pickler.name)
override val runsBefore = Set(Staging.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 =>
report.error("dividing by zero", tree.pos)
case _ =>
()
tree
end DivideZeroPhase
插件主类 (DivideZero
) 必须扩展 StandardPlugin
特性并实现 init
方法,该方法以插件选项作为参数,并返回要插入编译管道的 PluginPhase
列表。
我们的插件向管道添加了一个编译阶段。编译阶段必须扩展 PluginPhase
特性。为了指定阶段执行的时间,我们还需要指定 runsBefore
和 runsAfter
约束,它们是阶段名称列表。
我们现在可以通过覆盖 transformXXX
等方法来转换树。
编写研究编译器插件
以下是一个研究插件的模板。
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
end DummyResearchPlugin
研究插件必须扩展 ResearchPlugin
特性并实现 init
方法,该方法以插件选项作为参数,以及以编译阶段列表形式的编译管道。该方法可以替换、删除或添加管道中的任何阶段,并返回更新后的管道。