# Help: macro expander not gensyming local variables in returned function expr if signature is escaped

**URL:** <https://discourse.julialang.org/t/help-macro-expander-not-gensyming-local-variables-in-returned-function-expr-if-signature-is-escaped/19446>\
**Category:** General Usage\
**Tags:** macros, metaprogramming\
**Created:** [January 9, 2019, 7:12pm UTC](https://discourse.julialang.org/t/help-macro-expander-not-gensyming-local-variables-in-returned-function-expr-if-signature-is-escaped/19446 "2019-01-09T19:12:03Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![NHDaly](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nhdaly/32/1413_2.png) [@NHDaly](https://discourse.julialang.org/u/NHDaly)\
**Post date:** [January 9, 2019, 7:12pm UTC](https://discourse.julialang.org/t/help-macro-expander-not-gensyming-local-variables-in-returned-function-expr-if-signature-is-escaped/19446/1 "2019-01-09T19:12:04Z")

</div>

Hey, can anyone help with this macro expansion not working like I expect?

It’s not `gensym`ing the local variables in a function body if the function’s signature is `esc`aped. 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 `gensym`ing local variables in the function body!

Here’s a MWE:  
Un-escaped, everything is fine:

```julia
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
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?

---

<div class="post-metadata">

**Author:** ![NHDaly](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nhdaly/32/1413_2.png) [@NHDaly](https://discourse.julialang.org/u/NHDaly)\
**Post date:** [January 9, 2019, 7:14pm UTC](https://discourse.julialang.org/t/help-macro-expander-not-gensyming-local-variables-in-returned-function-expr-if-signature-is-escaped/19446/2 "2019-01-09T19:14:14Z")

</div>

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
julia> macro m()
         quote
           $(Expr(:function, :($(esc(:f))($(esc(:x)))), quote
             v = x + 1
             v
           end))
         end
       end

```

---

<div class="post-metadata">

**Author:** ![jeff.bezanson](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jeff.bezanson/32/48_2.png) [@jeff.bezanson](https://discourse.julialang.org/u/jeff.bezanson)\
**Post date:** [January 9, 2019, 7:50pm UTC](https://discourse.julialang.org/t/help-macro-expander-not-gensyming-local-variables-in-returned-function-expr-if-signature-is-escaped/19446/3 "2019-01-09T19:50:13Z")

</div>

Yes I think this is a bug in the macroexpander. I’ll see if it can be fixed in a non-breaking way.

---

<div class="post-metadata">

**Author:** ![NHDaly](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nhdaly/32/1413_2.png) [@NHDaly](https://discourse.julialang.org/u/NHDaly)\
**Post date:** [January 9, 2019, 8:07pm UTC](https://discourse.julialang.org/t/help-macro-expander-not-gensyming-local-variables-in-returned-function-expr-if-signature-is-escaped/19446/4 "2019-01-09T20:07:16Z")

</div>

Yeah looks like it to me too. Thanks Jeff!!

~~In the meantime, I think I was able to get the behavior I want by wrapping the function body with a `let`-block.~~

🙂 Thanks!

EDIT: JK the `let`-block doesn’t work because then the function arguments are converted into globals by the macro-expander.

---

<div class="post-metadata">

**Author:** ![ffevotte](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ffevotte/32/6587_2.png) [@ffevotte](https://discourse.julialang.org/u/ffevotte)\
**Post date:** [January 9, 2019, 8:08pm UTC](https://discourse.julialang.org/t/help-macro-expander-not-gensyming-local-variables-in-returned-function-expr-if-signature-is-escaped/19446/5 "2019-01-09T20:08:27Z")

</div>

> [@NHDaly](#):
>
> 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.

Not sure I understood exactly what you wanted to do, but if only the _name_ varies, maybe you should escape only it, and not the whole signature.

For example, this seems to work fine:

```julia
macro m()
    name1 = :f
    name2 = :g
    
    quote
        function $(esc(name1))(x)
            v = x+1
            v
        end

        function $(esc(name2))(x)
            v = x+2
            v
        end
    end
end

```

```julia
julia> @macroexpand @m
quote
    #= REPL[9]:3 =#
    function f(#16#x)
        #= REPL[9]:4 =#
        #15#v = #16#x + 1
        #= REPL[9]:5 =#
        #15#v
    end
    #= REPL[9]:8 =#
    function g(#18#x)
        #= REPL[9]:9 =#
        #17#v = #18#x + 2
        #= REPL[9]:10 =#
        #17#v
    end
end

```

And the functions are correctly defined with the names you provided:

```julia
julia> @m
g (generic function with 1 method)

julia> f(1)
2

julia> g(1)
3

```

---

<div class="post-metadata">

**Author:** ![NHDaly](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nhdaly/32/1413_2.png) [@NHDaly](https://discourse.julialang.org/u/NHDaly)\
**Post date:** [January 9, 2019, 8:16pm UTC](https://discourse.julialang.org/t/help-macro-expander-not-gensyming-local-variables-in-returned-function-expr-if-signature-is-escaped/19446/6 "2019-01-09T20:16:23Z")

</div>

🙂 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:

```julia
macro m(funcexpr)
    signature = funcexpr.args[1]

    f1_signature = deepcopy(signature)
    call_expr(generator_signature).args[1] = gensym("f1")

    f2_signature = deepcopy(signature)
    call_expr(generator_signature).args[1] = gensym("f2")
  
     ...
    quote
             $(esc(Expr(:function, signature, body)))
             $(Expr(:function, esc(f1_signature), quote ... end))
             $(Expr(:function, esc(f2_signature), quote ... end))
    end

```

~~I do think it’s a bug, but I have managed to find a workaround for now, which is to make those function bodies `quote let ... end end` instead.~~

EDIT: JK the `let`-block doesn’t work because then the function arguments are converted into globals by the macro-expander.

---

<div class="post-metadata">

**Author:** ![NHDaly](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nhdaly/32/1413_2.png) [@NHDaly](https://discourse.julialang.org/u/NHDaly)\
**Post date:** [January 9, 2019, 8:30pm UTC](https://discourse.julialang.org/t/help-macro-expander-not-gensyming-local-variables-in-returned-function-expr-if-signature-is-escaped/19446/7 "2019-01-09T20:30:23Z")

</div>

Oh, never mind, the `let`-block solution doesn’t work, because then the function arguments are converted into globals by the macro-expander:

```julia

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

```

---

<div class="post-metadata">

**Author:** ![NHDaly](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nhdaly/32/1413_2.png) [@NHDaly](https://discourse.julialang.org/u/NHDaly)\
**Post date:** [January 9, 2019, 8:31pm UTC](https://discourse.julialang.org/t/help-macro-expander-not-gensyming-local-variables-in-returned-function-expr-if-signature-is-escaped/19446/8 "2019-01-09T20:31:14Z")

</div>

Can you let me know if you open a github issue/PR about this? I’m interested to see what this code looks like and if maybe i can help! 🙂

---

<div class="post-metadata">

**Author:** ![ffevotte](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ffevotte/32/6587_2.png) [@ffevotte](https://discourse.julialang.org/u/ffevotte)\
**Post date:** [January 9, 2019, 8:44pm UTC](https://discourse.julialang.org/t/help-macro-expander-not-gensyming-local-variables-in-returned-function-expr-if-signature-is-escaped/19446/9 "2019-01-09T20:44:26Z")

</div>

> [@NHDaly](#):
>
> the `let` -block solution doesn’t work, because then the function arguments are converted into globals by the macro-expander

I think a `let`-block could work, provided that you define aliases for all your function arguments.

This seems to work, for example:

```julia
macro m()
    quote
        $(Expr(:function, esc(:(f(x))), quote
               let x=$(esc(:x))
                   v = x + 1
                   v
               end
           end))
    end
end

```

```julia
julia> @macroexpand @m
quote
    #= REPL[19]:3 =#
    function f(x)
        #= REPL[19]:3 =#
        let #8#x = x
            #= REPL[19]:4 =#
            #7#v = #8#x + 1
            #= REPL[19]:5 =#
            #7#v
        end
    end
end

```

Now getting the arguments list to generate the `let`-assignments is an extra burden, but should not be too difficult…

EDIT: I realize this might be precisely what you wanted to avoid, as you said above 😕

> [@NHDaly](#):
>
> 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

---

<div class="post-metadata">

**Author:** ![NHDaly](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nhdaly/32/1413_2.png) [@NHDaly](https://discourse.julialang.org/u/NHDaly)\
**Post date:** [January 9, 2019, 9:35pm UTC](https://discourse.julialang.org/t/help-macro-expander-not-gensyming-local-variables-in-returned-function-expr-if-signature-is-escaped/19446/10 "2019-01-09T21:35:07Z")

</div>

That’s a really good idea. Thanks!

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:

```julia
    # 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

```

Thanks again for your help though! 🙂
