我正在尝试创建一个使用一些
object
的宏。
假设我有以下定义:
trait Foo:
def doStuff(): Unit
// in other files
object Bar extends Foo:
def doStuff() = ...
object Qux extends Foo:
def doStuff() = ...
(
Foo
不是故意密封的,见下文)
我想创建一个具有以下形状的宏:
inline def runFoo(inline foo: Foo): Unit = ${ runFooImpl('foo) }
这样,当使用
runFoo
的任何单例实例调用 Foo
时,相应的 doStuff
方法将在编译时被调用。
例如:
runFoo(Bar)
将在
编译时触发
Bar.doStuff()
。
在伪代码中,宏看起来像这样:
def runFooImpl(fooExpr: Expr[Foo]): Expr[Unit] =
val foo: Foo = fooExpr.valueOrAbort // does not compile
foo.doStuff()
'{ () }
目前
valueOrAbort
无法工作,因为缺少FromExpr
。
我的问题是,是否有某种方法可以利用
Bar
、Qux
等编译时常量这一事实,以便能够在宏扩展期间从具体的 Expr[Foo]
中提取它们?
请注意,将
Foo
转换为密封特征(并通过模式匹配写入 FromExpr
)不是可接受的解决方案,因为我希望 Foo
可以由客户端代码扩展(限制所有 Foo
实现必须是 object
s)。
提前致谢
我以前也这么做过。这种方法需要几个条件:
您应该确保用户实际上会通过
object
- 您可以使用例如inline def runFoo[A <: Foo & Singleton]: Unit
你不能阻止用户做类似的事情
class Example1 {
def localFunction: Unit = {
object LocalDefinition extends Foo
runFoo[LocalDefinition]
}
}
class Example2 {
object LocalDefinition extends Foo
def localFunction: Unit = {
runFoo[LocalDefinition]
}
}
并且这不能实现为
object
- 即使它们是单例 - 包含对封闭类的引用。因此,这仍然需要在宏内部进行检查,并显示编译错误,并显示消息解释原因
一般来说 - 如果您想访问
object A extends Foo
,则宏可用的类路径上必须有Class[A$]
可用
一旦我们接受这些限制,我们可能会一起破解一些东西。几年前我已经制作了类似的原型,并使用我的OSS库中的结果让用户自定义字符串比较的工作方式。
首先起草宏的入口点:
object Macro:
inline def runStuff[A <: Foo & Singleton](inline a: A): Unit =
${ runStuffImpl[A] }
import scala.quoted.*
def runStuffImpl[A <: Foo & Singleton: Type](using Quotes): Expr[Unit] =
???
然后,我们可以实现一段代码,将
Symbol
名称转换为 Class
名称。我将使用不处理嵌套对象的简化版本:
def summonModule[M <: Singleton: Type](using Quotes): Option[M] =
val name: String = TypeRepr.of[M].typeSymbol.companionModule.fullName
val fixedName = name.replace(raw"$$.", raw"$$") + "$"
try
Option(Class.forName(fixedName).getField("MODULE$").get(null).asInstanceOf[M])
catch
case _: Throwable => None
我们实际上可以在宏中使用该实现(如果可用):
def runStuffImpl[A <: Foo & Singleton: Type](using Quotes): Expr[Unit] = {
import quotes.*
summonModule[A] match
case Some(foo) =>
foo.doStuff()
'{ () }
case None =>
reflect.report.throwError(s"${Type.show[A]} cannot be used in macros")
}
完成。
也就是说,这个宏类型类模式(我称之为宏类型类模式)非常脆弱,容易出错并且对用户来说不直观,所以我建议如果可能的话不要使用它,并且要非常清楚地解释什么样的对象可以去在那里,在文档和错误消息中。
如果你无法通过阅读代码来判断它为什么有效,我也建议不要使用它。