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

Scala 3宏:如何在编译期提取单例对象并调用其方法?

在Scala 3宏中提取Foo单例对象并在编译期调用doStuff

可以实现这个需求,不需要将Foo改为密封trait。核心思路是通过检查宏输入表达式的语法树,识别出指向Foo单例对象的引用,再通过反射在编译期获取该对象实例并调用doStuff()。

可行的宏实现代码

import scala.quoted.*

trait Foo:
  def doStuff(): Unit

inline def runFoo(inline foo: Foo): Unit = ${ runFooImpl('foo) }

def runFooImpl(fooExpr: Expr[Foo])(using Quotes): Expr[Unit] =
  import quotes.reflect.*
  
  // 递归解析表达式,获取单例对象的符号
  def getObjectSymbol(term: Term): Option[Symbol] = term match
    case Ident(sym) if sym.isObject => Some(sym)
    case Select(qualifier, _) => getObjectSymbol(qualifier)
    case _ => None

  getObjectSymbol(fooExpr.asTerm) match
    case Some(objSym) =>
      // 验证对象是否实现了Foo
      val fooType = TypeRepr.of[Foo]
      if objSym.typeInfo <:< fooType then
        // 通过反射获取单例对象实例
        val objInstance = Class
          .forName(objSym.fullName)
          .getDeclaredField("MODULE$")
          .get(null)
          .asInstanceOf[Foo]
        
        // 在编译期执行doStuff()
        objInstance.doStuff()
        '{ () }
      else
        report.error(s"${objSym.fullName} 未实现Foo trait")
        '{ () }
    case None =>
      report.error("仅允许传入实现Foo的单例对象作为参数")
      '{ () }

关键细节说明

  1. 语法树解析:通过fooExpr.asTerm将表达式转换为语法树节点,递归处理直接对象名或嵌套对象(如Outer.Inner)的情况,确保能识别所有合法的单例引用。
  2. 类型校验:利用objSym.typeInfo <:< fooType判断对象是否符合Foo的类型约束,避免非法输入。
  3. 反射获取实例:Scala单例对象会生成带MODULE$字段的类,通过反射加载该类并获取唯一实例。
  4. 编译期执行:直接调用objInstance.doStuff()会在宏展开阶段(编译期)执行方法逻辑,最终生成的运行时代码仅为空的Unit表达式。

约束与注意事项

  • 传入runFoo的参数必须是inline表达式,即编译期可解析的单例对象引用(符合你要求的所有Foo实现为object的限制)。
  • 若传入非单例对象(如new Foo { ... })或未实现Foo的对象,宏会直接抛出编译错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 16:43:31