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的单例对象作为参数") '{ () }
关键细节说明
- 语法树解析:通过
fooExpr.asTerm将表达式转换为语法树节点,递归处理直接对象名或嵌套对象(如Outer.Inner)的情况,确保能识别所有合法的单例引用。 - 类型校验:利用
objSym.typeInfo <:< fooType判断对象是否符合Foo的类型约束,避免非法输入。 - 反射获取实例:Scala单例对象会生成带
MODULE$字段的类,通过反射加载该类并获取唯一实例。 - 编译期执行:直接调用
objInstance.doStuff()会在宏展开阶段(编译期)执行方法逻辑,最终生成的运行时代码仅为空的Unit表达式。
约束与注意事项
- 传入
runFoo的参数必须是inline表达式,即编译期可解析的单例对象引用(符合你要求的所有Foo实现为object的限制)。 - 若传入非单例对象(如
new Foo { ... })或未实现Foo的对象,宏会直接抛出编译错误。
内容的提问来源于stack exchange,提问作者ncreep
相关产品推荐
相关产品推荐

