Skip to content

Commit 1dc0d3a

Browse files
committed
Fix macros producing Expr(:toplevel)
1 parent 9b55a0d commit 1dc0d3a

File tree

2 files changed

+37
-4
lines changed

2 files changed

+37
-4
lines changed

src/macro_expansion.jl

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -206,13 +206,42 @@ function prepare_macro_args(ctx, mctx, raw_args)
206206
return macro_args
207207
end
208208

209+
"""
210+
Insert a hygienic-scope around each arg of K"toplevel" returned from a macro.
211+
212+
It isn't correct for macro expansion to recurse into a K"toplevel" expression
213+
since one child may define a macro and the next may use it. However, not
214+
recursing now means we lose some important context: the module of the macro we
215+
just expanded, which is necessary for resolving the identifiers in the
216+
K"toplevel" AST. The solution implemented in JuliaLang/julia#53515 was to save
217+
our place and expand later using `Expr(:hygienic-scope toplevel_child mod)`.
218+
219+
Of course, these hygienic-scopes are also necessary because existing user code
220+
contains the corresponding escaping, which would otherwise cause errors. We
221+
already consumed the hygienic-scope that comes with every expansion, but won't
222+
be looking for escapes under :toplevel, so push hygienic-scope under toplevel
223+
"""
224+
function fix_toplevel_expansion(ctx, ex::SyntaxTree, mod::Module, lnn::LineNumberNode)
225+
if kind(ex) === K"toplevel"
226+
mapchildren(ctx, ex) do e
227+
@ast ctx ex [K"hygienic_scope" e mod::K"Value" lnn::K"Value"]
228+
end
229+
else
230+
mapchildren(e->fix_toplevel_expansion(ctx, e, mod, lnn), ctx, ex)
231+
end
232+
end
233+
209234
function expand_macro(ctx, ex)
210235
@assert kind(ex) == K"macrocall"
211236

212237
macname = ex[1]
213238
mctx = MacroContext(ctx.graph, ex, current_layer(ctx))
214239
macfunc = eval_macro_name(ctx, mctx, macname)
215240
raw_args = ex[2:end]
241+
macro_loc = let loc = source_location(LineNumberNode, ex)
242+
# Some macros, e.g. @cmd, don't play nicely with file == nothing
243+
isnothing(loc.file) ? LineNumberNode(loc.line, :none) : loc
244+
end
216245
# We use a specific well defined world age for the next checks and macro
217246
# expansion invocations. This avoids inconsistencies if the latest world
218247
# age changes concurrently.
@@ -242,10 +271,6 @@ function expand_macro(ctx, ex)
242271
else
243272
# Compat: attempt to invoke an old-style macro if there's no applicable
244273
# method for new-style macro arguments.
245-
macro_loc = let loc = source_location(LineNumberNode, ex)
246-
# Some macros, e.g. @cmd, don't play nicely with file == nothing
247-
isnothing(loc.file) ? LineNumberNode(loc.line, :none) : loc
248-
end
249274
macro_args = Any[macro_loc, current_layer(ctx).mod]
250275
for arg in raw_args
251276
# For hygiene in old-style macros, we omit any additional scope
@@ -281,6 +306,7 @@ function expand_macro(ctx, ex)
281306
# method was defined (may be different from `parentmodule(macfunc)`)
282307
mod_for_ast = lookup_method_instance(macfunc, macro_args,
283308
ctx.macro_world).def.module
309+
expanded = fix_toplevel_expansion(ctx, expanded, mod_for_ast, macro_loc)
284310
new_layer = ScopeLayer(length(ctx.scope_layers)+1, mod_for_ast,
285311
current_layer_id(ctx), true)
286312
push!(ctx.scope_layers, new_layer)

test/macros.jl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,13 @@ end
353353
@test identity(123) === 123
354354
"""; expr_compat_mode=true)
355355
@test test_result.value === true
356+
357+
# @enum produces Expr(:toplevel)
358+
JuliaLowering.include_string(test_mod, """
359+
@enum SOME_ENUM X1 X2 X3
360+
"""; expr_compat_mode=true)
361+
@test test_mod.SOME_ENUM <: Enum
362+
@test test_mod.X1 isa Enum
356363
end
357364

358365
end

0 commit comments

Comments
 (0)