Scala 宏注释:注释类型的 c.TypeCheck 导致 StackOverflowError [英] Scala Macro Annotations: c.TypeCheck of annotated type causes StackOverflowError

查看:39
本文介绍了Scala 宏注释:注释类型的 c.TypeCheck 导致 StackOverflowError的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a macro annotation which is intended to be applied to class definitions. Its purpose is an almost-but-not-quite serialization tool. It inspects the class's constructor parameters and then creates a factory method on the companion object which in turn supplies values for the parameters. It needs to know the types of the parameters in order to do this, so I've been calling Context.typeCheck on them.

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

The problem occurs when the annotated class's constructor takes a parameter of the same type as itself, or in other similar situations (for instance, if type A and type B both are annotated, and A has a parameter of B, and B has a parameter of A. Type parameters applied to the formal parameters count also). Any of these situations will result in the annotation being recursively invoked until StackOverflowError occurs.

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

I tried using "withMacrosDisabled=true" as an argument to c.typeCheck, and while this solves the problem, it introduces a different one. If the type being checked has not been previously seen, then the compiler remembers its definition, and its macros are never invoked at all. This isn't a problem for the self-reference case, but it does happen in the mutual reference case.

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

So I'm stuck. Is there a workaround? Can I solve this with c.openMacros?

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

Another option, if available, is that I don't strictly need the full definition of the type, I could get by with just its fully qualified name (scala.xml.NodeSeq instead of just NodeSeq). I get the TypeName in the AST, but these are rarely fully qualified, and I don't know how to get the fully qualified name without doing a full typeCheck.

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

As a side question, what is "withMacrosDisabled" good for? If using it prevents all macro expansion forever on the types found in the passed tree, not just for the current c.typeCheck, that seems like too big of a hammer. And you can't really use it even if that's actually what you want, because the macro evaluation would then depend on the order the types are encountered in their own source.

编辑:仔细想想,我认为编译器应该确保每个宏都只展开一次.在循环的情况下,就像在我的例子中一样,至少一个涉及的宏仍然会看到一个未完全处理的类,这在这种情况下似乎是不可避免的,因为它实际上是循环依赖.我想,在生成的 Type 上添加一个标志来指示宏处理不是最终的将是处理它的最佳方法,但这可能无法在 Paradise 中完成.

Edit: Thinking about it, I think the compiler should ensure that each macro is expanded exactly once. In the case of a cycle, as in my example, at least one of the macros involved would still see an incompletely-processed class, which seems unavoidable in a case like this as it is, in effect, a circular dependency. I guess, a flag on the resulting Type to indicate that macro processing is not final would be the best way to deal with it, but that probably can't be done in Paradise.

推荐答案

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

The workaround I ended up using is this:

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}

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

When you terminate the macro expansion, you can't just throw an exception (unless you catch it later), you have to return a valid tree (I just return the original input).

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

The effect of this is that whichever macro-annottee got evaluated first will end up short-circuiting its evaluation when it gets encountered the second time, after the compiler has initiated macro expansions of the whole graph cycle. At this point every annottee in the cycle will have a macro in-flight, all waiting on typechecks of each other. The version of the annottee returned by the short-circuit macro will then be used by these typechecks. (In mine, I just return the original input, but you could in principle do anything that doesn't need to do typechecks). However, the eventual output that the rest of the world sees, after macro expansion is done, is the output of the top-level macro. Caveat: I return entirely un-typechecked trees as the output of my macro - not sure what would happen if you returned a tree that had this inconsistent typechecking done on it. Probably nothing good.

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

In a simple graph with one cycle in it, every macro will see a fully-processed version of every class except the class that originally triggered the cycle. But more complex dependencies could result in macros potentially appearing in various states of expansion or non-expansion when seen by each other.

在我的代码中,这已经足够了,因为我只需要检查类的名称和类型的一致性,而我的宏不会更改这些内容.IOW 我的依赖不是真的循环,编译器只是认为它是.

In my code this is good enough because I only need to check the class's name and type conformance and my macros don't change these things. IOW my dependency isn't really circular, the compiler just thinks it is.

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

note: checkRecursion gets compared against 2 because for some reason the current macro expansion always appears twice in the result of c.openMacros.

这篇关于Scala 宏注释:注释类型的 c.TypeCheck 导致 StackOverflowError的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆