Stream: helpdesk (published)

Topic: Accessing __source__ in a generated macro.


view this post on Zulip iago-lito (Jul 30 2026 at 13:58):

I've just stumbled upon the following:

macro simple()
    println(__source__) # ->>--
end                     #     | this line (4)
@simple # <<-------------------

macro make()
    println(__source__) #->>--------
    quote                        # | this line(14)
        macro made()             # |
            println(__source__)  # | ->>--
        end                      # |     |
    end                          # |     | expected this line (15)
end                              # |     | got instead: UndefVarError: `__source__`.
@make # <<--------------------------     |
@made # <<--------------------------------

Is this expected? Can somebody explain?

__source__ is documented to be a keyword, but the experiment below is suggesting otherwise:

julia> xp = :(macro m() __source__ end)
:(macro m()
      #= REPL[2]:1 =#
      #= REPL[2]:1 =#
      __source__
  end)

julia> s = xp.args[2].args[3]
:__source__

julia> s isa #= regular =# Symbol
true

How come julia seems to be wanting to evaluate __source__ in Main within @made() whereas it is understanding it correctly as the special argument within @simple() and @make()?

view this post on Zulip Mason Protter (Jul 30 2026 at 14:06):

It's not a keyword, it's a secret argument to a macro.

julia> macro simple()
           (; __source__, __module__)
       end;

julia> methods(var"@simple")
# 1 method for macro "@simple" from Main:
 [1] var"@simple"(__source__::LineNumberNode, __module__::Module)
     @ REPL[16]:1

julia> var"@simple"(LineNumberNode(-10, "boo!"), Base.Math)
(__source__ = :(#= boo!:-10 =#), __module__ = Base.Math)

view this post on Zulip iago-lito (Jul 30 2026 at 14:07):

Oh, I didn't know you could invoke the macro like this. That's interesting :)

view this post on Zulip Mason Protter (Jul 30 2026 at 14:08):

Yeah, this is what the frontend does when it lowers an expression with a macro in it. It calls this function, and then inserts the returned expression into the code

view this post on Zulip iago-lito (Jul 30 2026 at 14:09):

Mason Protter said:

It's not a keyword, it's a secret argument to a macro.

Maybe the docs should not name it a "keyword" but rather a "parameter" then?

view this post on Zulip Mason Protter (Jul 30 2026 at 14:09):

your example will work if you esc the output.

view this post on Zulip Mason Protter (Jul 30 2026 at 14:10):

julia> macro make()
           println(__source__)
           quote
               macro made()
                   println(__source__)
               end
           end |> esc
       end
@make (macro with 1 method)

julia> @make
#= REPL[26]:1 =#
@made (macro with 1 method)

julia> @made
#= REPL[27]:1 =#

view this post on Zulip iago-lito (Jul 30 2026 at 14:10):

Hm. It does, but I can't make sense of it :\

view this post on Zulip iago-lito (Jul 30 2026 at 14:11):

Let's try to only esc the __source__ maybe?

view this post on Zulip Mason Protter (Jul 30 2026 at 14:11):

@macroexpand is our friend here:

julia> macro make()
           println(__source__)
           quote
               macro made()
                   println(__source__)
               end
           end
       end
@make (macro with 1 method)

julia> @macroexpand @make()
#= REPL[29]:1 =#
quote
    #= REPL[28]:4 =#
    macro Main.made()
        #= REPL[28]:4 =#
        #= REPL[28]:5 =#
        Main.println(Main.__source__)
    end
end

view this post on Zulip Mason Protter (Jul 30 2026 at 14:11):

The hygiene pass turned __source__ into Main.__source__

view this post on Zulip iago-lito (Jul 30 2026 at 14:11):

Yay:

macro make()
    println(__source__)
    src = esc(:__source__)
    quote
        macro made()
            println($src)
        end
    end
end
@make
@made # Works as expected.

view this post on Zulip iago-lito (Jul 30 2026 at 14:13):

Mason Protter said:

The hygiene pass turned __source__ into Main.__source__

Okay, the problem and the solution are both making sense to me now, thanks :)

view this post on Zulip iago-lito (Jul 30 2026 at 14:13):

Now..

view this post on Zulip Mason Protter (Jul 30 2026 at 14:13):

Note that your example does something slightly different (but ends up the same). You threaded the __source__ of @make into @made, but in my example, @made returns its own __source__.

view this post on Zulip iago-lito (Jul 30 2026 at 14:13):

Why would the hygiene pass do this? Feels like a bug.

view this post on Zulip iago-lito (Jul 30 2026 at 14:14):

Seems like it is incorrectly assuming that __source__ should be taken from enclosing scope, whereas it is a local variable of made, right?

view this post on Zulip iago-lito (Jul 30 2026 at 14:14):

reads through esc hygiene docs again

view this post on Zulip Mason Protter (Jul 30 2026 at 14:14):

Mason Protter said:

Note that your example does something slightly different (but ends up the same). You threaded the __source__ of @make into @made, but in my example, @made returns its own __source__.

sorry, scratch that, you did esc(:__source__), not esc(__source__), I misread.

view this post on Zulip Mason Protter (Jul 30 2026 at 14:16):

iago-lito said:

Seems like it is incorrectly assuming that __source__ should be taken from enclosing scope, whereas it is a local variable of made, right?

You didn't pass a local variable from made though, you just passed the bare expression that has a contextless symbol :__source__ in it, so it assumes it was some global variable.

view this post on Zulip iago-lito (Jul 30 2026 at 14:16):

From the docs:

A variable is considered local if it is assigned to (and not declared global), declared local, or used as a function argument name. Otherwise, it is considered global.

view this post on Zulip Mason Protter (Jul 30 2026 at 14:16):

Maybe this helps:

julia> macro foo()
           x = 1
           :(x)
       end
@foo (macro with 1 method)

julia> @foo
ERROR: UndefVarError: `x` not defined in `Main`
Suggestion: check for spelling errors or missing imports.
Stacktrace:
 [1] top-level scope
   @ REPL[31]:1

view this post on Zulip Mason Protter (Jul 30 2026 at 14:17):

also

julia> let x = 1
           @foo
       end
ERROR: UndefVarError: `x` not defined in `Main`
Suggestion: check for spelling errors or missing imports.
Stacktrace:
 [1] top-level scope
   @ REPL[32]:2

view this post on Zulip iago-lito (Jul 30 2026 at 14:17):

I think this misses the case of __source__ and __module__, right? They are incorrectyl considered "global" by the hygiene pass, whereas they are actually local parameters?

view this post on Zulip Mason Protter (Jul 30 2026 at 14:18):

It's not incorrect

view this post on Zulip Mason Protter (Jul 30 2026 at 14:18):

the error was that you didn't tell it that you didn't want them to be considered globals

view this post on Zulip Mason Protter (Jul 30 2026 at 14:18):

julia> macro foo(x)
           quote
               x
           end
       end
@foo (macro with 2 methods)

julia> @foo 1
ERROR: UndefVarError: `x` not defined in `Main`
Suggestion: check for spelling errors or missing imports.
Stacktrace:
 [1] top-level scope
   @ REPL[34]:1
 [2] macro expansion
   @ REPL[33]:3 [inlined]

view this post on Zulip Mason Protter (Jul 30 2026 at 14:19):

julia> @macroexpand @foo 1
quote
    #= REPL[33]:3 =#
    Main.x
end

view this post on Zulip iago-lito (Jul 30 2026 at 14:20):

Mason Protter said:

the error was that you didn't tell it that you didn't want them to be considered globals

I agree that it is my mistake to not have specified that I intended to use the local __source__.
My point is that I believe the hygiene pass should special-case __source__ and __macro__ just like macro does, don't you agree?

view this post on Zulip Mason Protter (Jul 30 2026 at 14:21):

This is why controlling hygiene is important. Consider the following two different macros:

julia> macro foo()
           quote
               x
           end
       end
@foo (macro with 2 methods)

julia> macro bar()
           quote
               $(esc(:x))
           end
       end
@bar (macro with 1 method)

both correctly tell you there's no global named x:

julia> @foo
ERROR: UndefVarError: `x` not defined in `Main`
Suggestion: check for spelling errors or missing imports.
Stacktrace:
 [1] top-level scope
   @ REPL[38]:1
 [2] macro expansion
   @ REPL[36]:3 [inlined]

julia> @bar
ERROR: UndefVarError: `x` not defined in `Main`
Suggestion: check for spelling errors or missing imports.
Stacktrace:
 [1] top-level scope
   @ REPL[39]:1
 [2] macro expansion
   @ REPL[37]:3 [inlined]

but only bar looks for an x in the local scope:

julia> let x = 1
           @foo
       end
ERROR: UndefVarError: `x` not defined in `Main`
Suggestion: check for spelling errors or missing imports.
Stacktrace:
 [1] top-level scope
   @ REPL[40]:2
 [2] macro expansion
   @ REPL[36]:3 [inlined]

julia> let x = 1
           @bar
       end
1

view this post on Zulip iago-lito (Jul 30 2026 at 14:21):

__source__ is not assigned to, not declared local, and not used as a function argument name BUT it is still a local.

view this post on Zulip Mason Protter (Jul 30 2026 at 14:21):

I don't agree at all

view this post on Zulip iago-lito (Jul 30 2026 at 14:23):

I mean at least it would be a sensible default expectation for these two "special" symbols at least within a macro body.

view this post on Zulip Mason Protter (Jul 30 2026 at 14:23):

I would expect them to be treated like other arguments

view this post on Zulip iago-lito (Jul 30 2026 at 14:24):

But they're not. Other arguments are considered local.

view this post on Zulip Mason Protter (Jul 30 2026 at 14:24):

no they arent?

view this post on Zulip Mason Protter (Jul 30 2026 at 14:25):

julia> macro foo(x)
           quote
               x
           end
       end
@foo (macro with 2 methods)

julia> @foo 1
ERROR: UndefVarError: `x` not defined in `Main`
Suggestion: check for spelling errors or missing imports.
Stacktrace:
 [1] top-level scope
   @ REPL[34]:1
 [2] macro expansion
   @ REPL[33]:3 [inlined]

julia> let x = 1
           @foo 2
       end
ERROR: UndefVarError: `x` not defined in `Main`
Suggestion: check for spelling errors or missing imports.
Stacktrace:
 [1] top-level scope
   @ REPL[44]:2
 [2] macro expansion
   @ REPL[42]:3 [inlined]

view this post on Zulip iago-lito (Jul 30 2026 at 14:25):

macro make()
    quote
        macro made(arg)
            println(arg) # <- Considered local by the hygiene pass.
            println(__source__) # <- Considered global.
        end
    end
end
@make
@made 5

view this post on Zulip Mason Protter (Jul 30 2026 at 14:26):

Ah, I see what you mean now. Yeah, that would make sense! This is probably able to be considered a bug

view this post on Zulip iago-lito (Jul 30 2026 at 14:27):

I'm glad we agree :) I'll report that then let's see :)

view this post on Zulip iago-lito (Jul 30 2026 at 14:38):

https://github.com/JuliaLang/julia/issues/62572

view this post on Zulip Notification Bot (Jul 30 2026 at 22:51):

This topic was moved here from #general > Accessing __source__ in a generated macro. by Mason Protter.

view this post on Zulip iago-lito (Jul 31 2026 at 07:32):

(sure @Mason Protter, I'd even agree to upgrade it to #**helpdesk (published)>?) (I can't)

view this post on Zulip Notification Bot (Jul 31 2026 at 09:11):

This topic was moved here from #helpdesk > Accessing __source__ in a generated macro. by Mason Protter.


Last updated: Aug 10 2026 at 05:52 UTC