反射
反射允许检查和构建类型化抽象语法树 (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 = ...