如何在Scala 3编译器插件中正确覆写类方法?
问题
需要在Scala 3编译器插件中,为继承SpecialSerialize trait的类自动生成覆写的toJson方法。示例类代码如下:
trait SpecialSerialize { def toJson(sb: StringBuilder, c:SJConfig): Unit = {println("wrong")} } case class Person(name:String, age:Int) extends SpecialSerialize
预期生成的代码:
case class Person(name:String, age:Int) extends SpecialSerialize { override def toJson(sb: StringBuilder, c:SJConfig): Unit = ... // 代码实现 }
编写的插件阶段代码如下:
class ReflectionWorkerPhase extends PluginPhase { import tpd._ val phaseName = "reflectionWorker" override val runsAfter = Set(Pickler.name) override def transformTypeDef(tree: TypeDef)(implicit ctx: Context): Tree = if tree.isClassDef && !tree.rhs.symbol.isStatic then // only look at classes // 0. Get a FreshContext so we can set the tree to this tree. (for '{} later) implicit val fresh = ctx.fresh fresh.setTree(tree) QuotesCache.init(fresh) implicit val quotes:Quotes = QuotesImpl.apply() // picks up fresh import quotes.reflect.* // 1. Set up method symbol, define parameters and return type val toJsonSymbol = Symbol.newMethod( Symbol.spliceOwner, "toJson", MethodType( List("sb","config"))( // parameter list _ => List( // types of the parameters TypeRepr.of[StringBuilder], TypeRepr.of[SJConfig], ), _ => TypeRepr.typeConstructorOf(classOf[Unit]) // return type ), Flags.Override, // Note override here Symbol.noSymbol ) // 2. Get our class' Symbol for ownership reassignment val classDef = tree.asInstanceOf[ClassDef] val classSymbol = classDef.symbol // 3. Define our method definition (DefDef) using our method symbol defined above val toJsonMethodDef = DefDef( toJsonSymbol, { case List(List(sb: Term, config: Term)) => given Quotes = toJsonSymbol.asQuotes Some({ // Multiple quotes here intentional... // Real code will generate a list of quoted statements quoted.Expr.ofList(List( '{ println("Hello") }, '{ println("World") } )) }.asTerm.changeOwner(toJsonSymbol)) } ).changeOwner(classSymbol) // 4. Add toJsonMethodDef to tree and return val cd = ClassDef.copy(classDef)( name = classDef.name, constr = classDef.constructor, parents = classDef.parents, selfOpt = classDef.self, body = toJsonMethodDef +: classDef.body ) cd.asInstanceOf[dotty.tools.dotc.ast.tpd.Tree] else tree }
运行插件编译Person类时出现错误:
Exception in thread "sbt-bg-threads-1" java.lang.ClassFormatError: Duplicate method name "toJson" with signature "(Lscala.collection.mutable.StringBuilder;Lco.blocke.scala_reflection.SJConfig;)V" in class file com/foo/Person at java.base/java.lang.ClassLoader.defineClass1(Native Method) ...
原因是后续编译阶段未将生成的方法识别为合法覆写,仍复制了trait中的toJson方法,导致JVM出现重复方法异常。需要修复让生成的方法被正确识别为覆写,避免重复生成。
解决方案
调整插件阶段的执行顺序
当前插件阶段runsAfter = Set(Pickler.name)执行时机过晚,编译器已经完成了trait方法的继承复制逻辑。需要将插件阶段提前到SuperAccessors之前执行,也就是在编译器生成trait方法的桥接/复制代码前介入。修改阶段配置:
override val runsAfter = Set(typer.name) override val runsBefore = Set(SuperAccessors.name)
这样插件生成的覆写方法会被编译器优先识别,避免后续阶段再复制trait中的原方法。
正确关联覆写的目标符号
手动创建的toJsonSymbol需要明确关联到SpecialSerialize trait中的toJson方法符号,而非凭空生成新符号,这样编译器才能判定这是合法的覆写:
// 获取SpecialSerialize trait中的toJson方法符号 val specialSerializeType = TypeRepr.of[SpecialSerialize] val traitToJsonSymbol = specialSerializeType .member(Symbol.requiredMethod("toJson")) .symbol .asMethod // 创建覆写方法时,指定overrideTarget为traitToJsonSymbol val toJsonSymbol = Symbol.newMethod( classSymbol, // 直接以当前类符号作为方法所属者 "toJson", traitToJsonSymbol.signature, // 复用原方法签名,避免手动定义出错 Flags.Override, traitToJsonSymbol // 明确标记覆写的目标方法 )
通过绑定到trait的方法符号,编译器会自动识别该方法为合法覆写,不会再复制原trait方法。
添加继承判定逻辑
在处理类之前,先判断类是否真正继承了SpecialSerialize,避免对无关类生成方法:
val classType = classSymbol.typeRef if classType <:< TypeRepr.of[SpecialSerialize] then // 执行生成覆写方法的逻辑 else tree
修复后的完整插件阶段代码示例
class ReflectionWorkerPhase extends PluginPhase { import tpd._ val phaseName = "reflectionWorker" // 调整阶段执行顺序 override val runsAfter = Set(typer.name) override val runsBefore = Set(SuperAccessors.name) override def transformTypeDef(tree: TypeDef)(implicit ctx: Context): Tree = tree match { case classDef: ClassDef if !classDef.symbol.isStatic => implicit val fresh = ctx.fresh fresh.setTree(tree) QuotesCache.init(fresh) implicit val quotes: Quotes = QuotesImpl.apply() import quotes.reflect.* val classSymbol = classDef.symbol val classType = classSymbol.typeRef val specialSerializeType = TypeRepr.of[SpecialSerialize] // 仅处理继承了SpecialSerialize的类 if classType <:< specialSerializeType then // 获取trait中的toJson方法符号 val traitToJsonSymbol = specialSerializeType .member(Symbol.requiredMethod("toJson")) .symbol .asMethod // 创建覆写方法的符号,关联到trait方法 val toJsonSymbol = Symbol.newMethod( classSymbol, "toJson", traitToJsonSymbol.signature, Flags.Override, traitToJsonSymbol ) // 生成方法体 val toJsonMethodDef = DefDef( toJsonSymbol, { case List(List(sb: Term, config: Term)) => Some({ quoted.Expr.ofList(List( '{ println("Hello") }, '{ println("World") } )) }.asTerm.changeOwner(toJsonSymbol)) } ) // 将新方法添加到类体中 val updatedClassDef = ClassDef.copy(classDef)( body = toJsonMethodDef +: classDef.body ) updatedClassDef.asInstanceOf[Tree] else classDef case _ => tree } }
内容的提问来源于stack exchange,提问作者Greg
相关产品推荐
相关产品推荐

