反射
反射允许检查和构建类型化抽象语法树 (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
上的方法。例如,下面使用的 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 = ...