Skip to content

Commit 858e324

Browse files
authored
Fixes for escape handling in Expr->SyntaxTree conversion (#62)
The manual `esc()` system allows macros to emit `Expr(:escape)` in almost any location within the AST so we generally need to unwrap and rewrap such escapes when pattern matching the incoming `Expr` tree during conversion to `SyntaxTree`. Also apply the same treatment to `Expr(:hygienic-scope)` in case sombody passes us code from `macroexpand()` or explicitly uses this form. Also fix some other issues found during testing of these changes: * `finally` block shouldn't be added if it's set to `false` in the Expr form * The test implementation of `≈` for ASTs was very buggy due to a previous refactor. Fix this. * Expr form allows any callable object as the macro name, so we can't really reject much here - remove the test for that. * Do not format Expr to string as part of conversion - this could be expensive and also fails in Base in some strange cases of esc() placement (eg, ncat dim argument) * Filter out `K"TOMBSTONE"` inside `expr_to_syntaxtree()` because this can end up in value position which is an error later in linear IR generation. I tacked on a change to convert `@ doc` -> `K"doc"` here as well so that we can use the improved documentation lowering rather than the doc macro's partial reimplementation of lowering.
1 parent c3b5b5f commit 858e324

File tree

4 files changed

+336
-66
lines changed

4 files changed

+336
-66
lines changed

src/ast.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,8 @@ end
142142
function makeleaf(ctx, srcref, k::Kind, value; kws...)
143143
graph = syntax_graph(ctx)
144144
if k == K"Identifier" || k == K"core" || k == K"top" || k == K"Symbol" ||
145-
k == K"globalref" || k == K"Placeholder"
145+
k == K"globalref" || k == K"Placeholder" || k == K"MacroName" ||
146+
k == "StringMacroName" || k == K"CmdMacroName"
146147
makeleaf(graph, srcref, k; name_val=value, kws...)
147148
elseif k == K"BindingId"
148149
makeleaf(graph, srcref, k; var_id=value, kws...)

src/compat.jl

Lines changed: 97 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ function expr_to_syntaxtree(ctx, @nospecialize(e), lnn::Union{LineNumberNode, No
3434
toplevel_src = if isnothing(lnn)
3535
# Provenance sinkhole for all nodes until we hit a linenode
3636
dummy_src = SourceRef(
37-
SourceFile("No source for expression: $e"),
37+
SourceFile("No source for expression"),
3838
1, JS.GreenNode(K"None", 0))
3939
_insert_tree_node(graph, K"None", dummy_src)
4040
else
@@ -91,29 +91,34 @@ function collect_expr_parameters(e::Expr, pos::Int)
9191
args = Any[e.args[1:pos-1]..., e.args[pos+1:end]...]
9292
return _flatten_params!(args, params)
9393
end
94-
function _flatten_params!(out::Vector{Any}, p::Expr)
94+
function _flatten_params!(out::Vector{Any}, params::Expr)
95+
p,p_esc = unwrap_esc(params)
9596
p1 = expr_parameters(p, 1)
9697
if !isnothing(p1)
97-
push!(out, Expr(:parameters, p.args[2:end]...))
98-
_flatten_params!(out, p1)
98+
push!(out, p_esc(Expr(:parameters, p.args[2:end]...)))
99+
_flatten_params!(out, p_esc(p1))
99100
else
100-
push!(out, p::Any)
101+
push!(out, params::Any)
101102
end
102103
return out
103104
end
104105
function expr_parameters(p::Expr, pos::Int)
105-
if length(p.args) >= pos &&
106-
p.args[pos] isa Expr &&
107-
p.args[pos].head === :parameters
108-
return p.args[pos]
106+
if pos <= length(p.args)
107+
e,_ = unwrap_esc(p.args[pos])
108+
if e isa Expr && e.head === :parameters
109+
return p.args[pos]
110+
end
109111
end
110112
return nothing
111113
end
112114

113115
"""
114116
If `b` (usually a block) has exactly one non-LineNumberNode argument, unwrap it.
115117
"""
116-
function maybe_unwrap_arg(b::Expr)
118+
function maybe_unwrap_arg(b)
119+
if !(b isa Expr)
120+
return b
121+
end
117122
e1 = findfirst(c -> !isa(c, LineNumberNode), b.args)
118123
isnothing(e1) && return b
119124
e2 = findfirst(c -> !isa(c, LineNumberNode), b.args[e1+1:end])
@@ -122,7 +127,7 @@ function maybe_unwrap_arg(b::Expr)
122127
end
123128

124129
function maybe_extract_lnn(b, default)
125-
!(b isa Expr) && return b
130+
!(b isa Expr) && return default
126131
lnn_i = findfirst(a->isa(a, LineNumberNode), b.args)
127132
return isnothing(lnn_i) ? default : b.args[lnn_i]
128133
end
@@ -141,7 +146,33 @@ end
141146

142147
function is_eventually_call(e)
143148
return e isa Expr && (e.head === :call ||
144-
e.head in (:where, :(::)) && is_eventually_call(e.args[1]))
149+
e.head in (:escape, :where, :(::)) && is_eventually_call(e.args[1]))
150+
end
151+
152+
function rewrap_escapes(hyg, ex)
153+
if hyg isa Expr && hyg.head in (:escape, :var"hygienic-scope")
154+
ex = Expr(hyg.head, rewrap_escapes(hyg.args[1], ex))
155+
if hyg.head === :var"hygienic-scope"
156+
append!(ex.args, @view hyg.args[2:end])
157+
end
158+
end
159+
return ex
160+
end
161+
162+
# Unwrap Expr(:escape) and Expr(:hygienic-scope). Return the unwrapped
163+
# expression and a function which will rewrap a derived expression in the
164+
# correct hygiene wrapper.
165+
function unwrap_esc(ex)
166+
orig_ex = ex
167+
while ex isa Expr && ex.head in (:escape, :var"hygienic-scope")
168+
@assert length(ex.args) >= 1
169+
ex = ex.args[1]
170+
end
171+
return ex, e->rewrap_escapes(orig_ex, e)
172+
end
173+
174+
function unwrap_esc_(e)
175+
unwrap_esc(e)[1]
145176
end
146177

147178
"""
@@ -212,59 +243,61 @@ function _insert_convert_expr(@nospecialize(e), graph::SyntaxGraph, src::SourceA
212243
child_exprs = Any[e.args[1], Symbol(op), e.args[2]]
213244
elseif e.head === :comparison
214245
for i = 2:2:length(child_exprs)
215-
op = child_exprs[i]
246+
op,op_esc = unwrap_esc(child_exprs[i])
216247
@assert op isa Symbol
217248
op_s = string(op)
218249
if is_dotted_operator(op_s)
219-
child_exprs[i] = Expr(:., Symbol(op_s[2:end]))
250+
child_exprs[i] = Expr(:., op_esc(Symbol(op_s[2:end])))
220251
end
221252
end
222253
elseif e.head === :macrocall
223254
@assert nargs >= 2
224-
a1 = e.args[1]
255+
a1,a1_esc = unwrap_esc(e.args[1])
225256
child_exprs = collect_expr_parameters(e, 3)
226257
if child_exprs[2] isa LineNumberNode
227258
src = child_exprs[2]
228259
end
229260
deleteat!(child_exprs, 2)
230261
if a1 isa Symbol
231-
child_exprs[1] = Expr(:MacroName, a1)
232-
elseif a1 isa Expr && a1.head === :(.) && a1.args[2] isa QuoteNode
233-
child_exprs[1] = Expr(:(.), a1.args[1], Expr(:MacroName, a1.args[2].value))
234-
elseif a1 isa GlobalRef
262+
child_exprs[1] = a1_esc(Expr(:MacroName, a1))
263+
elseif a1 isa Expr && a1.head === :(.)
264+
a12,a12_esc = unwrap_esc(a1.args[2])
265+
if a12 isa QuoteNode
266+
child_exprs[1] = a1_esc(Expr(:(.), a1.args[1],
267+
Expr(:MacroName, a12_esc(a12.value))))
268+
end
269+
elseif a1 isa GlobalRef && a1.mod === Core
235270
# TODO (maybe): syntax-introduced macrocalls are listed here for
236271
# reference. We probably don't need to convert these.
237272
if a1.name === Symbol("@cmd")
238273
elseif a1.name === Symbol("@doc")
274+
st_k = K"doc"
275+
child_exprs = child_exprs[2:end]
239276
elseif a1.name === Symbol("@int128_str")
240277
elseif a1.name === Symbol("@int128_str")
241278
elseif a1.name === Symbol("@big_str")
242279
end
243-
elseif a1 isa Function
244-
# pass
245-
else
246-
error("Unknown macrocall form at $src: $(sprint(dump, e))")
247-
@assert false
248280
end
249281
elseif e.head === Symbol("'")
250282
@assert nargs === 1
251283
st_k = K"call"
252284
child_exprs = Any[e.head, e.args[1]]
253285
elseif e.head === :. && nargs === 2
254-
a2 = e.args[2]
286+
a2, a2_esc = unwrap_esc(e.args[2])
255287
if a2 isa Expr && a2.head === :tuple
256288
st_k = K"dotcall"
257-
tuple_exprs = collect_expr_parameters(a2, 1)
289+
tuple_exprs = collect_expr_parameters(a2_esc(a2), 1)
258290
child_exprs = pushfirst!(tuple_exprs, e.args[1])
259291
elseif a2 isa QuoteNode
260-
child_exprs[2] = a2.value
292+
child_exprs[2] = a2_esc(a2.value)
261293
end
262294
elseif e.head === :for
263295
@assert nargs === 2
264296
child_exprs = Any[_to_iterspec(Any[e.args[1]], false), e.args[2]]
265297
elseif e.head === :where
266298
@assert nargs >= 2
267-
if !(e.args[2] isa Expr && e.args[2].head === :braces)
299+
e2,_ = unwrap_esc(e.args[2])
300+
if !(e2 isa Expr && e2.head === :braces)
268301
child_exprs = Any[e.args[1], Expr(:braces, e.args[2:end]...)]
269302
end
270303
elseif e.head in (:tuple, :vect, :braces)
@@ -281,18 +314,19 @@ function _insert_convert_expr(@nospecialize(e), graph::SyntaxGraph, src::SourceA
281314
# [catch var (block ...)]
282315
# [else (block ...)]
283316
# [finally (block ...)])
284-
if e.args[2] != false || e.args[3] != false
317+
e2 = unwrap_esc_(e.args[2])
318+
e3 = unwrap_esc_(e.args[3])
319+
if e2 !== false || e3 !== false
285320
push!(child_exprs,
286321
Expr(:catch,
287-
e.args[2] === false ? Expr(:catch_var_placeholder) : e.args[2],
288-
e.args[3] === false ? nothing : e.args[3]))
322+
e2 === false ? Expr(:catch_var_placeholder) : e.args[2],
323+
e3 === false ? nothing : e.args[3]))
289324
end
290325
if nargs >= 5
291326
push!(child_exprs, Expr(:else, e.args[5]))
292327
end
293-
if nargs >= 4
294-
push!(child_exprs,
295-
Expr(:finally, e.args[4] === false ? nothing : e.args[4]))
328+
if nargs >= 4 && unwrap_esc_(e.args[4]) !== false
329+
push!(child_exprs, Expr(:finally, e.args[4]))
296330
end
297331
elseif e.head === :flatten || e.head === :generator
298332
st_k = K"generator"
@@ -307,36 +341,37 @@ function _insert_convert_expr(@nospecialize(e), graph::SyntaxGraph, src::SourceA
307341
push!(child_exprs, _to_iterspec(next.args[2:end], true))
308342
pushfirst!(child_exprs, next.args[1])
309343
elseif e.head === :ncat || e.head === :nrow
310-
dim = popfirst!(child_exprs)
344+
dim = unwrap_esc_(popfirst!(child_exprs))
311345
st_flags |= JS.set_numeric_flags(dim)
312346
elseif e.head === :typed_ncat
313-
st_flags |= JS.set_numeric_flags(e.args[2])
347+
st_flags |= JS.set_numeric_flags(unwrap_esc_(e.args[2]))
314348
deleteat!(child_exprs, 2)
315349
elseif e.head === :(->)
316350
@assert nargs === 2
317-
if e.args[1] isa Expr && e.args[1].head === :block
351+
a1, a1_esc = unwrap_esc(e.args[1])
352+
if a1 isa Expr && a1.head === :block
318353
# Expr parsing fails to make :parameters here...
319354
lam_args = Any[]
320355
lam_eqs = Any[]
321-
for a in e.args[1].args
356+
for a in a1.args
322357
a isa Expr && a.head === :(=) ? push!(lam_eqs, a) : push!(lam_args, a)
323358
end
324359
!isempty(lam_eqs) && push!(lam_args, Expr(:parameters, lam_eqs...))
325-
child_exprs[1] = Expr(:tuple, lam_args...)
326-
elseif !(e.args[1] isa Expr && (e.args[1].head in (:tuple, :where)))
327-
child_exprs[1] = Expr(:tuple, e.args[1])
360+
child_exprs[1] = a1_esc(Expr(:tuple, lam_args...))
361+
elseif !(a1 isa Expr && (a1.head in (:tuple, :where)))
362+
child_exprs[1] = a1_esc(Expr(:tuple, a1))
328363
end
329364
src = maybe_extract_lnn(e.args[2], src)
330365
child_exprs[2] = maybe_unwrap_arg(e.args[2])
331366
elseif e.head === :call
332367
child_exprs = collect_expr_parameters(e, 2)
333-
a1 = child_exprs[1]
368+
a1,a1_esc = unwrap_esc(child_exprs[1])
334369
if a1 isa Symbol
335370
a1s = string(a1)
336371
if is_dotted_operator(a1s)
337372
# non-assigning dotop like .+ or .==
338373
st_k = K"dotcall"
339-
child_exprs[1] = Symbol(a1s[2:end])
374+
child_exprs[1] = a1_esc(Symbol(a1s[2:end]))
340375
end
341376
end
342377
elseif e.head === :function
@@ -362,17 +397,20 @@ function _insert_convert_expr(@nospecialize(e), graph::SyntaxGraph, src::SourceA
362397
# SyntaxTree:
363398
# (call f args... (do (tuple lam_args...) (block ...)))
364399
callargs = collect_expr_parameters(e.args[1], 2)
365-
fname = string(callargs[1])
366400
if e.args[1].head === :macrocall
367401
st_k = K"macrocall"
368-
callargs[1] = Expr(:MacroName, callargs[1])
402+
c1,c1_esc = unwrap_esc(callargs[1])
403+
callargs[1] = c1_esc(Expr(:MacroName, c1))
369404
else
370405
st_k = K"call"
371406
end
372407
child_exprs = Any[callargs..., Expr(:do_lambda, e.args[2].args...)]
373408
elseif e.head === :let
374-
if nargs >= 1 && !(e.args[1] isa Expr && e.args[1].head === :block)
375-
child_exprs[1] = Expr(:block, e.args[1])
409+
if nargs >= 1
410+
a1,_ = unwrap_esc(e.args[1])
411+
if !(a1 isa Expr && a1.head === :block)
412+
child_exprs[1] = Expr(:block, e.args[1])
413+
end
376414
end
377415
elseif e.head === :struct
378416
e.args[1] && (st_flags |= JS.MUTABLE_FLAG)
@@ -404,6 +442,12 @@ function _insert_convert_expr(@nospecialize(e), graph::SyntaxGraph, src::SourceA
404442
st_k = K"latestworld_if_toplevel"
405443
elseif e.head === Symbol("hygienic-scope")
406444
st_k = K"hygienic_scope"
445+
elseif e.head === :escape
446+
if length(e.args) == 1 && unwrap_esc_(e.args[1]) isa LineNumberNode
447+
# escape containing only a LineNumberNode will become empty and
448+
# thus must be removed before lowering sees it.
449+
st_k = K"TOMBSTONE"
450+
end
407451
elseif e.head === :meta
408452
# Messy and undocumented. Only sometimes we want a K"meta".
409453
@assert e.args[1] isa Symbol
@@ -490,10 +534,12 @@ function _insert_convert_expr(@nospecialize(e), graph::SyntaxGraph, src::SourceA
490534
end
491535

492536
#---------------------------------------------------------------------------
493-
# Throw if this script isn't complete. Finally, insert a new node into the
537+
# Throw if this function isn't complete. Finally, insert a new node into the
494538
# graph and recurse on child_exprs
495539
if st_k === K"None"
496540
error("Unknown expr head at $src: `$(e.head)`\n$(sprint(dump, e))")
541+
elseif st_k === K"TOMBSTONE"
542+
return nothing, src
497543
end
498544

499545
st_id = _insert_tree_node(graph, st_k, src, st_flags; st_attrs...)
@@ -503,7 +549,6 @@ function _insert_convert_expr(@nospecialize(e), graph::SyntaxGraph, src::SourceA
503549
if isnothing(child_exprs)
504550
return st_id, src
505551
else
506-
setflags!(graph, st_id, st_flags)
507552
st_child_ids, last_src = _insert_child_exprs(child_exprs, graph, src)
508553
setchildren!(graph, st_id, st_child_ids)
509554
return st_id, last_src
@@ -519,7 +564,9 @@ function _insert_child_exprs(child_exprs::Vector{Any}, graph::SyntaxGraph,
519564
last_src = c
520565
else
521566
(c_id, c_src) = _insert_convert_expr(c, graph, last_src)
522-
push!(st_child_ids, c_id)
567+
if !isnothing(c_id)
568+
push!(st_child_ids, c_id)
569+
end
523570
last_src = something(c_src, src)
524571
end
525572
end

0 commit comments

Comments
 (0)