Afaik, goto is a primitive. It is simply a decision of surface syntax that you use it as a macro instead of keyword. If you look at code_typed(...; optimize=false)
and code_lowered
you will see it all over the place. As far as I understood, Stefan’s solution disregards the abstract syntax tree (the thing manipulated by macros, with expressions) and instead considers lowered code and the resulting control flow graph. Lowered code does not contain loops:
julia> function f(A)
x=A[1]
@goto skip
return y
@label skip
while x>0
x-=1
end
x
end;
julia> code_lowered(f, Tuple{Vector{Int}})
1-element Array{Core.CodeInfo,1}:
CodeInfo(
2 1 ─ x = (Base.getindex)(A, 1) │
3 └── goto #3 │
4 2 ─ return Main.y │
6 3 ┄ %4 = x > 0 │
└── goto #5 if not %4 │
7 4 ─ x = x - 1 │
└── goto #3 │
9 5 ─ return x
In this sense, loops are not primitive structures in julia. Stefan’s rule needs to operate on a level between AST and lowered code: We see that currently, the lowering already decides whether bindings are local or global. With his rule, we would need to “semi-lower” without this knowledge, then search the CFG, then annotate local/global, and then go on as before. People who want to really understand scoping in top-level would need to understand the lowering process as well as parsing (AST construction).
PS. Thanks @StefanKarpinski for moving us out of the main thread!