Creating a variable inside a macro with specified name

Hi all,

I am trying to create a variable inside a Julia macro, then return the variable to the REPL, the variable should also have the name I specified from the macro input argument. I have two tests, the first test function below seems working:

macro my_test(args)

    var_call = add_variable(args)

    return esc( quote $args = $var_call end)
end

function add_variable(name)
    d = PhaseIndex(1)

    var_ref = generic_dy_ref(d,2)

    return var_ref
end

struct PhaseIndex
    p::Int64
end

struct generic_dy_ref
    phase::PhaseIndex
    index::Int64
end

It is able to generate a variable in the REPL with the same name as the input argument.

julia> @my_test(x)
generic_dy_ref(PhaseIndex(1), 2)

julia> x
generic_dy_ref(PhaseIndex(1), 2)

In my own macro I require a function call which is enclosed by :(), however, adding :() seems to create error at the return line, here is the modified test macro and the returning error, it seems that the “var_call” cannot be evaluated in this way?

macro my_test(args)
    arg = esc(args)

    var_call = :(add_variable($arg))
   
    return esc( quote $args = $var_call end)
end
julia> @my_test(s)
ERROR: syntax: invalid syntax (escape (outerref s)) 
Stacktrace:
 [1] top-level scope

But I do need the :() otherwise my own macro will not work, is there any way to get it working?
@odow I asked you about this on SO yesterday.

Thank you for your time.

You’re double escaping. You have esc(args) and at the end esc again. That’s one of the difficulties of macro hygiene in Julia that this is not allowed.

1 Like

The problem in this case is that you are escaping your variable twice in the returned expression.

It is very convenient to use the @macroexpand macro to verify what is the output of your macros to spot errors if something doesn’t work as intended.

In your case, using @macroexpand you would see:

julia> @macroexpand @my_test(s)
quote
    #= REPL[8]:6 =#
    s = add_variable($(Expr(:escape, :s)))
end

which shows that there is something weird on the arguments of add_variable.

If you remove your first line in the macro definition, everything should work as expected

julia> macro my_test(args)
           var_call = :(add_variable($args))
           return esc( quote $args = $var_call end)
       end

julia> @macroexpand @my_test(s)
quote
    #= REPL[10]:5 =#
    s = add_variable(s)
end
2 Likes

Thank you,

But what if I have to use esc() for one of the function arguments? My “model” here is a structure type, I have to use esc to ensure hygiene.

I guess I have to adjust the “ref” in the return line to exclude the escaped part of the expression?

macro differential(model,args...)

    ref = :(add_variable($(esc(model)),$(args)))
    ...

return esc( quote $args = $ref end)

I think it might be useful to have a read at this other discourse post:

to have a better understanding of escaping in macros.

As written there, each input generally has to be escaped once and exactly once.

So for your added example, you also just avoid escaping model (like done for args in the previous answer) inside this line:

ref = :(add_variable($(esc(model)),$(args)))

as it will anyhow be escaped once you escape the quote returned by the macro.

Thank you very much,

A final question, as I was starting to use macroexpand to check my code, how did you tell (like the code you showed above) that there should be error from something like $(Expr(:escape, :s)) as the function argument? From my understanding, macroexpand is just showing the expression to be evaluated from macro return.

You could look at JuMP for inspiration since its macros seem to do this (@variable in particular).

Except for the begin end that becomes quote end the output of @macroexpand should look like the code you’d write manually if you didn’t have the macro (for the most part, there are also linenumbernodes that you can ignore).

So whenever you see an Expr in the output of the @macroexpand, unless you are building macros to create expressions it means that something went wrong.

The $(Expr(:escape, _something_)) is quite a common pattern when starting to make macros as hygiene and escaping is not so straightforward so you are bound to find them a lot in the various macro definition attempts (at least I did and still do when creating macros). But you soon learn that it’s not supposed to be in the output of @macroexpand, and what it’s causing that (too many esc on a variable).

2 Likes