在 GitHub 上編輯此頁面

反射

反射能檢查和建構型別抽象語法樹 (Typed-AST)。它可用於來自 巨集 的引號表達式 (quoted.Expr) 和引號型別 (quoted.Type),或用於完整的 TASTy 檔案。

如果您正在撰寫巨集,請先閱讀 巨集。您可能會發現所有您需要的不使用引號反射。

API:從引號和拼接轉換至 TASTy 反射樹,反之亦然

使用 quoted.Exprquoted.Type,我們可以計算程式碼,也可以透過檢查 AST 來分析程式碼。巨集 提供程式碼產生將會是型別正確的保證。使用引號反射將會打破這些保證,並且可能在巨集擴充時間失敗,因此必須執行其他明確檢查。

要在巨集中提供反射功能,我們需要新增型別為 scala.quoted.Quotes 的隱含參數,並從它匯入 quotes.reflect.* 至使用它的範圍。

import scala.quoted.*

inline def natConst(inline x: Int): Int = ${natConstImpl('{x})}

def natConstImpl(x: Expr[Int])(using Quotes): Expr[Int] =
  import quotes.reflect.*
  ...

萃取器

import quotes.reflect.* 將會提供所有萃取器和 quotes.reflect.Trees 的方法。例如,下方使用的 Literal(_) 萃取器。

def natConstImpl(x: Expr[Int])(using Quotes): Expr[Int] =
  import quotes.reflect.*
  val tree: Term = x.asTerm
  tree match
    case Inlined(_, _, Literal(IntConstant(n))) =>
      if n <= 0 then
        report.error("Parameter must be natural number")
        '{0}
      else
        tree.asExprOf[Int]
    case _ =>
      report.error("Parameter must be a known constant")
      '{0}

我們可以使用 Printer.TreeStructure.show 輕鬆得知需要哪些萃取器,它會傳回樹狀結構的字串表示法。其他印表機也可以在 Printer 模組中找到。

tree.show(using Printer.TreeStructure)
// or
Printer.TreeStructure.show(tree)

方法 quotes.reflect.Term.{asExpr, asExprOf} 提供一種方法,可以返回 quoted.Expr。請注意,asExpr 會傳回 Expr[Any]。另一方面,asExprOf[T] 會傳回 Expr[T],如果類型不符合,則會在執行階段擲回例外。

位置

脈絡中的 Position 提供 ofMacroExpansion 值。它對應巨集的擴充位置。巨集作者可以取得關於該擴充位置的各種資訊。以下範例顯示我們如何取得位置資訊,例如開始行、結束行,甚至擴充點的原始碼。

def macroImpl()(quotes: Quotes): Expr[Unit] =
  import quotes.reflect.*
  val pos = Position.ofMacroExpansion

  val path = pos.sourceFile.jpath.toString
  val start = pos.start
  val end = pos.end
  val startLine = pos.startLine
  val endLine = pos.endLine
  val startColumn = pos.startColumn
  val endColumn = pos.endColumn
  val sourceCode = pos.sourceCode
  ...

樹狀結構公用程式

quotes.reflect 包含三個樹狀結構橫越和轉換的工具。

TreeAccumulator[X] 允許您橫越樹狀結構,並沿途彙總 X 型別的資料,方法是覆寫其方法 foldTree(x: X, tree: Tree)(owner: Symbol): X

foldOverTree(x: X, tree: Tree)(owner: Symbol): Xtree 的每個子項呼叫 foldTree(使用 fold 來提供每個呼叫前一個呼叫的值)。

例如,以下程式碼收集樹狀結構中的 val 定義。

def collectPatternVariables(tree: Tree)(using ctx: Context): List[Symbol] =
  val acc = new TreeAccumulator[List[Symbol]]:
    def foldTree(syms: List[Symbol], tree: Tree)(owner: Symbol): List[Symbol] = tree match
      case ValDef(_, _, rhs) =>
        val newSyms = tree.symbol :: syms
        foldTree(newSyms, body)(tree.symbol)
      case _ =>
        foldOverTree(syms, tree)(owner)
  acc(Nil, tree)

TreeTraverser 延伸 TreeAccumulator[Unit],並執行相同的橫越,但不會傳回任何值。

TreeMap 透過橫越轉換樹狀結構,透過覆載其方法,可以只轉換特定型別的樹狀結構,例如 transformStatement 只會轉換 Statement

ValDef.let

物件 quotes.reflect.ValDef 也提供一個方法 let,它允許我們將 rhs(右側)繫結到 val 並在 body 中使用它。此外,lets 將給定的 terms 繫結到名稱,並允許在 body 中使用它們。它們的類型定義如下所示

def let(rhs: Term)(body: Ident => Term): Term = ...

def lets(terms: List[Term])(body: List[Term] => Term): Term = ...