【问题标题】:Macro expansion in descendant classes后代类中的宏扩展
【发布时间】:2014-01-17 19:59:40
【问题描述】:

我有下一个班级结构:

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 类中有 Empty 序列。 我可以构建实际成员的 Seq,而无需在 Child 类中额外键入且不使用运行时反射吗?

【问题讨论】:

    标签: scala scala-macros


    【解决方案1】:

    子类检测的一般情况

    有趣的是,就在几天前,我们有a similar discussion at scala-user,在那里我们讨论了在一般情况下列出给定类的所有子类的可能性。这是我当时发布的答案:

    目前无法枚举任意类的所有子类,但我们确实有一个名为ClassSymbol.knownDirectSubclasses 的 API,它可以枚举密封的后代。

    但不幸的是,即使这样也不容易。 knownDirectSubclasses API 在人们以某种​​方式使用它的意义上或多或少是好的,但它也有一个我们目前不知道如何修复的严重缺陷:https://groups.google.com/forum/#!topic/scala-internals/LRfKffsPxVA

    子类检测的特殊情况

    但是,这个 StackOverflow 问题询问的是更具体的问题。我们将自己限制在某个类的成员中怎么样,然后可以选择那些是我们感兴趣的类的子类吗?

    好吧,事实证明,尽管目前有一个 API 可以处理这个问题(c.enclosingTree 方法家族的c.enclosingClass),但即使是这种特殊情况仍然无法稳健地处理。

    问题是 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 中我们将弃用非本地 @987654333 的原因之一@方法:https://github.com/scala/scala/pull/3354.

    随着宏注释的出现,这个主题将变得更加重要,所以我们还没有认输。我认为,期待这一领域取得进展是合理的,但我们无法为此提供具体的日期。

    一种可能的解决方法

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

    通过在Child 类上添加宏注解,在您的宏中,您将能够在对类的所有成员进行类型检查之前查看它们,这意味着检查这些成员不会导致潜在的虚假循环错误。

    但是,如果这些成员没有经过类型检查,那么它们对我们有什么好处?我们需要类型来执行子类检查,对吧?嗯,是的,也不是。我们确实需要类型,但我们不必从成员自己那里获取这些类型。我们可以获取带注释的类,复制它,对其进行类型检查,然后检查类型检查的结果,而不必担心破坏被注释者。以下示例将为实施此策略提供灵感:Can't access Parent's Members while dealing with Macro Annotations

    tl;dr

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

    【讨论】:

    • 感谢您提供如此完整和解释性的答案!我将仔细研究宏注释。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多