Skip to content

Macro hygiene differences in recursive vs non-recursive macroexpands #59354

@Drvi

Description

@Drvi

I recently filed an issue (JuliaServices/Match.jl#123) on the Match.jl repo because of the bad interaction between the @match macro and the logging macros on Julia 1.12

julia> @match 1 begin
          1 => @info "aaa"
       end
ERROR: AssertionError: Assertion to tell the compiler about the definedness of this variable
Stacktrace:
 [1] top-level scope
   @ REPL[30]:2
 [2] macro expansion
   @ logging/logging.jl:415 [inlined]

Turns out the problem is that @match traverses the AST and macroexpands one level at a time, so it can intercept macros like @match_fail which require special handling, and this breaks logging macros on Julia 1.12:

julia> Base.eval(@macroexpand1 @info "aaa")
ERROR: AssertionError: Assertion to tell the compiler about the definedness of this variable

The reason is that the variables which are checked by the @isdefined macro are not getting gensym'd. Here is an example:

julia> macro my_macro()
           quote
               local a
               a = 1
               @isdefined a
           end
       end

julia> @macroexpand @my_macro
quote
    #= REPL[7]:3 =#
    local var"#25#a"
    #= REPL[7]:4 =#
    var"#25#a" = 1
    #= REPL[7]:5 =#
    $(Expr(:isdefined, Symbol("#25#a")))
end

julia> @macroexpand1 @my_macro
quote
    #= REPL[7]:3 =#
    local var"#26#a"
    #= REPL[7]:4 =#
    var"#26#a" = 1
    #= REPL[7]:5 =#
    #= REPL[7]:5 =# @isdefined a
end

julia> using MacroTools

julia> MacroTools.prewalk((ex)->macroexpand(Main, ex, recursive=false), :(@my_macro))
quote
    #= REPL[7]:3 =#
    local var"#27#a"
    #= REPL[7]:4 =#
    var"#27#a" = 1
    #= REPL[7]:5 =#
    $(Expr(:isdefined, :a))
end

Note that a doesn't get gensym'd during the non-recursive macroexpand.

Is this intentional / expected?

Thank you!

julia> versioninfo()
Julia Version 1.12.0-rc1
Commit 228edd6610b (2025-07-12 20:11 UTC)
Build Info:
  Official https://julialang.org release
Platform Info:
  OS: macOS (arm64-apple-darwin24.0.0)
  CPU: 10 × Apple M1 Max
  WORD_SIZE: 64
  LLVM: libLLVM-18.1.7 (ORCJIT, apple-m1)
  GC: Built with stock GC
Threads: 1 default, 1 interactive, 1 GC (on 8 virtual cores)

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions