Scala宏注释:带注释类型的c.TypeCheck会导致StackOverflowError

时间:2013-11-13 13:13:51

标签: scala scala-macros

我有一个宏注释,旨在应用于类定义。它的目的是一个几乎但不是很完整的序列化工具。它检查类的构造函数参数,然后在随播对象上创建一个工厂方法,该方法又为参数提供值。为了做到这一点,它需要知道参数的类型,所以我一直在调用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,这似乎是一个太大的锤子。即使这实际上是你想要的,你也不能真正使用它,因为宏评估将取决于在他们自己的源中遇到类型的顺序。

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

2 个答案:

答案 0 :(得分:4)

这似乎与Can't access Parent's Members while dealing with Macro Annotations中开始的讨论有很大关系(另请参阅我在那里的答案中更详细阐述的链接)。

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

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

与此同时,虽然我正在尝试找出依赖问题的原则解决方案,但我也有兴趣通过提供一个解决方案或天堂的补丁来解锁您的用例,这将立即有用。您能否详细说明您的项目,以便我们能够提出修复方案?

答案 1 :(得分:3)

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

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}

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

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

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

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

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