Macro hygiene in nested quote not working

unfortunately I face severe problems when dealing with macros with nested quotes. Somehow the standard hygiene does not seem to work… three examples, all failing

first experiment … fails

module NestedQuoteHygiene
  export @hygienetest

  macro hygienetest()
    quote
      function a end  # hygiene
      nestedquote = quote
        println(a)  # no hygiene, i.e. refers to another a
      end
      (nestedquote, a)
    end
  end
end

when running this macro test, you can see that the a within the nested quote is not renamed accordingly

julia> NestedQuoteHygiene.@hygienetest
(quote
    println(a)
end, #8#a)

What do I do wrong?
I really want to refer to the a function defined in the macro, i.e. #8#a

second experiment … failing

I made a second test which looks even more surprising - this is interpolating a now

module NestedQuoteHygiene2
  export @hygienetest2

  macro hygienetest2()
    quote
      function a end  # hygiene
      nestedquote = quote
        println($a)  # no hygiene, i.e. refers to another a
      end
      (nestedquote, a)
    end
  end
end

run it like

julia> a = 4
4

julia> NestedQuoteHygiene2.@hygienetest2
(quote
    println(4)
end, #8#a)

that looks really bad… any idea?

third experiment… also failing

module NestedQuoteHygiene3
  export @hygienetest3

  macro hygienetest3()
    quote
      function a end  # hygiene
      symbol_a = Symbol(a)
      nestedquote = quote
        println($symbol_a)  # ERROR: UndefVarError: symbol_a not defined
      end
      (nestedquote, a)
    end
  end
end
julia> NestedQuoteHygiene3.@hygienetest3
ERROR: UndefVarError: symbol_a not defined

anyone who can help?
EDIT Clarification of Goal: I want to refer to function a end from the inner quote

any help is highly appreciated… this really blocks me currently

1 Like

Try to macroexpand and you will see the problem. In particular, are you after

module NestedQuoteHygiene
macro hygienetest()
    quote
        function a end  # hygiene
        nestedquote = quote
            println($(esc(:a))) # no hygiene, i.e. refers to another a
        end
        (nestedquote, a)
    end
end
end

thanks for helping!

Have you tried your suggestion?
When I am executing it I get ERROR: syntax: invalid syntax (escape (outerref a))

julia> nestedquote, f = NestedQuoteHygiene.@hygienetest
(quote
    #= REPL[1]:11 =#
    println($(Expr(:escape, :a)))
end, #10#a)

julia> eval(nestedquote)
ERROR: syntax: invalid syntax (escape (outerref a))
Stacktrace:
 [1] eval at .\boot.jl:319 [inlined]
 [2] eval(::Expr) at .\client.jl:389
 [3] top-level scope at none:0

also @macroexpand does not look good either

 @macroexpand NestedQuoteHygiene.@hygienetest
quote
    #= REPL[1]:7 =#
    function #12#a end
    #= REPL[1]:9 =#
    #13#nestedquote = (Core._expr)(:block, $(QuoteNode(:(#= REPL[1]:11 =#))), (Core._expr)(:call, :p
rintln, esc(:a)))
    #= REPL[1]:15 =#
    (#13#nestedquote, #12#a)
end

seems still unsolved unfortunately…

Then I misunderstood your specs, can you please tell us what you would like to see in the expanded macro?

I would like to refer to the function a end I am bit astonished, that this was not clear
however as this a gets Base.gensym to #12#a or similar, your escaping does not seem to work

as you see, I run your code and it just fails with the described message.
Also your @macroexpand shows an esc(:a) while the outer function a end is already renamed to function #12#a end

so ideally I want

@macroexpand IdealNestedQuoteHygiene.@idealhygienetest
quote
    #= REPL[1]:7 =#
    function #12#a end
    #= REPL[1]:9 =#
    #13#nestedquote = (Core._expr)(:block, $(QuoteNode(:(#= REPL[1]:11 =#))), (Core._expr)(:call, :p
rintln, #12#a)))
    #= REPL[1]:15 =#
    (#13#nestedquote, #12#a)
end

I think that

module NestedQuoteHygiene
macro hygienetest()
    a = gensym(:a)
    quote
        function $(a) end  # hygiene
        nestedquote = quote
            println($$(a)) # no hygiene, i.e. refers to another a
        end
        (nestedquote, $a)
    end
end
end

should do what you want, ie

julia> macroexpand(Main, :(NestedQuoteHygiene.@hygienetest))
quote
    #= REPL[6]:5 =#
    function #14###a#358 end
    #= REPL[6]:6 =#
    #15#nestedquote = (Core._expr)(:block, $(QuoteNode(:(#= REPL[6]:7 =#))), (Core._expr)(:call, :println, ##a#358))
    #= REPL[6]:9 =#
    (#15#nestedquote, #14###a#358)
end
3 Likes

lovely straightforward solution!
thank you so much!

still I am surprised that this is not easily possible by using “normal” macro hygiene.
Does someone know why julia implemented macro expansion of nested quotes the way it does?
Or does someone know where the code is for the macro expansion?

Macro hygiene is automatically replacing symbols, (AFAIK) there is no API for accessing the generated symbols during expansion.

I don’t think there is anything special about nested quotes here (except that you have to $ twice to get to the outer-outer value).

macroexpand.scm

1 Like

thank you very very much for all the effort, time, and helpful answers!

1 Like

still not complete…

the trick with assignments using Base.gensym does not work if you use it again as an assinment place…
It gets gensymed twice in the outer quote and gensymed once in the inner quote, again leaving me with incompatibility.

module MyTest
  macro mytest()
    a = gensym(:a)
    quote
      $a = 42
      nestedquote = quote
        println($$a)
      end
      (nestedquote, $a)
    end
  end
end
julia> @macroexpand MyTest.@mytest
quote
    #12###a#362 = 42
    #13#nestedquote = (Core._expr)(:block, $(QuoteNode((Core._expr)(:call, :println, ##a#362))
    (#13#nestedquote, #12###a#362)
end

See also https://github.com/JuliaLang/julia/pull/6910.

Also, you can do hygiene the other way round too: escape everything and use gensym when hygiene is needed.

PS: good to see that you’re working on traits :wink:

3 Likes

thanks for the issue link

I see, makes sense! if I escape everything, no extra renamings happen. Makes sense!

module MyTest
  macro mytest()
    a = gensym(:a)
    esc(quote
      $(a) = 42
      nestedquote = quote
        println($$a)
      end
      (nestedquote, $a)
    end)
  end
end

works as hoped for

quote
    #= none:5 =#
    ##a#365 = 42
    #= none:6 =#
    nestedquote = (Core._expr)(:block, $(QuoteNode(:(#= none:7 =#))), (Core._expr)(:call, :println,
##a#365))
    #= none:9 =#
    (nestedquote, ##a#365)
end

PS: :wink: I am almost there

1 Like