Help: Unexpected error when using a macro in a for loop

Hi, short history:
I define a macro in a module, it works fine, but when I call this macro inside a for it fails.
Here a full MWE.


## ----------------------------------------------------------------
# I wants a function '_gen_macro' that generate/eval a macro in a module 'mod'
function _macro_ex(mod, exs...)
    quote
        $(mod).foo($exs) # 'foo' is expected to be in the module
    end
end

function _gen_macro(mod)
    @eval mod begin
        macro _macro(ex, exs...)
            $(_macro_ex)($mod, ex, exs...)
        end
    end
end

## ----------------------------------------------------------------
# MWE 
module A
    import Main: _gen_macro
    foo(exs...) = @info("In Mod A", length(exs...))
    _gen_macro(A)
end

## ----------------------------------------------------------------
# Tests (eval each section in sequence, not at the same time)

## this works
let
    A.@_macro 1 2 3 4
end
# Output
# β”Œ Info: In Mod A
# β””   length(exs...) = 4

## But this fails before run time (I think at macro-expansion?)
for Mod in [A]
    @info("This won't be printed")
    Mod.@_macro 1 2 3 4
end
# Output
# ERROR: LoadError: LoadError: UndefVarError: Mod not defined


## But 'Mod' is defined and has '@_macro' in it...
for Mod in [A]
    has_macro = isdefined(Mod, Symbol("@_macro"))
    Mod_names = join(string.(names(Mod; all = true)), ", ")
    @info("This will be printed", Mod, has_macro, Mod_names)
end
# Output
# β”Œ Info: This will be printed
# β”‚   Mod = Main.A
# β”‚   has_macro = true
# β””   Mod_names = "#@_macro, #eval, #foo, #include, @_macro, A, eval, foo, include"

Why does the Mod not defined error occur?

Thanks

[EDIT] Better title, shorter MWE

eval happens at global scope

2 Likes

Hi, thanks. Can you elaborate further?
This is how I think stuff happens. I call eval only inside the function _gen_macro, I call this function when I define the module A, so it define in the global scope of A the macro @_macro. Later I even use A.@_macro successfully. What I don’t understand is why the for loop fails. The error occurs even before the printing line is executed so I suspect any code inside A runs either. Also, the error said Mod is not define, it is like the code inside the loop is being run in a scope other than the loop.

macro expansion happens before runtime, maybe writing it this way makes it more clear:

julia> @Base.__LINE__
1

julia> for b in [Base]
           @b.__LINE__
       end
ERROR: LoadError: UndefVarError: b not defined
in expression starting at REPL[19]:
1 Like

WOW, thanks, I didn’t know that @Mod.macroname is equivalent to Mod.@macroname.
But, there is any workaround?
My goal would be:
To call a macro call _macro contained in several modules listed on Mods with the same expression as argument.

I must add that I want to do that on a local scope.

Mods = [:A]
for Mod in Mods
    @info("This won't be printed")
    @eval $(Mod).@_macro 1 2 3 4
end

This works but is run at global scope.

https://docs.julialang.org/en/v1/base/base/#Base.@eval

takes an optional module which you want to evaluate into, try that? not sure if it will work for your case

Yes but that will eval the expression at the global scope of the module.
The real macro that I’m using do assign new variables, so I want such variables to remain in the scope where I called the macro. At the same time the same macro is defined in several modules, and behave different depending on the module.
For example I want this code:

let
   @A._macro a
   @B._macro b
end

to expand to:

let
   a = "From module A"
   b = "From module B"
end

Implicitly im using the module as the first argument of the macro. I’m maybe using an antipattern.
This is maybe more julian.

let
   # And `esc` A
   @_macro: A a
end

Now it works! Thanks


## ----------------------------------------------------------------
# Now the `_macro` will be defined globally and accept the module as argument
function _macro_ex(mod, exs...)
    quote
        $(esc(mod)).foo($(exs)) # 'foo' is expected to be in the module
    end
end

# Im expecting all arg to be ok
macro _macro(mod, ex, exs...)
    _macro_ex(mod, ex, exs...)
end

## ----------------------------------------------------------------
# MWE 
module A
    import Main: _gen_macro
    foo(exs...) = @info("In Mod A", length(exs...))
end

## ----------------------------------------------------------------
# Tests (eval each section in sequence, not at the same time)

## this works
let
    @_macro A 1 2 3 4
end
# Output
# β”Œ Info: In Mod A
# β””   length(exs...) = 4

## This is now working
for Mod in [A]
    @info("This is now printed")
    @_macro Mod 1 2 3 4
end
# Output
# [ Info: This is now printed
# β”Œ Info: In Mod A
# β””   length(exs...) = 4