在 GitHub 上編輯此頁面

編譯器外掛程式中的變更

自 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 特質。為了指定階段何時執行,我們還需要指定 runsBeforerunsAfter 約束,這些約束是階段名稱的清單。

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