-
Notifications
You must be signed in to change notification settings - Fork 9
Description
In trying to work out what to do in #109, I've realized that "deferred hygiene" is fairly complicated to implement correctly.
Deferred hygiene is what we get when we can't immediately lower some AST, but when that AST may contain scope_layer (or escape). For example, if a macro emits a double quoted AST that evaluates to some quoted code; if that quoted code contains scope_layer, what is the correct behavior?
There's at least three cases where we might produce expressions from macro expansion which can't be further expanded immediately (and thus we need to defer the processing of any embedded scope_layer or escape):
- For
K"inert": How should colliding names work when a macro generates quoted code containing multiple scope layers due to mixing macro arguments into double quoted code. Presumably such names should have automatic hygiene applied. - For
K"module": What do we do when a macro generates a module containing symbols from multiple scope layers? These symbols should probably have automatic hygiene and not collide. Colliding global names in the new module are particularly troubling because the only choice in the current runtime is to resort to name mangling. - For
K"toplevel": We should retain the scope layers but in general we need to defer the macro expansion and scope resolution pass to respect evaluation order in top level code.
It's worth noting that K"inert" is a rather different case from module and toplevel expressions: For module and toplevel, those expressions will be evaluated within the dynamic scope of the containing top level thunk - thus "relatively soon" - and it's only the lowering step which must be deferred for a short time. See #85 for a solution to this in cases where the module/toplevel expression is nested only in other module or toplevel expressions. For inert ASTs, these will often be stored in the lowered IR and there's no expectation that they will be further processed at any particular point in time. (These differences are more about "typical use case" than a big technical difference in implementation.)
Here's an example test case for inert quoted code:
macro make_quoted_code(init, y)
q = :(let
x = "inner x"
$init
($y, x)
end)
@ast q q [K"inert" q]
end
@make_quoted_code x="outer x" xAnd associated old-style macro (which at least throws an error if its return value is evaluated):
macro make_quoted_code(init, y)
QuoteNode(:(let
x = "inner x"
$(esc(init))
($(esc(y)), x)
end))
endDeferred hygiene is basically what #81 partially addressed for the specific case of top level statements produced by old-style macros, but we need a fully general solution.