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()?
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)
Oh, I didn't know you could invoke the macro like this. That's interesting :)
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
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?
your example will work if you esc the output.
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 =#
Hm. It does, but I can't make sense of it :\
Let's try to only esc the __source__ maybe?
@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
The hygiene pass turned __source__ into Main.__source__
Yay:
macro make()
println(__source__)
src = esc(:__source__)
quote
macro made()
println($src)
end
end
end
@make
@made # Works as expected.
Mason Protter said:
The hygiene pass turned
__source__intoMain.__source__
Okay, the problem and the solution are both making sense to me now, thanks :)
Now..
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__.
Why would the hygiene pass do this? Feels like a bug.
Seems like it is incorrectly assuming that __source__ should be taken from enclosing scope, whereas it is a local variable of made, right?
reads through hygiene docs againesc
Mason Protter said:
Note that your example does something slightly different (but ends up the same). You threaded the
__source__of@makeinto@made, but in my example,@madereturns its own__source__.
sorry, scratch that, you did esc(:__source__), not esc(__source__), I misread.
iago-lito said:
Seems like it is incorrectly assuming that
__source__should be taken from enclosing scope, whereas it is a local variable ofmade, 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.
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.
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
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
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?
It's not incorrect
the error was that you didn't tell it that you didn't want them to be considered globals
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> @macroexpand @foo 1
quote
#= REPL[33]:3 =#
Main.x
end
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?
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
__source__ is not assigned to, not declared local, and not used as a function argument name BUT it is still a local.
I don't agree at all
I mean at least it would be a sensible default expectation for these two "special" symbols at least within a macro body.
I would expect them to be treated like other arguments
But they're not. Other arguments are considered local.
no they arent?
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]
macro make()
quote
macro made(arg)
println(arg) # <- Considered local by the hygiene pass.
println(__source__) # <- Considered global.
end
end
end
@make
@made 5
Ah, I see what you mean now. Yeah, that would make sense! This is probably able to be considered a bug
I'm glad we agree :) I'll report that then let's see :)
https://github.com/JuliaLang/julia/issues/62572
This topic was moved here from #general > Accessing __source__ in a generated macro. by Mason Protter.
(sure @Mason Protter, I'd even agree to upgrade it to #**helpdesk (published)>?) (I can't)
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