Escaping expressions within macro calls

julia> macro macro1(expr)
           esc_expr = esc(expr)
           :($expr, $esc_expr)

julia> macro macro2(expr)
           esc_expr = esc(expr)
           :(@foo $expr, $esc_expr)

julia> @macroexpand1 @macro1 x
:((Main.x, x))

julia> @macroexpand1 @macro2 x
:(#= REPL[2]:3 =# @foo (x, $(Expr(:escape, :x))))

I was expecting :(Main.@foo (Main.x, x)) as the result of @macro2. Is this the expected behavior?

Indeed it is! Macros apply to the entire expression to the right of them. If you want them not to, you need to use parentheses. This can indeed seem like a “gotcha”, but see the following example:

julia> a = rand(10);

julia> +(@view a[1:2], [3, 4])
ERROR: LoadError: ArgumentError: Invalid use of @view macro: argument must be a reference expression A[...].
 [1] @view(::LineNumberNode, ::Module, ::Any) at ./views.jl:114
in expression starting at REPL[1857]:1

julia> +(@view(a[1:2]), [3, 4])
2-element Array{Float64,1}:

Many macros must be able to work even across equal signs, commas, etc., so this behavior is necessary.

1 Like

I’m sorry, I think I wasn’t clear enough. My question was not about the number of arguments in the @foo call. I’m asking if @macro2 shouldn’t process any esc() calls within @foo.

A simpler example:

julia> macro macro3(expr)
       :(@foo $(esc(expr)))

julia> @macroexpand1 @macro3 x
:(#= REPL[1]:2 =# @foo $(Expr(:escape, :x)))

Shouldn’t the result be Main.@foo x?

I guess I could achieve what I want in @macro2 with:

macro macro4(expr)
	:($(@__MODULE__).@foo $(@__MODULE__).$expr, $expr)

Macros are actually special here, because quoted macrocalls in other macros get expanded inside that macro’s definition, not inside its caller. So in this case @foo is already expanded inside @macro3, that’s why there’s no need for lowering to make it a GlobalRef. This should illustrate what is going on:

julia> macro m()
           @show __source__
           @show (@__LINE__, @__FILE__)
           :(@__LINE__, @__FILE__)
@m (macro with 1 method)

julia> @m
__source__ = :(#= REPL[2]:1 =#)
(#= REPL[1]:3 =# @__LINE__(), #= REPL[1]:3 =# @__FILE__()) = (3, "REPL[1]")
(4, "REPL[1]")

Does that help?


That indeed helped. Thanks.