There are many things going on here. I’m not sure I can explain everything, but let’s at least try.
The relevant part of the documentation is the section about macro hygiene. We see there that a symbol appearing in a macro is renamed (gensymed, like var"#5#f1" in your example) if the corresponding variable is considered local.
In macro m1, the name f1 is considered to refer to a global variable. But in macro m2, it is detected as being local and is renamed. My guess is that this change in behavior comes from the implicit begin...end block generated by quote...end. But I’m actually not sure whether this is intended or not.
The reason why macro m3 does not work is a bit tricky to explain. It is not really because of the presence of parentheses: as you can easily verify, parentheses surrounding the function name are allowed.
julia> (f)(x) = 2x
f (generic function with 1 method)
julia> f(4)
8
Rather, the presence of parentheses is a subtle indicator that the generated code is not really what you think it is. Let’s inspect it further:
julia> using MacroTools
julia> dump(MacroTools.striplines(@macroexpand T.@m3 foo))
Expr
head: Symbol block
args: Array{Any}((1,))
1: Expr
head: Symbol =
args: Array{Any}((2,))
1: Expr
head: Symbol call
args: Array{Any}((2,))
1: f1 (function of type typeof(Main.T.f1)) # The function itself; not its name
2: Expr
head: Symbol ::
args: Array{Any}((1,))
1: Expr
2: Expr
head: Symbol block
args: Array{Any}((1,))
1: Int64 1
(For comparison, the same output for `@m1`, which works)
julia> dump(MacroTools.striplines(@macroexpand T.@m1 foo))
Expr
head: Symbol =
args: Array{Any}((2,))
1: Expr
head: Symbol call
args: Array{Any}((2,))
1: GlobalRef # (qualified) name Main.T.f1
mod: Module Main.T
name: Symbol f1
2: Expr
head: Symbol ::
args: Array{Any}((1,))
1: Expr
head: Symbol call
args: Array{Any}((2,))
1: GlobalRef
2: Symbol foo
2: Expr
head: Symbol block
args: Array{Any}((1,))
1: Int64 1
What we see is that, instead of the name of f1, we got the function itself. Let’s try and understand what happens with this whole $(esc(...)) syntax. In the case of $(esc(sym)), which you got right:
-
sym is evaluated in the context of the macro expansion, yielding the symbol :foo, because that’s what the macro argument was named
- this symbol
:foo is escaped, meaning that it is marked to not be changed for hygiene purposes → it therefore reaches the macro expansion without modification
- when the macro expansion gets evaluated (in the context of the macro call),
:foo therefore refers to the function named foo in the current scope
Let’s follow the same steps in the case of $(esc(f1)):
-
f1 is evaluated in the context of the macro expansion, yielding the function T.f1 (not its name: the function itself)
-
escaping the function doesn’t mean much; nothing is done about it regarding hygiene (EDIT: to be clear, and as noted by @mcabbott, esc has no effect whatsoever here, and can be removed without changing anything)
-
when the macro expansion gets evaluated, Julia tries to interpret function T.f1 as a function name, which does not make sense => error
julia> T.@m3 foo
ERROR: syntax: invalid function name "typeof(T.f1)()"
All in all, if you surround your code in a quote...end block (which I would advise to do), you’ll have to make it so that Julia understands f1 as being a global variable. There are (at least) two ways of doing this:
- mark it with the
global keyword (-> @m2 in the example below)
- qualify the function name with the module name (->
@m3 in the example below)
module T
function f1 end
macro m1(sym)
:(f1(::typeof($(esc(sym)))) = 1)
end
macro m2(sym)
quote
global f1
f1(::typeof($(esc(sym)))) = 1
end
end
macro m3(sym)
quote
T.f1(::typeof($(esc(sym)))) = 1
end
end
end
We can check that all three macros work:
julia> using .T: @m1, @m2, @m3, f1
julia> foo1() = 1;
julia> @m1(foo1)
f1 (generic function with 1 method)
julia> foo2() = 1;
julia> @m2(foo2)
f1 (generic function with 2 methods)
julia> foo3() = 1;
julia> @m3(foo3)
julia> methods(T.f1)
# 3 methods for generic function "f1":
[1] f1(::typeof(foo3)) in Main at REPL[1]:18
[2] f1(::typeof(foo2)) in Main at REPL[1]:12
[3] f1(::typeof(foo1)) in Main at REPL[1]:6