反射
反射能檢查和建構型別抽象語法樹 (Typed-AST)。它可用於來自 巨集 的引號表達式 (quoted.Expr
) 和引號型別 (quoted.Type
),或用於完整的 TASTy 檔案。
如果您正在撰寫巨集,請先閱讀 巨集。您可能會發現所有您需要的不使用引號反射。
API:從引號和拼接轉換至 TASTy 反射樹,反之亦然
使用 quoted.Expr
和 quoted.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.Tree
s 的方法。例如,下方使用的 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): X
對 tree
的每個子項呼叫 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 = ...