Hey, can anyone help with this macro expansion not working like I expect?
It’s not gensyming the local variables in a function body if the function’s signature is escaped. Anyone seen this before? Thanks
I want to emit several functions from a macro. They all have the same function signature except for the name of the function, so I was creating that in a variable and interpolating it into the returned quoted expression.
However, I seem to have hit a problem where escaping the function signature stops the macro expander from gensyming local variables in the function body!
Here’s a MWE:
Un-escaped, everything is fine:
julia> macro m()
sig = :(f(x))
quote
$(Expr(:function, sig, quote
v = x + 1
v
end))
end
end
@m (macro with 2 methods)
julia> @macroexpand @m()
quote
#= none:3 =#
function #178#f(#180#x)
#= none:4 =#
#179#v = #180#x + 1
#= none:5 =#
#179#v
end
end
But if I escape the signature, v isn’t escaped:
julia> macro m()
sig = :(f(x))
quote
$(Expr(:function, esc(sig), quote
v = x + 1
v
end))
end
end
@m (macro with 2 methods)
julia> @macroexpand @m()
quote
#= none:3 =#
function f(x)
#= none:4 =#
Main.v = Main.x + 1
#= none:5 =#
Main.v
end
end
Any ideas? Is this a bug in the macro expander?
Also, weirdly, it works if I don’t wrap the returned expression in a quote block, and just return the function expression directly, but I think I need the quote block because i have three functions to return, not just one?
I guess i can go through and escape each piece of the function signature, which does seem to work, but i don’t want to because i want it to work for any kind of signature (including f(x::T) where T, etc) and I don’t want to have to muck about with that structure if i don’t have to.
julia> macro m()
quote
$(Expr(:function, :($(esc(:f))($(esc(:x)))), quote
v = x + 1
v
end))
end
end
Thanks for the help, @ffevotte! Ah, sorry i wasn’t clear.
Unfortunately I do want to escape the whole signature, because I want to escape the name, the arguments, and also any type parameters in a where T clause. Since the only thing i was changing was the name, this is what I’m doing:
Oh, never mind, the let-block solution doesn’t work, because then the function arguments are converted into globals by the macro-expander:
julia> macro m()
quote
$(Expr(:function, esc(:(f(x))), quote let
v = x + 1
v
end end))
end
end
>> @m (macro with 1 method)
julia> @macroexpand @m()
>> quote
#= none:3 =#
function f(x)
#= none:3 =#
let
#= none:4 =#
#327#v = Main.x + 1
#= none:5 =#
#327#v
end
end
end
Actually, what i ended up doing was just manually escaping every part of the signature, which is pretty yucky, but i guess not too bad. it looks like this:
# TODO: There is a bug in the julia macro-expander, preventing escaping the entire signature.
# Therefore, for now, we have to manually escape its internals
methods_helpername = gensym("methods_helper")
methods_signature = deepcopy(main_signature)
call_expr(methods_signature).args[1] = esc(methods_helpername)
call_expr(methods_signature).args[2:end] = [esc(p) for p in function_params]
# Escape the type parameters in where clauses (TODO: remove when bug is fixed)
sigexpr = methods_signature
while sigexpr.head != :call
sigexpr.args[2:end] = [esc(e) for e in sigexpr.args[2:end])]
sigexpr = sigexpr.args[1]
end