Scala 2元编程:typer阶段后通过编译器插件向类追加方法报错求解
核心问题原因
typer阶段完成后,所有AST节点已经绑定了对应的符号和类型属性,符号表的全局注册也已经完成,直接插入未做属性绑定的新节点、重新对整个类做类型检查、随意修改已有类的符号注册都会破坏符号系统一致性,触发断言错误。
最优解决方案:调整插件运行时机
如果仅需要获取包、类继承关系等基础信息,不需要依赖typer阶段的完整类型推导结果,直接将插件运行时机调整到typer阶段之前、namer阶段之后即可,此时插入未类型化的方法AST,后续typer阶段会自动完成符号注册和类型推导,不需要手动处理任何属性绑定:
// 插件阶段声明 override val phaseName = "method-inject-phase" override def runsBefore: Set[String] = Set(typer.phaseName) override def runsAfter: Set[String] = Set(namer.phaseName) // 转换逻辑 tree match { case pl @ ClassDef(mods, name, tparams, e @ Template(parent, self, body)) => parent.lift(1) match { case Some(a @ TypeTree()) => a.original match { case AppliedTypeTree(Select(This(TypeName(s)), tpt), args) => if (tpt.toString == "Policy") { // 直接插入quasiquote生成的未类型化方法即可 val insert = q""" def q(): String = "hello" """ pl.copy( impl = e.copy(body = body :+ insert) ) } else super.transform(tree) case _ => super.transform(tree) } case _ => super.transform(tree) } case _ => super.transform(tree) }
必须在typer阶段之后运行的正确实现
如果确实依赖typer阶段的完整类型信息,需要严格遵循以下流程,不能修改原有类的符号注册、不能重新对整个类做类型检查:
// 插件阶段声明 override val phaseName = "method-wiring-phase" override def runsAfter: Set[String] = Set(typer.phaseName) // 转换逻辑 tree match { case pl @ ClassDef(mods, name, tparams, e @ Template(parent, self, body)) if pl.symbol.info.baseClasses.exists(_.name.toString == "Policy") => // 1. 构造带预定义类型的方法节点 val stringType = typeOf[String] val methodRhs = Literal(Constant("hello")).setType(stringType) val newMethod = DefDef( Modifiers(), TermName("q"), Nil, List(Nil), TypeTree(stringType), methodRhs ) // 2. 为新方法创建符号,绑定所属类 val methodSym = pl.symbol.newMethod(TermName("q"), newMethod.mods.flags) methodSym.setInfo(MethodType(Nil, stringType)) newMethod.setSymbol(methodSym) // 3. 注册新方法符号到目标类的声明列表 pl.symbol.info.decls.enter(methodSym) // 4. 仅对新方法做类型检查,不要重新检查整个类 val typedMethod = atOwner(pl.symbol) { localTyper.typed(newMethod) } // 5. 直接返回修改后的类节点即可 pl.copy( impl = e.copy(body = body :+ typedMethod) ) case _ => super.transform(tree) }
原有实现的错误点
- 对已经完成类型检查的
ClassDef重新调用localTyper.typed,导致类符号重复注册,原有符号失效,触发NoSymbol断言错误。 - 错误调用
pl.symbol.owner.info.decls.unlink(pl.symbol)移除了原有类的符号注册,导致类的所有原有声明丢失,编译器认为类未实现抽象方法要求声明为abstract。 - 插入的新方法节点没有绑定对应的符号和类型属性,类型检查器无法识别节点类型信息。
内容的提问来源于stack exchange,提问作者sinanspd
相关产品推荐
相关产品推荐

