scala - 后代类中的宏扩展

标签 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。因此,对于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类中进行额外输入并且不使用运行时反射吗?

最佳答案

子类检测的一般情况

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

当前无法枚举任意类的所有子类,但是我们确实有一个称为ClassSymbol.knownDirectSubclasses的API,该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中我们将弃用非本地c.enclosingTree方法的原因之一:https://github.com/scala/scala/pull/3354

随着宏注释的出现,这个主题将变得越来越重要,因此我们暂时不接受失败。我认为,期望在这一领域取得进展是合理的,但是我们无法为此提供具体的日期。

可能的解决方法

正如我上面提到的,检测子类的任务所面临的“唯一”问题是,相对于执行它的宏扩展来说,这是一个非本地的操作。那么我们如何将其转变为本地业务呢?事实证明这是可能的。

通过在Child类上添加宏注释,可以在宏中查看类的所有成员,然后再进行类型检查,这意味着检查这些成员不会导致潜在的伪循环错误。

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

tl;博士

  • 目前,由于理论和实施方面的原因,宏扩展过程中的非本地操作可能很难或无法稳健地执行。 SI-7046是一个示例,此问题提供了另一个示例。
  • 有时可以通过将更大的作用域包含在宏中来将非本地宏扩展问题重新表述为本地问题。宏天堂插件提供的宏注释可能对此有用。
  • 关于scala - 后代类中的宏扩展,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21160585/

    相关文章:

    scala - 如何使用宏注释将无参数构造函数添加到 Scala 案例类?

    scala - 如何强制重新编译调用宏的类?

    java - 将 Apache Camel 与 Amazon AWS 结合使用

    Scala Play 异常 : Placeholder not replaced

    scala - scala 的 "collect"采用偏函数的 Spark 数据集等价物

    xml - 使用拉式解析器的 Scala 内存泄漏

    scala - 对 multipart/form-data 进行 POST 调用时,加特林测试返回 504

    Scala 宏获取封闭行

    基于 Scala 约束的类型和文字

    scala - 如何使用 Scala 宏转换和应用部分函数?