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

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)
}

原有实现的错误点

  1. 对已经完成类型检查的ClassDef重新调用localTyper.typed,导致类符号重复注册,原有符号失效,触发NoSymbol断言错误。
  2. 错误调用pl.symbol.owner.info.decls.unlink(pl.symbol)移除了原有类的符号注册,导致类的所有原有声明丢失,编译器认为类未实现抽象方法要求声明为abstract。
  3. 插入的新方法节点没有绑定对应的符号和类型属性,类型检查器无法识别节点类型信息。

内容的提问来源于stack exchange,提问作者sinanspd

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 04:45:03