Skip to content

Commit cd02671

Browse files
committed
Rewrite macros to use automatic hygiene
Manual `gensym` code often contains a lot of mistakes, either because the user uses something like `.` or `+`, or because it combines code from multiple modules (gensym is only unique within a pre-compile unit). This replaces most uses for macro local variables with proper scope markers, and checks for those markers correctly when doing replacements. I did not fully rewrite `_lift_one_interp_helper` or `replace_ref_begin_end_` however, since, while possible by adding `esc` to every argument that has not used a gensym value, if any other argument did is a value, I worried that could lead to macroexpand.scm making more new mistakes so I left if for a separate PR. Better yet, we could make a unhygienic-scope and unescape pair for marking the inverse/dual of the usual operations (marking a symbol as unescaped within a region of unhygienic (escaped) code to make these various uses easier to implement. But rewrite `replace_ref_begin_end_` to respect argument order and evaluation count (similar to its julia-syntax.scm counterpart) and scoping (not adding `let` unpredictably). This required also rewriting `gen_call_with_extracted_types` to properly support the ways that might require rewriting the expression, particularly against the new `::` syntax support in the interactive macros.
1 parent c4353c1 commit cd02671

File tree

16 files changed

+329
-167
lines changed

16 files changed

+329
-167
lines changed

base/boot.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -746,7 +746,7 @@ end
746746

747747
# module providing the IR object model
748748
# excluding types already exported by Core (GlobalRef, QuoteNode, Expr, LineNumberNode)
749-
# any type beyond these is self-quoting (see also Base.is_ast_node)
749+
# any type beyond these is self-quoting (see also Base.isa_ast_node)
750750
module IR
751751

752752
export CodeInfo, MethodInstance, CodeInstance, GotoNode, GotoIfNot, ReturnNode,

base/cartesian.jl

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,14 @@ If you want just a post-expression, supply [`nothing`](@ref) for the pre-express
3636
parentheses and semicolons, you can supply multi-statement expressions.
3737
"""
3838
macro nloops(N, itersym, rangeexpr, args...)
39-
_nloops(N, itersym, rangeexpr, args...)
39+
_nloops(N, itersym, true, rangeexpr, args...)
4040
end
4141

42-
function _nloops(N::Int, itersym::Symbol, arraysym::Symbol, args::Expr...)
43-
@gensym d
44-
_nloops(N, itersym, :($d->Base.axes($arraysym, $d)), args...)
42+
function _nloops(N::Int, itersym::Symbol, esc_rng::Bool, arraysym::Symbol, args::Expr...)
43+
_nloops(N, itersym, false, :(d->axes($(esc(arraysym)), d)), args...)
4544
end
4645

47-
function _nloops(N::Int, itersym::Symbol, rangeexpr::Expr, args::Expr...)
46+
function _nloops(N::Int, itersym::Symbol, esc_rng::Bool, rangeexpr::Expr, args::Expr...)
4847
if rangeexpr.head !== :->
4948
throw(ArgumentError("second argument must be an anonymous function expression to compute the range"))
5049
end
@@ -55,14 +54,16 @@ function _nloops(N::Int, itersym::Symbol, rangeexpr::Expr, args::Expr...)
5554
ex = Expr(:escape, body)
5655
for dim = 1:N
5756
itervar = inlineanonymous(itersym, dim)
57+
itervar = esc(itervar)
5858
rng = inlineanonymous(rangeexpr, dim)
59-
preexpr = length(args) > 1 ? inlineanonymous(args[1], dim) : (:(nothing))
60-
postexpr = length(args) > 2 ? inlineanonymous(args[2], dim) : (:(nothing))
59+
esc_rng && (rng = esc(rng))
60+
preexpr = length(args) > 1 ? esc(inlineanonymous(args[1], dim)) : nothing
61+
postexpr = length(args) > 2 ? esc(inlineanonymous(args[2], dim)) : nothing
6162
ex = quote
62-
for $(esc(itervar)) = $(esc(rng))
63-
$(esc(preexpr))
63+
for $itervar = $rng
64+
$preexpr
6465
$ex
65-
$(esc(postexpr))
66+
$postexpr
6667
end
6768
end
6869
end
@@ -290,14 +291,15 @@ struct LReplace{S<:AbstractString}
290291
end
291292
LReplace(sym::Symbol, val::Integer) = LReplace(sym, string(sym), val)
292293

293-
lreplace(ex::Expr, sym::Symbol, val) = lreplace!(copy(ex), LReplace(sym, val))
294+
lreplace(ex::Expr, sym::Symbol, val) = lreplace!(copy(ex), LReplace(sym, val), false, 0)
294295

295-
function lreplace!(sym::Symbol, r::LReplace)
296+
function lreplace!(sym::Symbol, r::LReplace, in_quote_context::Bool, escs::Int)
297+
escs == 0 || return sym
296298
sym == r.pat_sym && return r.val
297-
Symbol(lreplace!(string(sym), r))
299+
Symbol(lreplace_string!(string(sym), r))
298300
end
299301

300-
function lreplace!(str::AbstractString, r::LReplace)
302+
function lreplace_string!(str::String, r::LReplace)
301303
i = firstindex(str)
302304
pat = r.pat_str
303305
j = firstindex(pat)
@@ -329,7 +331,7 @@ function lreplace!(str::AbstractString, r::LReplace)
329331
if matching && j > lastindex(pat)
330332
if i > lastindex(str) || str[i] == '_'
331333
# We have a match
332-
return string(str[1:prevind(str, istart)], r.val, lreplace!(str[i:end], r))
334+
return string(str[1:prevind(str, istart)], r.val, lreplace_string!(str[i:end], r))
333335
end
334336
matching = false
335337
j = firstindex(pat)
@@ -339,24 +341,42 @@ function lreplace!(str::AbstractString, r::LReplace)
339341
str
340342
end
341343

342-
function lreplace!(ex::Expr, r::LReplace)
344+
function lreplace!(ex::Expr, r::LReplace, in_quote_context::Bool, escs::Int)
343345
# Curly-brace notation, which acts like parentheses
344-
if ex.head === :curly && length(ex.args) == 2 && isa(ex.args[1], Symbol) && endswith(string(ex.args[1]::Symbol), "_")
345-
excurly = exprresolve(lreplace!(ex.args[2], r))
346+
if !in_quote_context && ex.head === :curly && length(ex.args) == 2 && isa(ex.args[1], Symbol) && endswith(string(ex.args[1]::Symbol), "_")
347+
excurly = exprresolve(lreplace!(ex.args[2], r, in_quote_context, escs))
346348
if isa(excurly, Int)
347349
return Symbol(ex.args[1]::Symbol, excurly)
348350
else
349351
ex.args[2] = excurly
350352
return ex
351353
end
354+
elseif ex.head === :meta || ex.head === :inert
355+
return ex
356+
elseif ex.head === :$
357+
# no longer an executable expression (handle all equivalent forms of :inert, :quote, and QuoteNode the same way)
358+
in_quote_context = false
359+
elseif ex.head === :quote
360+
# executable again
361+
in_quote_context = true
362+
elseif ex.head === :var"hygienic-scope"
363+
# no longer our expression
364+
escs += 1
365+
elseif ex.head === :escape
366+
# our expression again once zero
367+
escs == 0 && return ex
368+
escs -= 1
369+
elseif ex.head === :macrocall
370+
# n.b. blithely go about altering arguments to macros also, assuming that is at all what the user intended
371+
# it is probably the user's fault if they put a macro inside here and didn't mean for it to get rewritten
352372
end
353373
for i in 1:length(ex.args)
354-
ex.args[i] = lreplace!(ex.args[i], r)
374+
ex.args[i] = lreplace!(ex.args[i], r, in_quote_context, escs)
355375
end
356376
ex
357377
end
358378

359-
lreplace!(arg, r::LReplace) = arg
379+
lreplace!(@nospecialize(arg), r::LReplace, in_quote_context::Bool, escs::Int) = arg
360380

361381

362382
poplinenum(arg) = arg

base/experimental.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ within this scope, even if the compiler can't prove this to be the case.
4141
Experimental API. Subject to change without deprecation.
4242
"""
4343
macro aliasscope(body)
44-
sym = gensym()
44+
sym = :aliasscope_result
4545
quote
4646
$(Expr(:aliasscope))
4747
$sym = $(esc(body))

base/meta.jl

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,21 @@ function unescape(@nospecialize ex)
628628
return ex
629629
end
630630

631+
"""
632+
Meta.reescape(unescaped_expr, original_expr)
633+
634+
Re-wrap `unescaped_expr` with the same level of escaping as `original_expr` had.
635+
This is the inverse operation of [`unescape`](@ref) - if the original expression
636+
was escaped, the unescaped expression is wrapped in `:escape` again.
637+
"""
638+
function reescape(@nospecialize(unescaped_expr), @nospecialize(original_expr))
639+
if isexpr(original_expr, :escape) || isexpr(original_expr, :var"hygienic-scope")
640+
return reescape(Expr(:escape, unescaped_expr), original_expr.args[1])
641+
else
642+
return unescaped_expr
643+
end
644+
end
645+
631646
"""
632647
Meta.uncurly(expr)
633648

base/reflection.jl

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1273,22 +1273,22 @@ It also supports the following syntax:
12731273
12741274
```jldoctest
12751275
julia> @macroexpand @invoke f(x::T, y)
1276-
:(Core.invoke(f, Tuple{T, Core.Typeof(y)}, x, y))
1276+
:(Core.invoke(f, Base.Tuple{T, Core.Typeof(y)}, x, y))
12771277
12781278
julia> @invoke 420::Integer % Unsigned
12791279
0x00000000000001a4
12801280
12811281
julia> @macroexpand @invoke (x::X).f
1282-
:(Core.invoke(Base.getproperty, Tuple{X, Core.Typeof(:f)}, x, :f))
1282+
:(Core.invoke(Base.getproperty, Base.Tuple{X, Core.Typeof(:f)}, x, :f))
12831283
12841284
julia> @macroexpand @invoke (x::X).f = v::V
1285-
:(Core.invoke(Base.setproperty!, Tuple{X, Core.Typeof(:f), V}, x, :f, v))
1285+
:(Core.invoke(Base.setproperty!, Base.Tuple{X, Core.Typeof(:f), V}, x, :f, v))
12861286
12871287
julia> @macroexpand @invoke (xs::Xs)[i::I]
1288-
:(Core.invoke(Base.getindex, Tuple{Xs, I}, xs, i))
1288+
:(Core.invoke(Base.getindex, Base.Tuple{Xs, I}, xs, i))
12891289
12901290
julia> @macroexpand @invoke (xs::Xs)[i::I] = v::V
1291-
:(Core.invoke(Base.setindex!, Tuple{Xs, V, I}, xs, v, i))
1291+
:(Core.invoke(Base.setindex!, Base.Tuple{Xs, V, I}, xs, v, i))
12921292
```
12931293
12941294
!!! compat "Julia 1.7"
@@ -1305,19 +1305,19 @@ macro invoke(ex)
13051305
f, args, kwargs = destructure_callex(topmod, ex)
13061306
types = Expr(:curly, :Tuple)
13071307
out = Expr(:call, GlobalRef(Core, :invoke))
1308-
isempty(kwargs) || push!(out.args, Expr(:parameters, kwargs...))
1309-
push!(out.args, f)
1308+
isempty(kwargs) || push!(out.args, Expr(:parameters, Any[esc(kw) for kw in kwargs]...))
1309+
push!(out.args, esc(f))
13101310
push!(out.args, types)
13111311
for arg in args
13121312
if isexpr(arg, :(::))
1313-
push!(out.args, arg.args[1])
1314-
push!(types.args, arg.args[2])
1313+
push!(out.args, esc(arg.args[1]))
1314+
push!(types.args, esc(arg.args[2]))
13151315
else
1316-
push!(out.args, arg)
1317-
push!(types.args, Expr(:call, GlobalRef(Core, :Typeof), arg))
1316+
push!(out.args, esc(arg))
1317+
push!(types.args, Expr(:call, GlobalRef(Core, :Typeof), esc(arg)))
13181318
end
13191319
end
1320-
return esc(out)
1320+
return out
13211321
end
13221322

13231323
getglobalref(gr::GlobalRef, world::UInt) = ccall(:jl_eval_globalref, Any, (Any, UInt), gr, world)
@@ -1367,42 +1367,42 @@ macro invokelatest(ex)
13671367

13681368
if !isa(f, GlobalRef)
13691369
out_f = Expr(:call, GlobalRef(Base, :invokelatest))
1370-
isempty(kwargs) || push!(out_f.args, Expr(:parameters, kwargs...))
1370+
isempty(kwargs) || push!(out_f.args, Expr(:parameters, Any[esc(kw) for kw in kwargs]...))
13711371

13721372
if isexpr(f, :(.))
1373-
s = gensym()
1373+
s = :s
13741374
check = quote
1375-
$s = $(f.args[1])
1375+
$s = $(esc(f.args[1]))
13761376
isa($s, Module)
13771377
end
1378-
push!(out_f.args, Expr(:(.), s, f.args[2]))
1378+
push!(out_f.args, Expr(:(.), s, esc(f.args[2])))
13791379
else
1380-
push!(out_f.args, f)
1380+
push!(out_f.args, esc(f))
13811381
end
1382-
append!(out_f.args, args)
1382+
append!(out_f.args, Any[esc(arg) for arg in args])
13831383

13841384
if @isdefined(s)
1385-
f = :(GlobalRef($s, $(f.args[2])))
1386-
elseif !isa(f, Symbol)
1387-
return esc(out_f)
1385+
f = :(GlobalRef($s, $(esc(f.args[2]))))
1386+
elseif isa(f, Symbol)
1387+
check = esc(:($(Expr(:isglobal, f))))
13881388
else
1389-
check = :($(Expr(:isglobal, f)))
1389+
return out_f
13901390
end
13911391
end
13921392

13931393
out_gr = Expr(:call, GlobalRef(Base, :invokelatest_gr))
1394-
isempty(kwargs) || push!(out_gr.args, Expr(:parameters, kwargs...))
1394+
isempty(kwargs) || push!(out_gr.args, Expr(:parameters, Any[esc(kw) for kw in kwargs]...))
13951395
push!(out_gr.args, isa(f, GlobalRef) ? QuoteNode(f) :
13961396
isa(f, Symbol) ? QuoteNode(GlobalRef(__module__, f)) :
13971397
f)
1398-
append!(out_gr.args, args)
1398+
append!(out_gr.args, Any[esc(arg) for arg in args])
13991399

14001400
if isa(f, GlobalRef)
1401-
return esc(out_gr)
1401+
return out_gr
14021402
end
14031403

14041404
# f::Symbol
1405-
return esc(:($check ? $out_gr : $out_f))
1405+
return :($check ? $out_gr : $out_f)
14061406
end
14071407

14081408
function destructure_callex(topmod::Module, @nospecialize(ex))

base/show.jl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
# This file is a part of Julia. License is MIT: https://julialang.org/license
22

33
using .Compiler: has_typevar
4-
using .Meta: isidentifier, isoperator, isunaryoperator, isbinaryoperator, ispostfixoperator,
5-
is_id_start_char, is_id_char, _isoperator, is_syntactic_operator, is_valid_identifier
4+
using .Meta: isidentifier, isoperator, isunaryoperator, isbinaryoperator, ispostfixoperator,
5+
is_id_start_char, is_id_char, _isoperator, is_syntactic_operator, is_valid_identifier,
6+
is_unary_and_binary_operator
67

78
function show(io::IO, ::MIME"text/plain", u::UndefInitializer)
89
show(io, u)

base/simdloop.jl

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -60,22 +60,22 @@ function compile(x, ivdep)
6060
check_body!(x)
6161

6262
var,range = parse_iteration_space(x.args[1])
63-
r = gensym("r") # Range value
64-
j = gensym("i") # Iteration variable for outer loop
65-
n = gensym("n") # Trip count for inner loop
66-
i = gensym("i") # Trip index for inner loop
67-
quote
63+
# r: Range value
64+
# j: Iteration variable for outer loop
65+
# n: Trip count for inner loop
66+
# i: Trip index for inner loop
67+
return quote
6868
# Evaluate range value once, to enhance type and data flow analysis by optimizers.
69-
let $r = $range
70-
for $j in Base.simd_outer_range($r)
71-
let $n = Base.simd_inner_length($r,$j)
72-
if zero($n) < $n
69+
let r = $(esc(range))
70+
for j in Base.simd_outer_range(r)
71+
let n = Base.simd_inner_length(r,j)
72+
if zero(n) < n
7373
# Lower loop in way that seems to work best for LLVM 3.3 vectorizer.
74-
let $i = zero($n)
75-
while $i < $n
76-
local $var = Base.simd_index($r,$j,$i)
77-
$(x.args[2]) # Body of loop
78-
$i += 1
74+
let i = zero(n)
75+
while i < n
76+
local $(esc(var)) = Base.simd_index(r,j,i)
77+
$(esc(x.args[2])) # Body of loop
78+
i += 1
7979
$(Expr(:loopinfo, Symbol("julia.simdloop"), ivdep)) # Mark loop as SIMD loop
8080
end
8181
end
@@ -125,12 +125,12 @@ either case, your inner loop should have the following properties to allow vecto
125125
* No iteration ever waits on a previous iteration to make forward progress.
126126
"""
127127
macro simd(forloop)
128-
esc(compile(forloop, nothing))
128+
compile(forloop, nothing)
129129
end
130130

131131
macro simd(ivdep, forloop)
132132
if ivdep === :ivdep
133-
esc(compile(forloop, Symbol("julia.ivdep")))
133+
compile(forloop, Symbol("julia.ivdep"))
134134
else
135135
throw(SimdError("Only ivdep is valid as the first argument to @simd"))
136136
end

base/some.jl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,9 @@ macro something(args...)
152152
which is why we need the last argument first
153153
when building the final expression.
154154
=#
155-
for arg in reverse(args)
156-
val = gensym()
155+
for i in reverse(eachindex(args))
156+
arg = args[i]
157+
val = Cartesian.inlineanonymous(:val, i)
157158
expr = quote
158159
$val = $(esc(arg))
159160
if !isnothing($val)

0 commit comments

Comments
 (0)