You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Scala 3.5.1宏获取普通类主构造器字段的Expr

Scala 3元编程:获取普通类主构造器字段值

我需要在Scala 3.5.1中通过元编程获取普通类(非样例类)主构造器声明字段的值,核心是实现select函数,用来在生成toString方法的逻辑中获取每个构造器参数对应的实例值。原toStrImpl方法作为背景示例,用来自动生成类的toString实现,但select函数尚未完成,且原代码存在几处需要修正的细节。

核心实现思路

普通类的主构造器参数若要能被外部访问,要么显式用val/var声明,要么Scala 3会默认生成私有字段和对应的访问器方法。我们可以通过构造器参数符号的accessor方法获取对应的访问器符号,然后构建一个选择表达式(Term.Select)来访问实例的该字段,最后转换回Expr类型。

完整代码实现

import scala.quoted.*

/** Given the expression `productExpr` that gets an instance of
 * a regular class `A`, and the symbol of a parameter of `A`'s
 * primary-constructor, obtain the expression that gets the value
 * of said parameter. */
def select[A: Type](using quotes: Quotes)(productExpr: Expr[A], elemSymbol: quotes.reflect.Symbol): Expr[Any] =
  import quotes.reflect.*
  // 获取构造器参数对应的访问器符号
  val accessorSymbol = elemSymbol.accessor.getOrElse(
    throw new IllegalArgumentException(s"Parameter ${elemSymbol.name} has no accessor")
  )
  // 构建访问实例字段的表达式
  val productTerm = productExpr.asTerm
  val selectTerm = Select(productTerm, accessorSymbol)
  // 转换回Expr类型
  selectTerm.asExprOf[Any]

def toStrImpl[A: Type](product: Expr[A])(using quotes: Quotes): Expr[String] = {
  import quotes.reflect.*

  // 修正:原代码中TypeRepr.of[T]应为TypeRepr.of[A]
  val productSymbol = TypeRepr.of[A].typeSymbol
  val primaryConstructor = productSymbol.primaryConstructor
  var stringBuilderExpr = '{ new StringBuilder(${ Expr(productSymbol.name) }) }

  for {
    paramsList <- primaryConstructor.paramSymss
    if paramsList.forall(_.isTerm) // 跳过类型参数
  } do {
    stringBuilderExpr = '{ $stringBuilderExpr.append('(') }
    var isFirst = true
    for param <- paramsList do {
      if isFirst then isFirst = false 
      else stringBuilderExpr = '{ $stringBuilderExpr.append(", ") }
      
      val argValueExpr: Expr[Any] = select(product, param)
      // 修正:原代码中paramName需要获取param.name,且argValueExpr.toString错误,直接使用$argValueExpr
      stringBuilderExpr = '{ 
        $stringBuilderExpr.append(${ Expr(param.name) }).append('=').append($argValueExpr) 
      }
    }
    stringBuilderExpr = '{ $stringBuilderExpr.append(')') }
  }
  '{ $stringBuilderExpr.toString }
}

关键细节说明

  1. 访问器符号获取:elemSymbol.accessor会返回构造器参数对应的字段访问器符号,若参数没有生成访问器(比如既不是val/var,也不是Scala 3默认的私有字段),会抛出异常,确保我们能正确访问字段。
  2. 表达式构建:通过Select(productTerm, accessorSymbol)构建出访问实例字段的语法树节点,再通过asExprOf[Any]转换回Expr类型,方便后续拼接。
  3. 原代码修正:
    • 将TypeRepr.of[T]改为TypeRepr.of[A],匹配泛型参数
    • 替换paramName为Expr(param.name),正确获取参数名称
    • 移除argValueExpr.toString,直接使用$argValueExpr传递给append方法,因为StringBuilder.append支持接收任意Any类型

内容的提问来源于stack exchange,提问作者Readren

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 20:24:51