Macro scoping issue

I have a macro that provides a small DSL and then introduces a variable in the calling scope. I noticed under some circumstances it fails, here is a MWE:

macro my_macro(var, val)
    :($(esc(var)) = $(val))
end
function test1()
    for i = 1:3
        @eval @my_macro(my_var, $i)
        @show my_var
    end
    @show my_var
end
test1()
my_var = 1
my_var = 2
my_var = 3
my_var = 3

I guess the for loop decides my_var does not clash with anything in the parent scope, however I did not expect my_var to be visible outside the loop.

When I introduce a second call to the macro, after the for loop, suddenly the variable is not even visible inside the loop:

function test2()
    for i = 1:3
        @eval @my_macro(my_var, $i)
        @show my_var
    end

    @my_macro(my_var, 5)
    @show my_var
end
test2()
ERROR: LoadError: UndefVarError: my_var not defined
Stacktrace:
 [1] macro expansion at ./show.jl:641 [inlined]

The problem is that @eval evaluates the following expression in the global scope, even though it is executed inside a function. Actually, after running test1() you’ll also have the variable my_var in Main.

I’m not sure about what’s happening in the second example, but the error does not happen if the name of the variable outside the loop is different.

1 Like

I see, that makes sense. Also escaping val in the macro allows me to have i in the call to the macro without @eval:

macro my_macro(var, val)
    :($(esc(var)) = $(esc(val)))
end

function test1()
    for i = 1:3
        @my_macro(my_var, i)
        @show my_var
    end
    @show my_var
end

which gives

my_var = 1
my_var = 2
my_var = 3
ERROR: LoadError: UndefVarError: my_var not defined
Stacktrace:
 [1] macro expansion at ./show.jl:641 [inlined]

as I would expect.

I think the reason I went with @eval is because I have more complicated expressions in the block I pass to the macro, but I’ll keep digging. Thanks!

1 Like