Module Generation from macros

I am trying to generate modules using a macro, but it keeps failing. I am not sure how to handle this.
EG.

julia> macro mymodule(name)
                quote
                  module ($name)
                  end
                end
              end
@mymodule (macro with 1 method)

julia> @mymodule Foo
ERROR: syntax: module expression not at top level

So Julia does not seem to want to accept this. However, I found and closed issues on that very same topic

You can @eval the module to get around this.

julia> macro mymodule(name)
           quote
               @eval module $(esc(name))
               end
           end
       end
@mymodule (macro with 1 method)

julia> @mymodule Foo
Foo

julia> Foo
Foo
1 Like

Try:

macro mymodule(name)
  Expr(:toplevel,
    :(module ($name)
      end))
end
2 Likes

@fengyang.wang Thank you! Your solution worked very nicely.
@MikeInnes Your solution worked for 0.5, but not 0.6, I am not sure why.

The name (and in general, all user inputs) has to be escaped.

1 Like

Does someone know why the first macro works and the second fails?

macro mymodule(name)
    modname = esc(name)
    return :(module $modname end)
end
@mymodule MyMod

Works. And the second, using a quote block does not throwing ERROR: syntax: "module" expression not at top level (see also https://github.com/JuliaLang/julia/issues/1200):

macro mymodule(name)
    modname = esc(name)
    return quote module $modname end end
end
@mymodule MyMod

If you do a @macroexpand on both versions you will see that in the first instance you get something like

Expr
  head: Symbol module
  args: Array{Any}((3,))
    1: Bool true
    2: Symbol MyMod
    3: Expr
      head: Symbol block
      args: Array{Any}((2,))
        1: LineNumberNode
          line: Int64 3
          file: Symbol REPL[7]
        2: LineNumberNode
          line: Int64 3
          file: Symbol REPL[7]

And in the second, you get

Expr
  head: Symbol block
  args: Array{Any}((2,))
    1: LineNumberNode
      line: Int64 3
      file: Symbol REPL[12]
    2: Expr
      head: Symbol module
      args: Array{Any}((3,))
        1: Bool true
        2: Symbol MyMod
        3: Expr
          head: Symbol block
          args: Array{Any}((2,))
            1: LineNumberNode
              line: Int64 3
              file: Symbol REPL[12]
            2: LineNumberNode
              line: Int64 3
              file: Symbol REPL[12]

Those are clearly very different. Julia does not allow you to define modules inside of blocks. If it would, you can have modules defined inside for loops, which wouldn’t make much sense. As the error indicates, module declaration has to be at the top level.

The generated expression in the second case is equivalent to

begin
module MyMod
end
end

Which Julia will simply not allow.

1 Like

Thank you for the explanation!

Some previous forms do not work fully on v1.6. The two following ones are ok for me on Julia v1.6

macro my_mod(x)
    @assert Meta.isexpr(x, :module, 3)
    (notbare::Bool, nm::Symbol, body::Expr) = x.args
    pushfirst!(body.args, esc(:(
        export go, RES ;
        go() = "launched" ;
        const RES = "landing ok")))
    Expr(:toplevel,
        :(module $(esc(nm)); $(body.args...) end))
end
@my_mod module M1 end
using Test
@test M1.go() == "launched"
@test M1.RES == "landing ok"

AND

macro my_mod2(x)
    @assert Meta.isexpr(x, :module, 3)
    (notbare::Bool, nm::Symbol, body::Expr) = x.args
    pushfirst!(body.args, esc(:(
        export go, RES ;
        go() = "launched" ;
        const RES = "landing ok")))
    Expr(:toplevel,
        Expr(:module, notbare, esc(nm), body))
end
@my_mod2 module M2 end
using Test
@test M2.go() == "launched"
@test M2.RES == "landing ok"
1 Like