Use of `eval(QuoteNode(expr))`

The official documentation says that @eval expr returns eval(QuoteNode(expr)). Why not simply expr?
Does it have something to do with interpolation?

That’s wrong. It’s returning an expression that evals the quoted version of expr, i.e. the expression it returns unquoted is more or less eval($(QuoteNode(expr))).

Sorry, but I still can’t see the difference between the following two macros:

macro m1(expr)
    eval(QuoteNode(expr))
end

and

macro m2(expr)
    expr
end

Isn’t the eval above being executed at parse time? And Isn’t eval(QuoteNode(x)) just x, in general?
Could you please provide a minimal example where m1 and m2 show different behavior?

What I mean is that the document is wrong. It also does not mean what you write though. The expression in the doc was meant to be the returned expression of the macro, not the implementation of the macro so not, eval is not executed at parse time. What I said is that the implementation is more or less return :(eval($(QuoteNode(expr))))

2 Likes

Thanks, that helped. Is there a way to see the rewriting steps and not just the final result?
Your example doesn’t work, but you did write “more or less”. This seems to work:

julia> macro my_eval(expr)
           :(eval($(Expr(:quote, expr))))
       end
@my_eval (macro with 1 method)

julia> a = 1; @my_eval $a
1

Are you looking for @macroexpand?

@macroexpand @my_eval $a
1 Like

Yes that is the “more or less” part. QuoteNode and Expr(:quote) treats $ differently. This should be mentioned already in the (dev) doc for expression types/heads but I didn’t check to make sure.

Depending on what you mean as the rewriting step and final result, @macroexpand gives you the result of the macro which include macro hygiene rewrite but not lowering. If you want to see the bare result of the macro without anything else, you can manually call the macro, which is nothing more than a badly named function…

julia> (@eval Base.$(Symbol("@eval")))(LineNumberNode(1), Main, :aaaa)
:(Core.eval(Main, :aaaa))

Do note that you’ll need to be more careful if you want to pass a expression with $ in. The easiest way I can think of now is to actually write a macro that returns the correctly quoted expression for you, i.e.

julia> macro m(expr)
           QuoteNode(expr)
       end
@m (macro with 1 method)

julia> @m $a
:($(Expr(:$, :a)))

julia> (@eval Base.$(Symbol("@eval")))(LineNumberNode(1), Main, (@m $a))
:(Core.eval(Main, $(Expr(:quote, :($(Expr(:$, :a)))))))

julia> (@eval Base.$(Symbol("@eval")))(LineNumberNode(1), Main, $a)
ERROR: syntax: "$" expression outside quote

(Note the use of QuoteNode in this case)

Note that you can also just look at the definition directly. https://github.com/JuliaLang/julia/blob/8649af9abed7d628567f68bf280113ce05d76d3f/base/essentials.jl#L178 I find all the methods for @eval by calling methods on the function @eval and if you know the signature you want to call it with you can also use @which.

julia> @which @eval a
@eval(__source__::LineNumberNode, __module__::Module, ex) in Base at essentials.jl:166
2 Likes

Or

julia> macro call_macro(expr)
           @assert expr.head == :macrocall
           return :($(esc(expr.args[1]))($(QuoteNode(expr.args[2])), $__module__, $(QuoteNode.(expr.args[3:end])...)))
       end
@call_macro (macro with 1 method)

julia> @call_macro @eval a
:(Core.eval(Main, :a))

julia> @call_macro @eval $a
:(Core.eval(Main, $(Expr(:quote, :($(Expr(:$, :a)))))))

julia> eval(@call_macro @eval $a)
ERROR: UndefVarError: a not defined
Stacktrace:
 [1] top-level scope at none:0
 [2] eval at ./boot.jl:328 [inlined]
 [3] eval(::Expr) at ./client.jl:404
 [4] top-level scope at none:0

julia> a = 2
2

julia> eval(@call_macro @eval $a)
2
3 Likes

Thanks for the detailed answers. Maybe I’m doing something wrong but methods doesn’t work with macros so I had to write something like this:

macro methods(expr)
    is_macro = isa(expr, Expr) && expr.head == :macrocall
    :(methods($(esc(is_macro ? expr.args[1] : expr))))
end

I’ll start reading the developer’s documentation right now. Sorry if I asked about things that were already explained there.