Scala 3 quotes如何获取case class字段的默认值
访问case class字段默认值的可行方案
当前版本的TASTy/Quotes API完全支持获取case class字段默认值,之前的调用方式未命中目标,核心原因是Scala 3的默认值实现机制:参数默认值不会直接存储在字段符号上,而是由编译器在类的伴生对象中生成专属合成方法存储。
核心实现逻辑
Scala编译器为构造方法中每一个带默认值的参数,都会在对应伴生对象中生成名为<init>$default$<序号>的合成方法:
- 序号从1开始计数,对应参数在主构造方法参数列表中的位置
- 调用该合成方法即可直接返回对应参数的默认值
- 没有配置默认值的参数不会生成对应合成方法
以定义的case class User(name: String = "Shayan")为例,编译器会自动在User的伴生对象中生成<init>$default$1方法,调用该方法就会返回字符串"Shayan"。
可运行实现代码
import scala.quoted.* inline def listCaseClassDefaults[T]: Map[String, Any] = ${ listCaseClassDefaultsImpl[T] } private def listCaseClassDefaultsImpl[T: Type](using Quotes): Expr[Map[String, Any]] = import quotes.reflect.* val typeRepr = TypeRepr.of[T] val classSymbol = typeRepr.classSymbol.getOrElse( report.errorAndThrow("Target type is not a concrete class") ) // 获取主构造方法、伴生对象符号 val primaryCtor = classSymbol.primaryConstructor val companionSymbol = typeRepr.typeSymbol.companionModule val defaultEntryExprs = primaryCtor.paramSymss.flatten.zipWithIndex.flatMap { (param, index) => val targetMethodName = s"$$init$$default$$${index + 1}" // 查找对应默认值合成方法 companionSymbol.declaredMethod(targetMethodName).headOption.map { defaultMethod => val defaultExpr = Ref(companionSymbol).select(defaultMethod).asExprOf[Any] Expr(param.name -> defaultExpr) } } Expr.ofList(defaultEntryExprs).asExprOf[Map[String, Any]] // 测试验证 case class User(name: String = "Shayan", age: Int = 18, email: String) @main def runTest(): Unit = println(listCaseClassDefaults[User]) // 输出结果: Map(name -> Shayan, age -> 18)
之前调用方式无效的原因
caseFields接口仅返回字段的基础符号信息,包含名称、类型、可见性等属性,默认值属于构造调用阶段的fallback逻辑,不属于字段本身的元数据,因此不会挂载在caseFields返回的符号对象上Symbol.tree是官方明确标注的不稳定内部接口,本身就不保证返回完整的类定义结构,依赖该接口获取信息会出现随机的字段缺失,不适合在宏逻辑中使用
额外注意点
- 如果要获取case class伴生对象apply方法的参数默认值,对应的合成方法名规则为
apply$default$<序号>,查找逻辑和构造方法默认值完全一致 - 多参数列表的场景下,遍历所有参数列表的参数后按全局顺序计数即可,不需要单独处理参数列表的分层
- 合成方法的查找范围要限定在伴生对象的成员内,不要去类本身的成员里查找,类本身不会持有这些默认值方法
内容的提问来源于stack exchange,提问作者shayan
相关产品推荐
相关产品推荐

