This is because the @add_default_constructor
macro expansion happens during the @verify
macro expansion, and not during the evaluation of the result of @verify
.
To make things maybe a bit clearer, let’s separate the two macro expansion phases:
# Fake module, so that things are self-sufficient
module julia_utils
macro add_default_constructor(x)
"OK"
end
end
macro verify()
quote
using .julia_utils: @add_default_constructor
mutable struct A
a::Int
A() = new()
end
@add_default_constructor A
end |> esc
end
This is the first (non-recursive) macro expansion:
julia> @macroexpand1 @verify
quote
#= REPL[2]:3 =#
using .julia_utils: @add_default_constructor
#= REPL[2]:4 =#
mutable struct A
#= REPL[2]:5 =#
a::Int
#= REPL[2]:6 =#
A() = begin
#= REPL[2]:6 =#
new()
end
end
#= REPL[2]:8 =#
#= REPL[2]:8 =# @add_default_constructor A
end
As you can see, everything goes well at this stage. But then the output of @verify
contains a macro call. Before evaluating this output syntax, Julia has to expand this second macro call. Let me re-iterate to make this very clear: the @add_default_constructor
macro call gets expanded before the output syntax of @verify
begins evaluating. At this stage, julia_utils.@add_default_constructor
is still unknown, since it will be added to the environment later, when the first line of the output syntax of @verify
will be evaluated.
Does this make sense?
Any solution where the using julia_utils
and the @add_default_constructor
macro call happen in two clearly separate contexts will work. Including (but not limited to) the one you found.
An other solution would be to load the @add_default_constructor
macro during the expansion of @verify
:
macro verify()
@eval using .julia_utils: @add_default_constructor
quote
mutable struct A
a::Int
A() = new()
end
@add_default_constructor A
end |> esc
end
julia> @macroexpand @verify
quote
#= REPL[2]:4 =#
mutable struct A
#= REPL[2]:5 =#
a::Int
#= REPL[2]:6 =#
A() = begin
#= REPL[2]:6 =#
new()
end
end
#= REPL[2]:8 =#
"OK"
end
I guess such a solution should not be recommended since it will only work in the top-level context. But seeing the rest of the macro expansion, it should be relatively safe in this case: defining a new type will also only work at top level.