Macro: quote versus colon

I am going over macros, and decided to test whether quote and colon were identical. See the code below. Apparently they are not. And if not, it means that there likely side effects in rare instances. What am I missing? Thanks.

julia> e = quote 2+3 end
quote
    #= none:1 =#
    2 + 3
end

julia> dump(e)
Expr
  head: Symbol block
  args: Array{Any}((2,))
    1: LineNumberNode
      line: Int64 1
      file: Symbol none
    2: Expr
      head: Symbol call
      args: Array{Any}((3,))
        1: Symbol +
        2: Int64 2
        3: Int64 3

julia> dump(:(2+3))
Expr
  head: Symbol call
  args: Array{Any}((3,))
    1: Symbol +
    2: Int64 2
    3: Int64 3
2 Likes
quote 2+3 end

is equivalent to

:( begin 2+3 end )

Edit: The documentation (from typing ?quote at the REPL) also says: “Unlike the other means of quoting, :( ... ), this form introduces QuoteNode elements to the expression tree, which must be considered when directly manipulating the tree. For other purposes, :( ... ) and quote .. end blocks are treated identically.” (I’m not sure exactly when that happens.)

4 Likes

I’ve stumbled on this while creating a variation of the assert macro, and it’s pretty significant for that use case. Apparently the LineNumberNode gets in the way of the system error message showing you the point in the code where the macro is used, and what you get is the macro definition line instead:

Example with colon, showing the code line where the macro is used:

macro assertcolon(ex)
    msg = Main.Base.string(ex)
    :(
        $(esc(ex)) ? $(nothing) : throw(AssertionError($msg))
    )
end

function mytestcolon(xx)
    @assertcolon 0 == 2 - xx
end
mytestcolon(2)
mytestcolon(1)

Resulting error message:

LoadError: AssertionError: 0 == 2 - xx
in expression starting at /home/nic/src/myassert.jl:12

mytestcolon(xx::Int64) at myassert.jl:9
top-level scope at myassert.jl:12

Now using quote:

macro assertquote(ex)
    msg = Main.Base.string(ex)
    quote
        $(esc(ex)) ? $(nothing) : throw(AssertionError($msg))
    end
end

function mytestquote(xx)
    @assertquote 0 == 2 - xx
end
mytestquote(2)
mytestquote(1)

The resulting message:

LoadError: AssertionError: 0 == 2 - xx
in expression starting at /home/nic/src/myassert.jl:12

mytestquote(xx::Int64) at myassert.jl:4
top-level scope at myassert.jl:12

As you can see, the message now points straight to the macro line, and retains the top function call, but the actual code line where the macro was called is gone… Gotta use colon to do sneaky code changes, and quote to leave traces. Add this to the list of meta-programming gotchas, I guess!

1 Like

Relevant issue:

1 Like