Document Expr(:symbolicgoto, ...) and :symboliclabel

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.

What do you mean exactly, because it is possible to write macro calls in quotes:

julia> macro fbody()
         esc(quote
         @goto A
         print("skipped")
         @label A
         print("A")
         end)
       end
@fbody (macro with 1 method)

julia> f() = (@fbody); f()
A
julia> @macroexpand f() = (@fbody)
:(f() = begin
          #= REPL[45]:1 =#
          begin
              #= REPL[42]:3 =#
              $(Expr(:symbolicgoto, :A))
              #= REPL[42]:4 =#
              print("skipped")
              #= REPL[42]:5 =#
              $(Expr(:symboliclabel, :A))
              #= REPL[42]:6 =#
              print("A")
          end
      end)

I could not find a way without escaping the whole body. Specifically, when I want to determine the labels myself, as in

macro fbody()
    quote
        @goto $esc(A)
        print("skipped")
        @label $esc(A)
        print("A")
    end
end

but perhaps I am using the wrong syntax.

There are two issues here though:

  1. getting the above to work,
  2. having part of the AST undocumented (which I think still is an issue).

Like this?

julia> macro fbody(name::Symbol)
         esc(quote
         @goto $name
         print("skipped")
         @label $name
         print("A")
         end)
       end
@fbody (macro with 2 methods)

julia> f() = @fbody(x); f()
A

The problem with @goto $esc(A) is it’s only escaping esc and the variable A does not exist.

Sorry, perhaps I was not clear in my reply. Again, assume that I don’t want to escape the whole block.

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