Scala 子类中的宏扩展

Scala 子类中的宏扩展,scala,scala-macros,Scala,Scala Macros,我有下一个班级结构: trait SomeType trait Root { val allMySomeTypes: Seq[SomeType] } class Child extends Root { object MyType1 extends SomeType {...} object MyType2 extends SomeType {...} } 我想将val allMySomeTypes初始化为扩展在具体类中定义的SomeType的所有对象的Seq。因此,对于子实例

我有下一个班级结构:

trait SomeType

trait Root {
  val allMySomeTypes: Seq[SomeType]
}

class Child extends Root {
  object MyType1 extends SomeType {...}
  object MyType2 extends SomeType {...}
}
我想将val allMySomeTypes初始化为扩展在具体类中定义的SomeType的所有对象的Seq。因此,对于子实例,它将是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的宏扩展,我在子类中有空序列。
我可以在不在子类中额外键入和不使用运行时反射的情况下构建实际成员的Seq吗?

子类检测的一般情况

有趣的是,就在几天前,我们讨论了在一般情况下列出给定类的所有子类的可能性。以下是我当时发布的答案:

目前无法枚举任意类的所有子类,但我们有一个名为
ClassSymbol.knownDirectSubclass
的API,它枚举密封的子类

但不幸的是,即使这样也不容易。knownDirectSubclasses API在某种程度上是很好的,因为人们正在以某种方式使用它,但它也有一个严重的缺陷,我们目前不知道如何修复:

子类检测的特殊情况

然而,这个问题问的是一些更具体的问题。我们把自己局限于某个类的成员,这样我们就可以选择我们感兴趣的类的子类了吗

事实证明,尽管目前有一个API来处理这个问题(
c.enclosuringclass
,属于
c.enclosuringtree
方法家族),但即使是这种特殊情况也无法得到可靠的处理

问题是Scala宏是在类型检查期间展开的,这意味着在展开给定宏时,它的封闭树正在进行类型检查。“正在进行类型检查”状态意味着一些封闭的树可能暂时处于不一致的状态,如果试图检查它们,这些树将崩溃。在上有一个更详细的讨论,但这里我只举一个简单的例子

例如,如果宏在具有未指定返回类型的方法内展开(例如
def foo=yourmacrocal(1,2,3)
),则调用
c.enclosuringdef.symbol.typeSignature
将导致宏展开失败,因为要知道该方法的签名,需要推断其返回类型,但是要推断返回类型,您需要扩展宏,对于宏,您需要知道方法的签名等。顺便说一句,编译器足够聪明,不会在这里无限循环-它会提前中断循环并显示循环引用错误

这意味着要很好地处理非局部宏扩展,我们需要对类型签名及其依赖项进行某种声明性抽象,而不仅仅是命令式
typeSignature
方法。在过去的几个月里,我一直在断断续续地思考这个问题,但到目前为止还没有找到任何令人满意的方法,这就是为什么在Scala 2.11中我们要反对非本地
c.enclosingTree
方法的原因之一

随着宏注释的出现,这个主题将变得更加重要,所以我们还没有承认失败。我认为,期望在这方面取得进展是合理的,但我们没有具体的日期可以提供这方面的进展

可能的解决方法

正如我前面提到的,检测子类任务的“唯一”问题是,相对于执行它的宏扩展来说,它是一个非局部的操作。那么我们把它变成一个本地操作怎么样?事实证明这是可能的

通过在
子类
上放置宏注释,在宏中,您将能够在类型检查之前看到类的所有成员,这意味着检查这些成员不会导致潜在的伪循环错误

然而,如果这些成员没有进行打字检查,那么他们对我们有什么好处?我们需要类型来执行子类检查,对吗?嗯,是和否。我们确实需要类型,但我们不必从成员自己那里获得这些类型。我们可以获取带注释的类,复制它,进行类型检查,然后检查类型检查的结果,而不必担心会破坏注释对象。下面的示例将为实施此策略提供灵感:

tl;dr

  • 目前,由于理论和实施方面的原因,宏观扩张期间的非本地操作可能很难或不可能稳健执行。这是一个例子,这个问题提供了另一个例子
  • 有时,通过在宏中包含更大的范围,可以将非局部宏扩展问题重新表述为局部问题。宏天堂插件提供的宏注释可能对此有所帮助

  • 谢谢你如此完整和解释的答案!我将更详细地了解宏注释。
    trait Root {
      val allMySomeTypes: Seq[SomeType] = macro getMembersOfCurrentByType_impl[SomeType]
    }