在 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.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): 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 = ...