Skip to content

Handle escaped assignee from macroexpand output #29

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 25, 2025
Merged

Conversation

fonsp
Copy link
Member

@fonsp fonsp commented Apr 25, 2025

Something changed in macroexpand in Julia 1.12. The result now includes lots of Expr(Symbol("hygienic-scope") and Expr(:escape, and ExpressionExplorer needs to handle this correctly.

Change in macroexpand output

Julia 1.10:

julia> @macroexpand @enum Fruit 🍎 🍐
:($(Expr(:toplevel, :(#= Enums.jl:210 =#), quote
    $(Expr(:meta, :doc))
    primitive type Fruit <: Base.Enums.Enum{Int32} 32 end
end, :(#= Enums.jl:211 =#), :(function Fruit(var"#6#x"::Base.Enums.Integer)
      #= Enums.jl:211 =#
      #= Enums.jl:212 =#
      (0 Base.Enums.:<= var"#6#x" Base.Enums.:<= 1) || Base.Enums.enum_argument_error(:Fruit, var"#6#x")
      #= Enums.jl:213 =#
      return Base.Enums.bitcast(Fruit, Base.Enums.convert(Int32, var"#6#x"))
  end), :(#= Enums.jl:215 =#), :((Base.Enums.Enums).namemap(::Base.Enums.Type{Fruit}) = begin
          #= Enums.jl:215 =#
          Dict{Int32, Symbol}(0 => :🍎, 1 => :🍐)
      end), :(#= Enums.jl:216 =#), :((Base.Enums.Base).typemin(var"#8#x"::Base.Enums.Type{Fruit}) = begin
          #= Enums.jl:216 =#
          Fruit(0)
      end), :(#= Enums.jl:217 =#), :((Base.Enums.Base).typemax(var"#9#x"::Base.Enums.Type{Fruit}) = begin
          #= Enums.jl:217 =#
          Fruit(1)
      end), :(#= Enums.jl:218 =#), :(let var"#3#type_hash" = Base.Enums.hash(Fruit)
      #= Enums.jl:223 =#
      (Base.Enums.Enums)._enum_hash(var"#11#x"::Fruit, var"#12#h"::Base.Enums.UInt) = begin
              #= Enums.jl:223 =#
              Base.Enums.hash(var"#3#type_hash", Base.Enums.hash(Base.Enums.Integer(var"#11#x"), var"#12#h"))
          end
  end), :(#= Enums.jl:225 =#), :(let var"#4#insts" = (Base.Enums.Any[Fruit(var"#5#v") for var"#5#v" = Int32[0, 1]]...,)
      #= Enums.jl:226 =#
      (Base.Enums.Base).instances(::Base.Enums.Type{Fruit}) = begin
              #= Enums.jl:226 =#
              var"#4#insts"
          end
  end), :(const 🍎 = Fruit(0)), :(const 🍐 = Fruit(1)), :(Base.Enums.nothing))))


Julia 1.12

julia%                                                                          ➜  ~ julia +1.12
               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.12.0-beta1 (2025-04-02)
 _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org release
|__/                   |

julia> @macroexpand @enum Fruit 🍎 🍐
:($(Expr(:toplevel, :(#= Enums.jl:210 =#), :($(Expr(Symbol("hygienic-scope"), :(#= Enums.jl:210 =# Base.@__doc__ primitive type $(Expr(:escape, :Fruit)) <: Enum{Int32} 32 end), Base.Enums, :(#= REPL[1]:1 =#)))), :(#= Enums.jl:211 =#), :($(Expr(Symbol("hygienic-scope"), :(function ($(Expr(:escape, :Fruit)))(x::Integer)
      #= Enums.jl:211 =#
      #= Enums.jl:212 =#
      0 <= x <= 1 || enum_argument_error(:Fruit, x)
      #= Enums.jl:213 =#
      return bitcast($(Expr(:escape, :Fruit)), convert(Int32, x))
  end), Base.Enums, :(#= REPL[1]:1 =#)))), :(#= Enums.jl:215 =#), :($(Expr(Symbol("hygienic-scope"), :(Enums.namemap(::Type{$(Expr(:escape, :Fruit))}) = begin
          #= Enums.jl:215 =#
          $(Expr(:escape, Dict{Int32, Symbol}(0 => :🍎, 1 => :🍐)))
      end), Base.Enums, :(#= REPL[1]:1 =#)))), :(#= Enums.jl:216 =#), :($(Expr(Symbol("hygienic-scope"), :(Base.typemin(x::Type{$(Expr(:escape, :Fruit))}) = begin
          #= Enums.jl:216 =#
          ($(Expr(:escape, :Fruit)))(0)
      end), Base.Enums, :(#= REPL[1]:1 =#)))), :(#= Enums.jl:217 =#), :($(Expr(Symbol("hygienic-scope"), :(Base.typemax(x::Type{$(Expr(:escape, :Fruit))}) = begin
          #= Enums.jl:217 =#
          ($(Expr(:escape, :Fruit)))(1)
      end), Base.Enums, :(#= REPL[1]:1 =#)))), :(#= Enums.jl:218 =#), :($(Expr(Symbol("hygienic-scope"), :(let type_hash = hash($(Expr(:escape, :Fruit)))
      #= Enums.jl:223 =#
      Enums._enum_hash(x::$(Expr(:escape, :Fruit)), h::UInt) = begin
              #= Enums.jl:223 =#
              hash(type_hash, hash(Integer(x), h))
          end
  end), Base.Enums, :(#= REPL[1]:1 =#)))), :(#= Enums.jl:225 =#), :($(Expr(Symbol("hygienic-scope"), :(let insts = (Any[($(Expr(:escape, :Fruit)))(v) for v = Int32[0, 1]]...,)
      #= Enums.jl:226 =#
      Base.instances(::Type{$(Expr(:escape, :Fruit))}) = begin
              #= Enums.jl:226 =#
              insts
          end
  end), Base.Enums, :(#= REPL[1]:1 =#)))), :($(Expr(Symbol("hygienic-scope"), :(const $(Expr(:escape, :🍎)) = ($(Expr(:escape, :Fruit)))(0)), Base.Enums, :(#= REPL[1]:1 =#)))), :($(Expr(Symbol("hygienic-scope"), :(const $(Expr(:escape, :🍐)) = ($(Expr(:escape, :Fruit)))(1)), Base.Enums, :(#= REPL[1]:1 =#)))), :($(Expr(Symbol("hygienic-scope"), :nothing, Base.Enums, :(#= REPL[1]:1 =#)))))))

The solution is to simply ignore the :escape in expressions like :(Expr(:escape, :asdf) = 123). We could track whether we are inside hygienic-scope, but I don't escape-without-hygienicscope is an important edge case to take into account.

@fonsp
Copy link
Member Author

fonsp commented Apr 25, 2025

I also asked the Julia community if this change was intentional: JuliaLang/julia#58225

@fonsp fonsp merged commit 8848b96 into main Apr 25, 2025
6 checks passed
@fonsp fonsp deleted the handle-escaped-assignee branch April 25, 2025 12:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant