Scala宏需求:查找带类型参数的密封特质的直接已知子类
在Scala宏中获取带类型参数的密封特质的具体化子类型
问题场景
假设我们有如下带类型参数的密封特质及其子类:
sealed trait T[A] case class C[A]() extends T[A]
在宏中持有T[String]的WeakTypeTag时,通过.asClass.knownDirectSubclasses只能拿到C[A],无法直接得到C[String],需要手动完成类型参数的替换。
解决方案核心思路
- 从具体化的父类型(
T[String])中提取原始父类(T)和类型参数映射(将父类的类型变量A映射到具体类型String)。 - 遍历父类的所有直接子类,将子类的类型变量按照上述映射替换为具体类型,构造出具体化的子类类型(如
C[String])。
完整宏实现代码
import scala.reflect.macros.blackbox import scala.language.experimental.macros import scala.reflect.runtime.universe._ object SubtypeMacro { // 对外暴露的接口方法 def findConcreteSubtypes[T: WeakTypeTag]: List[Type] = macro findConcreteSubtypesImpl[T] // 宏的具体实现 def findConcreteSubtypesImpl[T: WeakTypeTag](c: blackbox.Context): c.Tree = { import c.universe._ val baseConcreteType = weakTypeTag[T].tpe // 获取密封特质的原始类(不带类型参数的T) val baseRawClass = baseConcreteType.typeSymbol.asClass // 提取原始类的类型参数列表(比如T的类型参数A) val baseTypeParams = baseRawClass.typeParams // 提取具体化后的类型参数(比如T[String]中的String) val baseConcreteArgs = baseConcreteType.args // 构建类型变量 -> 具体类型的映射表 val typeParamMap = baseTypeParams.zip(baseConcreteArgs).toMap // 处理每个直接子类,生成具体化类型 val concreteSubtypes = baseRawClass.knownDirectSubclasses.map { subclassSymbol => val subclassRawClass = subclassSymbol.asClass // 替换子类的类型参数:如果子类的类型参数在父类的映射表中存在,则替换为具体类型,否则保留原变量 val substitutedArgs = subclassRawClass.typeParams.map { param => typeParamMap.getOrElse(param, param.asType.toType) } // 构造带具体参数的子类类型 appliedType(subclassSymbol.asType.toTypeConstructor, substitutedArgs) }.toList // 将结果转换成Scala代码树,返回给调用处 q"$concreteSubtypes" } }
测试示例
sealed trait T[A] case class C[A]() extends T[A] // 测试多参数子类的情况 case class D[A, B]() extends T[(A, B)] object TestMacro extends App { // 获取T[String]的具体化子类 val stringSubtypes = SubtypeMacro.findConcreteSubtypes[T[String]] println(stringSubtypes) // 输出: List(C[String]) // 获取T[(Int, Double)]的具体化子类 val tupleSubtypes = SubtypeMacro.findConcreteSubtypes[T[(Int, Double)]] println(tupleSubtypes) // 输出: List(D[Int, Double]) }
关键细节说明
- 类型映射的准确性:通过
baseTypeParams.zip(baseConcreteArgs)确保父类的类型变量和具体类型一一对应,避免替换错位。 appliedType的优势:相比直接使用substituteTypes,appliedType直接通过类型构造器+参数列表生成类型,更适配子类类型参数与父类不完全一致的场景(比如子类有额外参数)。- 递归查找扩展:如果需要递归查找所有子类(包括间接子类),可以在遍历
knownDirectSubclasses时,对每个子类再递归调用相同逻辑,收集所有层级的子类型。
内容的提问来源于stack exchange,提问作者Markus Appel
相关产品推荐
相关产品推荐

