Importing a macro within another macro

Why doesn’t this work?

macro verify()
    return esc(quote
        using julia_utils: @add_default_constructor
        mutable struct A
            a::Int
            A() = new()
        end
        @add_default_constructor A
    end)
end
@verify

gives

ERROR: LoadError: LoadError: UndefVarError: @add_default_constructor not defined

However,

macro verify()
    return esc(quote
        using julia_utils: @add_default_constructor
    end)
end
@verify
mutable struct A       
    a::Int
    A() = new()
end
@add_default_constructor A

works just fine.

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.

1 Like

I have read this over a few times, and I am sure you have done a great job explaining this, but all I was able to take away was: @add_default_constructor gets expanded before @verify is evaluated, and for that reason @add_default_constructor is not yet available (known) during evaluation. If what I said is correct, I still don’t understand what I am saying, or rather, why.

Had not seen that before responding.

Ok, this is getting clearer. Thank you!

Since @macro_expand return a Expr, you can actually pass the quoted expression to macro expand and return the result in the macro definition

The @eval works nicely.

macro Ma(a)
    return esc(quote
        if $a <= 10
            println($a)
            @eval @Ma $a+1
        end
    end)
end
@Ma 1
1
2
3
4
5
6
7
8
9
10

Thanks @ffevotte.