Scala宏开发:如何从WeakTypeTag获取完整类型信息生成正确Witness
核心问题解析
1. 为什么泛型参数T1、T2会显示为占位符,而不是具体类型?
这不是编译期类型擦除导致的,根本原因是宏的展开时机和调用位置不对:
- 宏是在调用点所处的编译作用域即时展开的。你把
Type2String[^^[T1, T2]]写在了case class ^^[T1, T2]的类体内部,这个代码位置的编译发生在类的定义阶段。此时T1、T2就是这个类自身声明的两个抽象类型参数,没有绑定任何具体的类型实参,宏通过WeakTypeTag拿到的Type结构里,这两个位置就是抽象类型的符号引用,直接调用toString自然就会输出符号本身的名字"T1"、"T2"。 - 等到你写
val e1 = ^^[Int, String]()实例化类的时候,^^类的代码早已编译完成,对应的宏也已经在类定义阶段展开结束,根本不会在实例化阶段重新执行宏去捕获Int、String这些具体的类型值。最终拿到的Witness自然就是类定义阶段生成的、带占位符字符串的错误结果。
2. 如何修改代码拿到正确的shapeless.Witness.Aux[String("Int ^^ String")]?
需要从两个方向调整实现:
第一、修正宏内部的代码生成逻辑
你之前的宏实现存在两个bug,哪怕拿到了正确的类型,也生成不出合法的单例Witness:
- 之前的quasiquote写法
q"shapeless.Witness.mkWitness[$str]($str)"中,$str会被解析为代码里的变量引用,不是字符串字面量常量,既不能生成单例的字面量类型,值的位置也不是合法的单例字符串。 - 拿到Type之后需要先做别名解引用,避免拿到的是类型符号的内部名而非可读的类型描述。
修正后的宏实现示例:
def apply[A: WeakTypeTag]: Tree = { // 先解类型别名,拿到完整的带实参的类型结构 val targetType = weakTypeOf[A].dealias // 生成类型的字符串描述 val strRepr = targetType.toString // 显式构造字符串字面量的常量树,保证生成的是单例类型 val strLiteral = Literal(Constant(strRepr)) q"shapeless.Witness.mkWitness[${strLiteral.tpe}]($strLiteral)" }
第二、调整宏的调用位置,保证展开时能拿到具体的类型绑定
不能把宏调用直接放在泛型类的类体里引用类自身的抽象类型参数,否则宏永远只能拿到未绑定的占位符。可以通过两种方式让宏在“类型已经绑定具体实参”的上下文里展开:
- 方式一:直接在实例化位置调用宏,比如写
Type2String[^^[Int, String]],此时宏拿到的就是带完整实参的类型,生成的字符串就是"Int ^^ String"。 - 方式二:把类内部的
wTSelf改成实例级的方法定义,让宏在方法被调用的位置捕获当前类型。比如:
当你执行case class ^^[T1, T2]() { // 把val改成def,让宏在实际调用这个方法的位置展开 final def wTSelf: Witness.Lt[String] = macro Macros.apply[^^[T1, T2]] }e1.wTSelf的时候,宏展开的位置已经处于e1: ^^[Int, String]的作用域,能拿到绑定了具体Int、String实参的完整类型结构,最终生成的Witness的单例值就会是"Int ^^ String"。
注意:如果保留原来的
final val wTSelf = ...的写法,这个常量会在类构造时初始化,对应的宏还是会在类定义阶段展开,依然会拿到占位符的结果。
内容的提问来源于stack exchange,提问作者tribbloid
相关产品推荐
相关产品推荐

