Macros with multiple parameters

I’m having trouble with macros taking multiple parameters. Specifically, I’m writing a macro which needs to take @__DIR__ and an expression as its arguments. Here’s a simplified example:

macro tm1(dir, action)
   :(begin
        println("tm1 dir: ", $dir)
        $action
     end)
end

The following invocation fails:

@tm1 @__DIR__ begin
   3 + 3
end

The error is:

ERROR: LoadError: LoadError: MethodError: no method matching var"@tm1"(::LineNumberNode, ::Module, ::Expr)
Closest candidates are:
  var"@tm1"(::LineNumberNode, ::Module, ::Any, ::Any) at /path/to/file.jl

But if I invoke the macro with an explicit string instead of @__DIR__, it works:

@tm1 "/tmp" begin
   3 + 3
end

All right, maybe macros don’t like taking another macro as an argument?

macro tm2(dir)
   :(begin
        println("tm2 dir: ", $dir)
     end)
end

@tm2 @__DIR__

Which works fine! So a macro with one parameter can take another macro as an argument, but with two (or more?) it breaks? WTF?

Julia version 1.6.2.

@__DIR__ needs to be in parentheses.

1 Like

The problem is that when you write:

@a @b x

the parser treats that as calling macro @b with argument x, then calling macro @a with just one argument.

You can see that by using dump to look at the syntax tree:

julia> dump(:(@a @b x))
Expr
  head: Symbol macrocall
  args: Array{Any}((3,))
    1: Symbol @a
    2: LineNumberNode
      line: Int64 1
      file: Symbol REPL[5]
    3: Expr
      head: Symbol macrocall
      args: Array{Any}((3,))
        1: Symbol @b
        2: LineNumberNode
          line: Int64 1
          file: Symbol REPL[5]
        3: Symbol x

or just by constructing the expression and letting julia add some parentheses to disambiguate:

julia> :(@a @b x)
:(#= REPL[6]:1 =# @a #= REPL[6]:1 =# @b(x))

And you can fix this by adding the parentheses that you actually want instead:

julia> @tm1(@__DIR__(), begin
         3 + 3
       end)
tm1 dir: /home/user
6

In this case, it’s also sufficient to just add parens on the call to @__DIR__ to avoid it trying to grab the whole begin block as an argument:

julia> @tm1 @__DIR__() begin
         3 + 3
       end
tm1 dir: /home/user
6
2 Likes

Thank you very much!