編譯器外掛程式中的變更
自 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
,該方法會將外掛程式的選項作為引數,以及以編譯器階段清單形式提供的編譯器管線。該方法可以替換、移除或新增任何階段到管線,並傳回更新後的管線。