如何在Scala 3宏中调用内部类的构造方法?
解决Scala宏中内部类的"stale symbol"错误
问题根源
你碰到的stale symbol错误,是因为内部类Inner的符号在宏展开阶段和类定义阶段处于不同编译周期,直接通过$outer.Inner引用内部类构造器会导致符号失效。Scala 3宏处理内部类时,不能简单通过外部实例直接访问内部类构造器,必须正确处理内部类的符号上下文。
可行解决方案
方案1:通过反射API精准获取内部类构造器
修改宏实现,利用quotes的反射能力找到Inner类的符号,再生成合法的构造调用表达式:
inline def makeInnerMacro[X, Y, Z]( inline outer: Outer[X], inline l: List[Y], inline f: Y => Z, ): Outer[X]#Inner[Y, Z] = ${ makeInnerImpl('{outer}, '{l}, '{f}) } def makeInnerImpl[X: Type, Y: Type, Z: Type]( outer: Expr[Outer[X]], l: Expr[List[Y]], f: Expr[Y => Z], )(using qCtx: Quotes): Expr[Outer[X]#Inner[Y, Z]] = import qCtx.reflect.* // 获取外部类Outer的符号 val outerClassSymbol = TypeRepr.of[Outer[X]].typeSymbol.asClass // 查找内部类Inner的符号 val innerClassSymbol = outerClassSymbol.declaredType("Inner").typeSymbol.asClass // 获取Inner的主构造器 val innerConstructor = innerClassSymbol.primaryConstructor // 准备构造参数,用show替代toString获取更准确的Lambda源码 val fString = Expr(f.show) val constructorArgs = List(outer, l, f, fString).map(_.asTerm) // 生成构造调用并转换为目标类型的Expr val innerInstance = Apply( Select.unique(outer.asTerm, innerClassSymbol.name), constructorArgs ) innerInstance.asExprOf[Outer[X]#Inner[Y, Z]]
方案2:调整构造调用的写法,明确路径依赖
通过引入临时变量持有外部实例,避免直接在宏引号中通过外部实例引用内部类,确保符号上下文正确:
inline def makeInnerMacro[X, Y, Z]( inline outer: Outer[X], inline l: List[Y], inline f: Y => Z, ): Outer[X]#Inner[Y, Z] = ${ makeInnerImpl('{outer}, '{l}, '{f}) } def makeInnerImpl[X: Type, Y: Type, Z: Type]( outer: Expr[Outer[X]], l: Expr[List[Y]], f: Expr[Y => Z], )(using qCtx: Quotes): Expr[Outer[X]#Inner[Y, Z]] = import qCtx.reflect.* val fString = Expr(f.show) // 通过临时变量持有外部实例,再调用内部类构造器 '{ val outerInst = $outer new outerInst.Inner($l, $f, $fString) }
关键注意点
- 用
f.show替代f.toString:show方法能精准获取Lambda的源码字符串,而toString可能返回编译器生成的内部标识,不符合需求。 - 内部类符号的正确获取:必须通过外部类的符号上下文查找内部类,不能直接通过实例访问,否则会触发符号过期错误。
- 路径依赖类型:
Outer[X]#Inner[Y,Z]是标准的类型投影写法,确保宏返回类型与实际内部类实例类型匹配。
修改后,MyOuter.inner会正确生成为Inner(List(0, 1, 2), <func>, "num => num.toString"),编译错误彻底消除。
内容的提问来源于stack exchange,提问作者Todd O'Bryan
相关产品推荐
相关产品推荐

