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

Scala宏需求:查找带类型参数的密封特质的直接已知子类

在Scala宏中获取带类型参数的密封特质的具体化子类型

问题场景

假设我们有如下带类型参数的密封特质及其子类:

sealed trait T[A]
case class C[A]() extends T[A]

在宏中持有T[String]的WeakTypeTag时,通过.asClass.knownDirectSubclasses只能拿到C[A],无法直接得到C[String],需要手动完成类型参数的替换。

解决方案核心思路

  1. 从具体化的父类型(T[String])中提取原始父类(T)和类型参数映射(将父类的类型变量A映射到具体类型String)。
  2. 遍历父类的所有直接子类,将子类的类型变量按照上述映射替换为具体类型,构造出具体化的子类类型(如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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 16:36:22