How to print human-readable macro expansion?

Sometimes I’d like to check what a macro is doing, by displaying its expansion. For simple cases, this is not a problem:

julia> @macroexpand(@assert 0<1)
:(if 0 < 1
      nothing
  else
      Base.throw(Base.AssertionError("0 < 1"))
  end)

The output is very understandable here. But when I try to expand the @enum macro, the output seems completely incomprehensible:

julia> Base.remove_linenums!(
           @macroexpand(@enum Fruit apple)
       )
:($(Expr(:toplevel, :(#= Enums.jl:190 =#), quote
    $(Expr(:meta, :doc))
    primitive type Fruit <: Base.Enums.Enum{Int32} 32 end
end, :(#= Enums.jl:191 =#), :(function Fruit(var"#3#x"::Base.Enums.Integer)
      (0 Base.Enums.:<= var"#3#x" Base.Enums.:<= 0) || Base.Enums.enum_argument_error(:Fruit, var"#3#x")
      return Base.Enums.bitcast(Fruit, Base.Enums.convert(Int32, var"#3#x"))
  end), :(#= Enums.jl:195 =#), :((Base.Enums.Enums).namemap(::Base.Enums.Type{Fruit}) = begin
          Dict{Int32, Symbol}(0 => :apple)
      end), :(#= Enums.jl:196 =#), :((Base.Enums.Base).typemin(var"#5#x"::Base.Enums.Type{Fruit}) = begin
          Fruit(0)
      end), :(#= Enums.jl:197 =#), :((Base.Enums.Base).typemax(var"#6#x"::Base.Enums.Type{Fruit}) = begin
          Fruit(0)
      end), :(#= Enums.jl:198 =#), :(let var"#1#insts" = (Base.Enums.Any[Fruit(var"#2#v") for var"#2#v" = Int32[0]]...,)
      (Base.Enums.Base).instances(::Base.Enums.Type{Fruit}) = begin
              var"#1#insts"
          end
  end), :(const apple = Fruit(0)), :(Base.Enums.nothing))))

Is there some way to understand the code above? Generally, is it possible to print macro expansions in a form that looks like human-authored source code, e.g. if you want to directly insert the expanded code into a source file?

The code is often better to read/comprehend:

julia> @less @enum Fruit apple
macro enum(T::Union{Symbol,Expr}, syms...)
    if isempty(syms)
        throw(ArgumentError("no arguments given for Enum $T"))
    end
    basetype = Int32
    ...

You can also open the source file:

julia> @which @enum Fruit apple
var"@enum"(__source__::LineNumberNode, __module__::Module, T::Union{Expr, Symbol}, syms...)
 in Base.Enums at Enums.jl:123

with an editor of your choice.

MacroTools.@expand was written to help with this problem IIUC, but I find it’s choice of variable names more confusing than the var"#3#x" etc. of @macroexpand. Give it a try and see if you prefer it.

That’s nice to know, but MacroTools.@expand(@enum Fruit apple) still generates rather complicated output. One issue is that there are nested quoted expressions, but Base.remove_linenums! does not recursively descends into inner levels to remove the line number information that clutters the output.

Maybe prettify from MacroTools can help?

2 Likes

Thanks, MacroTools.prettify works wonderfully! All the clutter has been removed, and variables have been renamed to look sane (and cute). I still have questions about the actual code generated by expanding @enum (maybe I should do this in a new thread). I see a sub-expression,

quote
    $(Expr(:meta, :doc))
    primitive type Fruit <: Base.Enums.Enum{Int32} 32 end
end

What’s the meaning of $(Expr(:meta, :doc))? Is this some kind of docstring for the enum?