Skip to content

Commit 8a0e033

Browse files
authored
[JuliaLowering] get macro name in ctx.world; fix lowering iterator (#60168)
Evaluating a macro's name should be done in the same world we pass into lowering and use for expansion. Fix this for all reasonable macro names. The lowering iterator (currently very cold code only used for module/toplevel expressions that JuliaLowering "controls") also needs to update the expansion world between steps.
1 parent 6110f3c commit 8a0e033

File tree

4 files changed

+92
-91
lines changed

4 files changed

+92
-91
lines changed

JuliaLowering/src/eval.jl

Lines changed: 28 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -33,50 +33,42 @@ end
3333
# how we end up putting this into Base.
3434

3535
struct LoweringIterator{GraphType}
36-
ctx::MacroExpansionContext{GraphType}
36+
expr_compat_mode::Bool # later stored in module?
3737
todo::Vector{Tuple{SyntaxTree{GraphType}, Bool, Int}}
3838
end
3939

40-
function lower_init(ex::SyntaxTree, mod::Module, macro_world::UInt; expr_compat_mode::Bool=false)
41-
graph = ensure_macro_attributes(syntax_graph(ex))
42-
ctx = MacroExpansionContext(graph, mod, expr_compat_mode, macro_world)
43-
ex = reparent(ctx, ex)
44-
LoweringIterator{typeof(graph)}(ctx, [(ex, false, 0)])
40+
function lower_init(ex::SyntaxTree{T};
41+
expr_compat_mode::Bool=false) where {T}
42+
LoweringIterator{T}(expr_compat_mode, [(ex, false, 0)])
4543
end
4644

47-
function lower_step(iter, push_mod=nothing)
48-
if !isnothing(push_mod)
49-
push_layer!(iter.ctx, push_mod, false)
50-
end
51-
45+
function lower_step(iter, mod, world=Base.get_world_counter())
5246
if isempty(iter.todo)
5347
return Core.svec(:done)
5448
end
5549

56-
ex, is_module_body, child_idx = pop!(iter.todo)
50+
top_ex, is_module_body, child_idx = pop!(iter.todo)
5751
if child_idx > 0
58-
next_child = child_idx + 1
59-
if child_idx <= numchildren(ex)
60-
push!(iter.todo, (ex, is_module_body, next_child))
61-
ex = ex[child_idx]
52+
if child_idx <= numchildren(top_ex)
53+
push!(iter.todo, (top_ex, is_module_body, child_idx + 1))
54+
ex = top_ex[child_idx]
55+
elseif is_module_body
56+
return Core.svec(:end_module)
6257
else
63-
if is_module_body
64-
pop_layer!(iter.ctx)
65-
return Core.svec(:end_module)
66-
else
67-
return lower_step(iter)
68-
end
58+
return lower_step(iter, mod)
6959
end
60+
else
61+
ex = top_ex
7062
end
7163

7264
k = kind(ex)
7365
if !(k in KSet"toplevel module")
74-
ex = expand_forms_1(iter.ctx, ex)
66+
ctx1, ex = expand_forms_1(mod, ex, iter.expr_compat_mode, world)
7567
k = kind(ex)
7668
end
7769
if k == K"toplevel"
7870
push!(iter.todo, (ex, false, 1))
79-
return lower_step(iter)
71+
return lower_step(iter, mod)
8072
elseif k == K"module"
8173
name = ex[1]
8274
if kind(name) != K"Identifier"
@@ -93,7 +85,7 @@ function lower_step(iter, push_mod=nothing)
9385
return Core.svec(:begin_module, newmod_name, std_defs, loc)
9486
else
9587
# Non macro expansion parts of lowering
96-
ctx2, ex2 = expand_forms_2(iter.ctx, ex)
88+
ctx2, ex2 = expand_forms_2(ctx1, ex)
9789
ctx3, ex3 = resolve_scopes(ctx2, ex2)
9890
ctx4, ex4 = convert_closures(ctx3, ex3)
9991
ctx5, ex5 = linearize_ir(ctx4, ex4)
@@ -457,7 +449,7 @@ end
457449
@fzone "JL: eval" function eval(mod::Module, ex::SyntaxTree;
458450
macro_world::UInt=Base.get_world_counter(),
459451
opts...)
460-
iter = lower_init(ex, mod, macro_world; opts...)
452+
iter = lower_init(ex; opts...)
461453
_eval(mod, iter)
462454
end
463455

@@ -466,69 +458,32 @@ function eval(mod::Module, ex; opts...)
466458
eval(mod, expr_to_syntaxtree(ex); opts...)
467459
end
468460

469-
if VERSION >= v"1.13.0-DEV.1199" # https://github.com/JuliaLang/julia/pull/59604
470-
471461
function _eval(mod, iter)
472-
modules = Module[]
473-
new_mod = nothing
462+
modules = Module[mod]
474463
result = nothing
475464
while true
476-
thunk = lower_step(iter, new_mod)::Core.SimpleVector
477-
new_mod = nothing
465+
thunk = lower_step(iter, modules[end])::Core.SimpleVector
478466
type = thunk[1]::Symbol
479467
if type == :done
480468
break
481469
elseif type == :begin_module
482-
push!(modules, mod)
483470
filename = something(thunk[4].file, :none)
484-
mod = @ccall jl_begin_new_module(mod::Any, thunk[2]::Symbol, thunk[3]::Cint,
485-
filename::Cstring, thunk[4].line::Cint)::Module
486-
new_mod = mod
487-
elseif type == :end_module
488-
@ccall jl_end_new_module(mod::Module)::Cvoid
489-
result = mod
490-
mod = pop!(modules)
491-
else
492-
@assert type == :thunk
493-
result = Core.eval(mod, thunk[2])
494-
end
495-
end
496-
@assert isempty(modules)
497-
return result
498-
end
499-
500-
else
501-
502-
function _eval(mod, iter, new_mod=nothing)
503-
in_new_mod = !isnothing(new_mod)
504-
result = nothing
505-
while true
506-
thunk = lower_step(iter, new_mod)::Core.SimpleVector
507-
new_mod = nothing
508-
type = thunk[1]::Symbol
509-
if type == :done
510-
@assert !in_new_mod
511-
break
512-
elseif type == :begin_module
513-
name = thunk[2]::Symbol
514-
std_defs = thunk[3]
515-
result = Core.eval(mod,
516-
Expr(:module, std_defs, name,
517-
Expr(:block, thunk[4], Expr(:call, m->_eval(m, iter, m), name)))
518-
)
471+
mod = @ccall jl_begin_new_module(
472+
modules[end]::Any, thunk[2]::Symbol, thunk[3]::Cint,
473+
filename::Cstring, thunk[4].line::Cint)::Module
474+
push!(modules, mod)
519475
elseif type == :end_module
520-
@assert in_new_mod
521-
return mod
476+
@ccall jl_end_new_module(modules[end]::Module)::Cvoid
477+
result = pop!(modules)
522478
else
523479
@assert type == :thunk
524-
result = Core.eval(mod, thunk[2])
480+
result = Core.eval(modules[end], thunk[2])
525481
end
526482
end
483+
@assert length(modules) === 1
527484
return result
528485
end
529486

530-
end
531-
532487
"""
533488
include(mod::Module, path::AbstractString)
534489

JuliaLowering/src/macro_expansion.jl

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -158,25 +158,49 @@ function fixup_macro_name(ctx::MacroExpansionContext, ex::SyntaxTree)
158158
end
159159
end
160160

161-
function eval_macro_name(ctx::MacroExpansionContext, mctx::MacroContext, ex::SyntaxTree)
162-
# `ex1` might contain a nontrivial mix of scope layers so we can't just
163-
# `eval()` it, as it's already been partially lowered by this point.
164-
# Instead, we repeat the latter parts of `lower()` here.
165-
ex1 = expand_forms_1(ctx, fixup_macro_name(ctx, ex))
166-
ctx2, ex2 = expand_forms_2(ctx, ex1)
167-
ctx3, ex3 = resolve_scopes(ctx2, ex2)
168-
ctx4, ex4 = convert_closures(ctx3, ex3)
169-
ctx5, ex5 = linearize_ir(ctx4, ex4)
161+
function _eval_dot(world::UInt, mod, ex::SyntaxTree)
162+
if kind(ex) === K"."
163+
mod = _eval_dot(world, mod, ex[1])
164+
ex = ex[2]
165+
end
166+
kind(ex) in KSet"Identifier Symbol" && mod isa Module ?
167+
Base.invoke_in_world(world, getproperty, mod, Symbol(ex.name_val)) :
168+
nothing
169+
end
170+
171+
# If macroexpand(ex[1]) is an identifier or dot-expression, we can simply grab
172+
# it from the scope layer's module in ctx.macro_world. Otherwise, we need to
173+
# eval arbitrary code (which, TODO: does not use the correct world age, and it
174+
# isn't clear the language is meant to support this).
175+
function eval_macro_name(ctx::MacroExpansionContext, mctx::MacroContext, ex0::SyntaxTree)
170176
mod = current_layer(ctx).mod
171-
expr_form = to_lowered_expr(ex5)
177+
ex = fixup_macro_name(ctx, expand_forms_1(ctx, ex0))
172178
try
173-
# Using Core.eval here fails when precompiling packages since we hit the
174-
# user-facing error (in `jl_check_top_level_effect`) that warns that
175-
# effects won't persist when eval-ing into a closed module.
176-
# `jl_invoke_julia_macro` bypasses this by calling `jl_toplevel_eval` on
177-
# the macro name. This is fine assuming the first argument to the
178-
# macrocall is effect-free.
179-
ccall(:jl_toplevel_eval, Any, (Any, Any), mod, expr_form)
179+
if kind(ex) === K"Value"
180+
!(ex.value isa GlobalRef) ? ex.value :
181+
Base.invoke_in_world(ctx.macro_world, getglobal,
182+
ex.value.mod, ex.value.name)
183+
elseif kind(ex) === K"Identifier"
184+
layer = get(ex, :scope_layer, nothing)
185+
if !isnothing(layer)
186+
mod = ctx.scope_layers[layer].mod
187+
end
188+
Base.invoke_in_world(ctx.macro_world, getproperty,
189+
mod, Symbol(ex.name_val))
190+
elseif kind(ex) === K"." &&
191+
(ed = _eval_dot(ctx.macro_world, mod, ex); !isnothing(ed))
192+
ed
193+
else
194+
# `ex` might contain a nontrivial mix of scope layers so we can't
195+
# just `eval()` it, as it's already been partially lowered by this
196+
# point. Instead, we repeat the latter parts of `lower()` here.
197+
ctx2, ex2 = expand_forms_2(ctx, ex)
198+
ctx3, ex3 = resolve_scopes(ctx2, ex2)
199+
ctx4, ex4 = convert_closures(ctx3, ex3)
200+
ctx5, ex5 = linearize_ir(ctx4, ex4)
201+
expr_form = to_lowered_expr(ex5)
202+
ccall(:jl_toplevel_eval, Any, (Any, Any), mod, expr_form)
203+
end
180204
catch err
181205
throw(MacroExpansionError(mctx, ex, "Macro not found", :all, err))
182206
end

JuliaLowering/test/macros.jl

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,5 +515,27 @@ end
515515
""")
516516
code = JuliaLowering.include_string(test_mod, """Mod1.@indirect_MODULE()""")
517517
@test JuliaLowering.eval(test_mod, code) === test_mod # !== test_mod.Mod1
518+
# the lowering/eval iterator needs to expand in the correct world age (currently
519+
# the only way to hit this from user code is macros producing toplevel)
520+
521+
@testset "macros defining macros" begin
522+
@eval test_mod macro make_and_use_macro_toplevel()
523+
Expr(:toplevel,
524+
esc(:(macro from_toplevel_expansion()
525+
:(123)
526+
end)),
527+
esc(:(@from_toplevel_expansion())))
528+
end
529+
530+
@test JuliaLowering.include_string(
531+
test_mod, "@make_and_use_macro_toplevel()"; expr_compat_mode=true) === 123
532+
533+
if isdefined(test_mod, Symbol("@from_toplevel_expansion"))
534+
Base.delete_binding(test_mod, Symbol("@from_toplevel_expansion"))
535+
end
536+
537+
@test JuliaLowering.include_string(
538+
test_mod, "@make_and_use_macro_toplevel()"; expr_compat_mode=false) === 123
539+
end
518540

519541
end

JuliaLowering/test/macros_ir.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ _never_exist = @m_not_exist 42
147147
#---------------------
148148
MacroExpansionError while expanding @m_not_exist in module Main.TestMod:
149149
_never_exist = @m_not_exist 42
150-
# └──────────┘ ── Macro not found
150+
# ─────────┘ ── Macro not found
151151
Caused by:
152152
UndefVarError: `@m_not_exist` not defined in `Main.TestMod`
153153
Suggestion: check for spelling errors or missing imports.

0 commit comments

Comments
 (0)