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 constru
This seems to be quite related to the discussion started in Can't access Parent's Members while dealing with Macro Annotations (also see a link to a more lengthy elaboration in my answer over there).
If possible I would like to avoid situations when macros see half-expanded or half-populated types in order to reduce the potential for confusion. Last months I've been thinking about ways to avoid that, but there's been a number of higher-priority distractions, so I haven't gotten far yet.
Two potential ideas that I'm pondering are: 1) come up with a notation to specify effects of macro annotations, so that we don't have to expand macros in order to know what classes and members comprise our program (if that works out, then the macro engine can first precompute the list of members and only then launch macro expansions), 2) figure out a mechanism of specifying how macros depend on program elements, so that expansions are ordered correctly. Just yesterday I've also learned about Backstage Java and David Herman's work on typed hygienic macros - this should also be relevant. What do you think about these directions of thought?
In the meanwhile, while I'm trying to figure out a principled solution to the problem of dependencies, I'm also interested in unblocking your use case by providing a workaround or a patch to paradise that would be immediately useful. Could you elaborate on your project, so that we could come up with a fix?
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).
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.
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.
note: checkRecursion gets compared against 2 because for some reason the current macro expansion always appears twice in the result of c.openMacros.