5

我有一个宏注释,旨在应用于类定义。它的目的是一个几乎但不完全的序列化工具。它检查类的构造函数参数,然后在伴随对象上创建一个工厂方法,该方法反过来为参数提供值。它需要知道参数的类型才能做到这一点,所以我一直在对它们调用 Context.typeCheck。

当被注解的类的构造函数接受了与自己相同类型的参数时,或者在其他类似的情况下(例如,如果类型 A 和类型 B 都被注解,并且 A 有一个参数 B,而 B 有一个参数A. 应用于形式参数的类型参数也算)。任何这些情况都会导致注解被递归调用,直到 StackOverflowError 发生。

我尝试使用“withMacrosDisabled=true”作为 c.typeCheck 的参数,虽然这解决了问题,但它引入了一个不同的问题。如果被检查的类型以前没有见过,那么编译器会记住它的定义,并且它的宏根本不会被调用。这对于自引用案例来说不是问题,但在相互引用案例中确实会发生。

所以我被困住了。有解决方法吗?我可以用 c.openMacros 解决这个问题吗?

另一个选项(如果可用)是我并不严格需要类型的完整定义,我可以只使用它的完全限定名称(scala.xml.NodeSeq 而不仅仅是 NodeSeq)。我在 AST 中获得了 TypeName,但是这些很少是完全限定的,而且我不知道如何在不进行完整的 typeCheck 的情况下获得完全限定的名称。

作为一个附带问题,“withMacrosDisabled”有什么用?如果使用它可以永久阻止在传递的树中找到的类型的所有宏扩展,而不仅仅是当前的 c.typeCheck,这似乎是一个太大的锤子。即使这实际上是你想要的,你也不能真正使用它,因为宏评估将取决于类型在它们自己的源中遇到的顺序。

编辑:考虑一下,我认为编译器应该确保每个宏都只扩展一次。在循环的情况下,如在我的示例中,所涉及的至少一个宏仍然会看到一个未完全处理的类,这在这种情况下似乎是不可避免的,因为它实际上是一个循环依赖。我想,结果类型上的标志表明宏处理不是最终的,这将是处理它的最佳方法,但这可能无法在天堂中完成。

4

2 回答 2

4

这似乎与Can't access Parent's Members while processing Macro Annotations中开始的讨论非常相关(另请参阅我的答案中更冗长的阐述的链接)。

如果可能的话,我想避免宏看到半扩展或半填充类型的情况,以减少混淆的可能性。上个月我一直在考虑避免这种情况的方法,但是有一些更高优先级的干扰,所以我还没有走多远。

我正在考虑的两个潜在想法是:1)提出一个符号来指定宏注释的效果,这样我们就不必扩展宏来了解哪些类和成员组成了我们的程序(如果可行的话,然后宏引擎可以首先预先计算成员列表,然后才启动宏扩展),2)找出指定宏如何依赖于程序元素的机制,以便扩展正确排序。就在昨天,我还了解了 Backstage Java 和 David Herman 在类型化卫生宏方面的工作——这也应该是相关的。您如何看待这些思想方向?

同时,虽然我试图找出解决依赖问题的原则性解决方案,但我也有兴趣通过提供一种解决方法或立即有用的天堂补丁来解锁您的用例。您能否详细说明您的项目,以便我们提出解决方案?

于 2013-11-14T19:32:47.223 回答
4

我最终使用的解决方法是:

val open = c.openMacros
val checkRecursion = open.count({check=>(c.macroApplication.toString == check.macroApplication.toString) && (c.enclosingPosition.toString == check.enclosingPosition.toString)})
if (checkRecursion > 2) // see note
  {do something to terminate macro expansion}

当您终止宏扩展时,您不能只抛出异常(除非您稍后捕获它),您必须返回一个有效的树(我只是返回原始输入)。

这样做的效果是,在编译器启动整个图形周期的宏扩展之后,无论哪个宏被注释者首先被评估,当它第二次遇到时,它的评估最终会短路。此时,循环中的每个注释者都会有一个正在运行的宏,它们都在等待彼此的类型检查。然后,这些类型检查将使用短路宏返回的注释版本。(在我的情况下,我只返回原始输入,但原则上你可以做任何不需要做类型检查的事情)。但是,世界其他地方看到的最终输出,在宏扩展完成后,是顶级宏的输出。警告:我返回完全未经类型检查的树作为我的宏的输出 - 不确定如果你返回的树对它进行了这种不一致的类型检查会发生什么。大概没什么好说的。

在一个包含一个循环的简单图中,每个宏都将看到每个类的完整处理版本,但最初触发循环的类除外。但是更复杂的依赖关系可能导致宏在彼此看到时可能出现在各种扩展或非扩展状态。

在我的代码中,这已经足够好了,因为我只需要检查类的名称和类型一致性,并且我的宏不会改变这些东西。IOW 我的依赖并不是真正的循环,编译器只是认为它是循环的。

注意:checkRecursion 与 2 进行比较,因为由于某种原因,当前的宏扩展总是在 c.openMacros 的结果中出现两次。

于 2013-12-09T08:40:32.517 回答