我有下一个班级结构:
trait SomeType
trait Root {
val allMySomeTypes: Seq[SomeType]
}
class Child extends Root {
object MyType1 extends SomeType {...}
object MyType2 extends SomeType {...}
}
我想将val allMySomeTypes初始化为扩展在具体类中定义的SomeType的所有对象的Seq。所以对于Child实例,它将是val allMySomeTypes:Seq [SomeType] = Seq(MyType1,MyType2)。
我写了一个用于查找具有某种基类型的对象的宏:
def getMembersOfCurrentByType_impl[S](c: Context)(implicit ev: c.WeakTypeTag[S]) = {
import c.universe._
val seqApply = Select(reify(Seq).tree, newTermName("apply"))
val objs = c.enclosingClass.symbol.typeSignature.declarations.collect {
case o: ModuleSymbol if o.typeSignature <:< ev.tpe => Ident(o) //Select(c.prefix.tree, o)
}.toList
c.Expr[Seq[S]] {Apply(seqApply, objs)}
}
并将其绑定到
trait Root {
val allMySomeTypes: Seq[SomeType] = macro getMembersOfCurrentByType_impl[SomeType]
}
但是,显然,由于基本特征的宏扩展,我在Child类中有空序列。 我可以在没有在Child类中额外输入而不使用运行时反射的情况下构建实际成员的Seq吗?
答案 0 :(得分:5)
子类检测的一般情况
有趣的是,我们在几天前就已经a similar discussion at scala-user了,在那里我们讨论了在一般情况下列出给定类的所有子类的可能性。这是我当时发布的答案:
目前无法枚举任意类的所有子类,但我们确实有一个名为ClassSymbol.knownDirectSubclasses
的API,它枚举了密封的后代。
但不幸的是,即便这样也不容易。 knownDirectSubclasses API在人们以某种方式使用它的意义上或多或少都很好,但它也有一个严重的缺陷,我们目前不知道如何修复:https://groups.google.com/forum/#!topic/scala-internals/LRfKffsPxVA。
子类检测的特殊情况
然而,这个StackOverflow问题会询问更具体的内容。我们如何将自己局限于某个类的成员,然后可以选择那些我们感兴趣的类的子类吗?
嗯,事实证明,尽管目前有一个API来处理(c.enclosingClass
方法家族的c.enclosingTree
),但即使是这个特殊情况仍然无法有力地处理
问题是Scala宏在类型检查期间被扩展,这意味着在扩展给定宏的时候,其封闭的树正在进行类型检查。 “被类型检查”状态意味着一些封闭的树可能暂时处于不一致的状态,如果试图检查它们就会崩溃。在Scala Macro Annotations: c.TypeCheck of annotated type causes StackOverflowError进行了更精细的讨论,但在这里我只举一个简单的例子。
例如,如果宏在具有未指定返回类型的方法(例如def foo = yourMacro(1, 2, 3)
)内展开,则调用c.enclosingDef.symbol.typeSignature
将导致宏扩展失败,因为,知道签名对于该方法,您需要推断其返回类型,但要推断您需要扩展宏的返回类型,您需要知道该方法的签名等。顺便说一句,编译器足够智能有序不要在这里无限循环 - 它会提前打破循环并显示循环参考错误。
这意味着要很好地处理非本地宏扩展,我们需要对类型签名及其依赖关系进行某种声明性抽象,而不仅仅是命令式typeSignature
方法。在过去的几个月里,我一直在思考这个问题,但到目前为止还没有提出任何令人满意的结果,这也是为什么在Scala 2.11中我们要弃用非本地{{{ 1}}方法:https://github.com/scala/scala/pull/3354。
随着宏观注释的出现,这个主题将变得更加重要,所以我们还没有承认失败。我认为,期待这一领域取得进展是合理的,但我们没有具体的日期可以提供给这一领域。
可能的解决方法
正如我上面提到的,我们在检测子类的任务中遇到的“唯一”问题是,它是一个与执行它的宏扩展相关的非本地操作。那么我们如何将其转变为本地运营呢?事实证明这是可能的。
通过在c.enclosingTree
类上放置一个宏注释,在宏中你可以在它们被类型检查之前看到该类的所有成员,这意味着检查这些成员不会导致潜在的假循环错误。
然而,如果这些成员没有受到严格监控,那么他们对我们有什么好处呢?我们需要类型来执行子类检查,对吧?嗯,是的,不。我们确实需要类型,但我们不必从成员本身获取这些类型。我们可以使用带注释的类,复制它,对其进行检查,然后检查类型检查的结果,而不必担心会破坏被注意者。这是一个为实施此策略提供灵感的示例:Can't access Parent's Members while dealing with Macro Annotations。
<强> TL;博士强>