To get something equivalent to @goto foo and @label foo in a macro, one has to use Expr(:symbolicgoto, :foo) or Expr(:symbolicgoto, :foo) (see eg this and similar topics).
Would it make sense to document this in the docstring of @goto and @label? It is not in the manual or docstrings at all AFAICT.
I see now, @goto/@label only take name::Symbol, not esc(name)::Expr. It’s backwards to force every macro to support escaped input expressions anyway. We can instead escape the @goto/@label expressions, weirdly nested interpolated quotes but whatever:
julia> macro fbody(name::Symbol)
quote
$( esc(:(@goto $name)) )
print("skipped")
$( esc(:(@label $name)) )
print("A")
end
end
@fbody (macro with 2 methods)
julia> @macroexpand f() = @fbody(x)
:(f() = begin
#= REPL[80]:1 =#
begin
#= REPL[79]:3 =#
$(Expr(:symbolicgoto, :x))
#= REPL[79]:4 =#
Main.print("skipped")
#= REPL[79]:5 =#
$(Expr(:symboliclabel, :x))
#= REPL[79]:6 =#
Main.print("A")
end
end)
I believe using API in the expression as in source code is idiomatic. The stability of undocumented expression headers like :symbolicgoto is dubious; there are definitely some expression headers like :thunk that have been mentioned to be strictly internal. I’m not even entirely certain about the stability of the headers and structure of expressions for documented syntax or language keywords, maybe those should be documented or clarified as internal. For example, I don’t see it written anywhere that :(@mac func(input)) needs a line number argument: Expr(:macrocall, Symbol("@mac"), LineNumberNode(0, Symbol("blah")), Expr(:call, :func, :input)).
Nested macro are exempted from interact badly with macro hygiene. It’s a wart in the language, people are working on fixing it with the new JuliaLowering system to have a whole new type of macros.
But for now, when you call a macro inside another macro, it is effectively already escaped from the point of view of the outer macro.
I think that might be about the expressions inside the inner macro call’s definition’s body because the inputs at its call are definitely affected:
julia> macro hygienedemo(name)
quote $( esc(:(@goto $name)) ) end
end
@hygienedemo (macro with 1 method)
julia> @macroexpand @hygienedemo(x)
quote
#= REPL[84]:2 =#
$(Expr(:symbolicgoto, :x))
end
julia> macro hygienedemo2(name)
quote $( (:(@goto $name)) ) end
end
@hygienedemo2 (macro with 1 method)
julia> @macroexpand @hygienedemo2(x)
quote
#= REPL[86]:2 =#
$(Expr(:symbolicgoto, Symbol("#33#x")))
end
which is why macro fbody(name::Symbol) looks as ugly as it is.
Yes, but the immediate problem is that the surface syntax for @goto and @label are technically macros (or are exposed via macros), so this is hard to avoid.
Yes, you’re right, I made way too broad a statement. IIRC there’s some edge-cases where they can essentially auto-escape themselves, but that’s not always the case. I don’t remember the specifics though, I think it’s about the dangers of interpolating into nested macros.
The reason for them staying behind the macro is in case we ever want to change the representation of goto to something different (or make it first class). It would mean making a breaking change, so we’d rather keep it behind the macro for now