如何用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 } }
关键细节说明
- 访问器符号获取:
elemSymbol.accessor会返回构造器参数对应的字段访问器符号,若参数没有生成访问器(比如既不是val/var,也不是Scala 3默认的私有字段),会抛出异常,确保我们能正确访问字段。 - 表达式构建:通过
Select(productTerm, accessorSymbol)构建出访问实例字段的语法树节点,再通过asExprOf[Any]转换回Expr类型,方便后续拼接。 - 原代码修正:
- 将
TypeRepr.of[T]改为TypeRepr.of[A],匹配泛型参数 - 替换
paramName为Expr(param.name),正确获取参数名称 - 移除
argValueExpr.toString,直接使用$argValueExpr传递给append方法,因为StringBuilder.append支持接收任意Any类型
- 将
内容的提问来源于stack exchange,提问作者Readren
相关产品推荐
相关产品推荐

