Skip to content

Commit 2c8a478

Browse files
committed
expr_to_syntaxtree: don't fail when incoming expr is invalid
1 parent ad7d2d4 commit 2c8a478

File tree

4 files changed

+103
-88
lines changed

4 files changed

+103
-88
lines changed

src/compat.jl

Lines changed: 81 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@ end
1111

1212
"""
1313
An Expr -> SyntaxTree transformation that should preserve semantics, but will
14-
have low-quality provenance info (namely, each tree node will be associated with
15-
the last seen LineNumberNode in the pre-order expr traversal).
14+
produce low-quality provenance info (namely, each tree node will be associated
15+
with the last seen LineNumberNode in the pre-order expr traversal).
1616
1717
Last-resort option so that, for example, we can lower the output of old
1818
Expr-producing macros. Always prefer re-parsing source text over using this.
1919
20-
Supports parsed and/or macro-expanded exprs, but not lowered exprs
20+
Supports parsed and/or macro-expanded exprs, but not lowered exprs. Since
21+
macrocall and quote may occur in this tree, we can't throw errors for malformed
22+
syntax; we can only convert known-good exprs that have a SyntaxTree equivalent.
2123
"""
2224
function expr_to_syntaxtree(@nospecialize(e), lnn::Union{LineNumberNode, Nothing}=nothing)
2325
graph = ensure_attributes!(
@@ -46,22 +48,22 @@ end
4648
return out
4749
end
4850

49-
function _expr_replace!(@nospecialize(e), replace_pred::Function, replacer!::Function,
51+
function _expr_replace(@nospecialize(e), replace_pred::Function, replacer::Function,
5052
recurse_pred=(@nospecialize e)->true)
5153
if replace_pred(e)
52-
replacer!(e)
53-
end
54-
if e isa Expr && recurse_pred(e)
55-
for a in e.args
56-
_expr_replace!(a, replace_pred, replacer!, recurse_pred)
57-
end
54+
replacer(e)
55+
elseif e isa Expr && recurse_pred(e)
56+
Expr(e.head, [_expr_replace(a, replace_pred, replacer, recurse_pred) for a in e.args]...)
5857
end
5958
end
6059

6160
function _to_iterspec(exs::Vector, is_generator::Bool)
6261
if length(exs) === 1 && exs[1].head === :filter
63-
@assert length(exs[1].args) >= 2
64-
return Expr(:filter, _to_iterspec(exs[1].args[2:end], true), exs[1].args[1])
62+
return if length(exs[1].args) >= 2
63+
Expr(:filter, _to_iterspec(exs[1].args[2:end], true), exs[1].args[1])
64+
else # invalid
65+
Expr(:filter, exs[1].args[1])
66+
end
6567
end
6668
outex = Expr(:iteration)
6769
for e in exs
@@ -71,7 +73,7 @@ function _to_iterspec(exs::Vector, is_generator::Bool)
7173
end
7274
elseif e.head === :(=)
7375
push!(outex.args, Expr(:in, e.args...))
74-
else
76+
else # invalid. TODO: at least find something that round-trips.
7577
@assert false "unknown iterspec in $e"
7678
end
7779
end
@@ -253,14 +255,14 @@ function _insert_convert_expr(@nospecialize(e), graph::SyntaxGraph, src::SourceA
253255
elseif e.head === :comparison
254256
for i = 2:2:length(child_exprs)
255257
op,op_esc = unwrap_esc(child_exprs[i])
256-
@assert op isa Symbol
257-
op_s = string(op)
258-
if is_dotted_operator(op_s)
259-
child_exprs[i] = Expr(:., op_esc(Symbol(op_s[2:end])))
258+
if op isa Symbol
259+
op_s = string(op)
260+
if is_dotted_operator(op_s)
261+
child_exprs[i] = Expr(:., op_esc(Symbol(op_s[2:end])))
262+
end
260263
end
261264
end
262-
elseif e.head === :macrocall
263-
@assert nargs >= 2
265+
elseif e.head === :macrocall && nargs >= 2
264266
a1,a1_esc = unwrap_esc(e.args[1])
265267
child_exprs = collect_expr_parameters(e, 3)
266268
if child_exprs[2] isa LineNumberNode
@@ -289,8 +291,7 @@ function _insert_convert_expr(@nospecialize(e), graph::SyntaxGraph, src::SourceA
289291
elseif a1.name === Symbol("@big_str")
290292
end
291293
end
292-
elseif e.head === Symbol("'")
293-
@assert nargs === 1
294+
elseif e.head === Symbol("'") && nargs === 1
294295
st_k = K"call"
295296
child_exprs = Any[e.head, e.args[1]]
296297
elseif e.head === :. && nargs === 2
@@ -302,11 +303,9 @@ function _insert_convert_expr(@nospecialize(e), graph::SyntaxGraph, src::SourceA
302303
elseif a2 isa QuoteNode
303304
child_exprs[2] = a2_esc(a2.value)
304305
end
305-
elseif e.head === :for
306-
@assert nargs === 2
306+
elseif e.head === :for && nargs === 2
307307
child_exprs = Any[_to_iterspec(Any[e.args[1]], false), e.args[2]]
308-
elseif e.head === :where
309-
@assert nargs >= 2
308+
elseif e.head === :where && nargs >= 2
310309
e2,_ = unwrap_esc(e.args[2])
311310
if !(e2 isa Expr && e2.head === :braces)
312311
child_exprs = Any[e.args[1], Expr(:braces, e.args[2:end]...)]
@@ -315,7 +314,7 @@ function _insert_convert_expr(@nospecialize(e), graph::SyntaxGraph, src::SourceA
315314
child_exprs = collect_expr_parameters(e, 1)
316315
elseif e.head in (:curly, :ref)
317316
child_exprs = collect_expr_parameters(e, 2)
318-
elseif e.head === :try
317+
elseif e.head === :try && nargs >= 3
319318
child_exprs = Any[e.args[1]]
320319
# Expr:
321320
# (try (block ...) var (block ...) [block ...] [block ...])
@@ -343,22 +342,21 @@ function _insert_convert_expr(@nospecialize(e), graph::SyntaxGraph, src::SourceA
343342
st_k = K"generator"
344343
child_exprs = Any[]
345344
next = e
346-
while next.head === :flatten
347-
@assert next.args[1].head === :generator
345+
while next.head === :flatten && length(next.args) >= 1 && next.args[1].head === :generator
348346
push!(child_exprs, _to_iterspec(next.args[1].args[2:end], true))
349347
next = next.args[1].args[1]
350348
end
351-
@assert next.head === :generator
352-
push!(child_exprs, _to_iterspec(next.args[2:end], true))
353-
pushfirst!(child_exprs, next.args[1])
349+
if next.head === :generator
350+
push!(child_exprs, _to_iterspec(next.args[2:end], true))
351+
pushfirst!(child_exprs, next.args[1])
352+
end
354353
elseif e.head === :ncat || e.head === :nrow
355354
dim = unwrap_esc_(popfirst!(child_exprs))
356355
st_flags |= JS.set_numeric_flags(dim)
357356
elseif e.head === :typed_ncat
358357
st_flags |= JS.set_numeric_flags(unwrap_esc_(e.args[2]))
359358
deleteat!(child_exprs, 2)
360-
elseif e.head === :(->)
361-
@assert nargs === 2
359+
elseif e.head === :(->) && nargs === 2
362360
a1, a1_esc = unwrap_esc(e.args[1])
363361
if a1 isa Expr && a1.head === :block
364362
# Expr parsing fails to make :parameters here...
@@ -397,13 +395,12 @@ function _insert_convert_expr(@nospecialize(e), graph::SyntaxGraph, src::SourceA
397395
src = maybe_extract_lnn(e.args[2], src)
398396
child_exprs[2] = maybe_unwrap_arg(e.args[2])
399397
end
400-
elseif e.head === :module
401-
@assert nargs === 3
402-
if !e.args[1]
398+
elseif e.head === :module && nargs === 3
399+
if e.args[1] === false
403400
st_flags |= JS.BARE_MODULE_FLAG
404401
end
405402
child_exprs = Any[e.args[2], e.args[3]]
406-
elseif e.head === :do
403+
elseif e.head === :do && nargs === 2
407404
# Expr:
408405
# (do (call f args...) (-> (tuple lam_args...) (block ...)))
409406
# SyntaxTree:
@@ -421,21 +418,19 @@ function _insert_convert_expr(@nospecialize(e), graph::SyntaxGraph, src::SourceA
421418
st_k = K"call"
422419
end
423420
child_exprs = Any[callargs..., Expr(:do_lambda, e.args[2].args...)]
424-
elseif e.head === :let
425-
if nargs >= 1
426-
a1,_ = unwrap_esc(e.args[1])
427-
if !(a1 isa Expr && a1.head === :block)
428-
child_exprs[1] = Expr(:block, e.args[1])
429-
end
421+
elseif e.head === :let && nargs >= 1
422+
a1,_ = unwrap_esc(e.args[1])
423+
if !(a1 isa Expr && a1.head === :block)
424+
child_exprs[1] = Expr(:block, e.args[1])
430425
end
431-
elseif e.head === :struct
426+
elseif e.head === :struct && nargs >= 1
432427
e.args[1] && (st_flags |= JS.MUTABLE_FLAG)
433428
child_exprs = child_exprs[2:end]
434429
# TODO handle docstrings after refactor
435430
elseif (e.head === :using || e.head === :import)
436-
_expr_replace!(e,
437-
(e)->(e isa Expr && e.head === :.),
438-
(e)->(e.head = :importpath))
431+
e2 = _expr_replace(e, (e)->(e isa Expr && e.head === :.),
432+
(e)->Expr(:importpath, e.args...))
433+
child_exprs = e2.args
439434
elseif e.head === :kw
440435
st_k = K"="
441436
elseif e.head in (:local, :global) && nargs > 1
@@ -463,36 +458,37 @@ function _insert_convert_expr(@nospecialize(e), graph::SyntaxGraph, src::SourceA
463458
if e.args[1] isa Expr && e.args[1].head === :purity
464459
st_k = K"meta"
465460
child_exprs = [Expr(:quoted_symbol, :purity), Base.EffectsOverride(e.args[1].args...)]
466-
else
467-
@assert e.args[1] isa Symbol
468-
if e.args[1] === :nospecialize
469-
if nargs > 2
470-
st_k = K"block"
471-
# Kick the can down the road (should only be simple atoms?)
472-
child_exprs = map(c->Expr(:meta, :nospecialize, c), child_exprs[2:end])
473-
else
474-
st_id, src = _insert_convert_expr(e.args[2], graph, src)
475-
setmeta!(SyntaxTree(graph, st_id); nospecialize=true)
476-
return st_id, src
477-
end
478-
elseif e.args[1] in (:inline, :noinline, :generated, :generated_only,
479-
:max_methods, :optlevel, :toplevel, :push_loc, :pop_loc,
480-
:no_constprop, :aggressive_constprop, :specialize, :compile, :infer,
481-
:nospecializeinfer, :force_compile, :propagate_inbounds, :doc)
482-
# TODO: Some need to be handled in lowering
483-
for (i, ma) in enumerate(e.args)
484-
if ma isa Symbol
485-
# @propagate_inbounds becomes (meta inline
486-
# propagate_inbounds), but usually(?) only args[1] is
487-
# converted here
488-
child_exprs[i] = Expr(:quoted_symbol, e.args[i])
489-
end
490-
end
461+
elseif nargs === 0
462+
# pass
463+
elseif e.args[1] === :nospecialize
464+
if nargs > 2
465+
st_k = K"block"
466+
# Kick the can down the road (should only be simple atoms?)
467+
child_exprs = map(c->Expr(:meta, :nospecialize, c), child_exprs[2:end])
491468
else
492-
# Can't throw a hard error; it is explicitly tested that meta can take arbitrary keys.
493-
@error("Unknown meta form at $src: `$e`\n$(sprint(dump, e))")
494-
child_exprs[1] = Expr(:quoted_symbol, e.args[1])
469+
st_id, src = _insert_convert_expr(e.args[2], graph, src)
470+
setmeta!(SyntaxTree(graph, st_id); nospecialize=true)
471+
return st_id, src
495472
end
473+
elseif e.args[1] in (:inline, :noinline, :generated, :generated_only,
474+
:max_methods, :optlevel, :toplevel, :push_loc, :pop_loc,
475+
:no_constprop, :aggressive_constprop, :specialize, :compile, :infer,
476+
:nospecializeinfer, :force_compile, :propagate_inbounds, :doc)
477+
# TODO: Some need to be handled in lowering
478+
for (i, ma) in enumerate(e.args)
479+
if ma isa Symbol
480+
# @propagate_inbounds becomes (meta inline
481+
# propagate_inbounds), but usually(?) only args[1] is
482+
# converted here
483+
child_exprs[i] = Expr(:quoted_symbol, e.args[i])
484+
end
485+
end
486+
else
487+
# Can't throw a hard error; it is explicitly tested that meta can
488+
# take arbitrary keys.
489+
@error("Unknown meta form at $src: `$e`\n$(sprint(dump, e))")
490+
st_k = K"meta"
491+
child_exprs[1] = Expr(:quoted_symbol, e.args[1])
496492
end
497493
elseif e.head === :scope_layer
498494
@assert nargs === 2
@@ -501,27 +497,22 @@ function _insert_convert_expr(@nospecialize(e), graph::SyntaxGraph, src::SourceA
501497
st_id, src = _insert_convert_expr(e.args[1], graph, src)
502498
setattr!(graph, st_id, scope_layer=e.args[2])
503499
return st_id, src
504-
elseif e.head === :symbolicgoto || e.head === :symboliclabel
505-
@assert nargs === 1
500+
elseif e.head === :symbolicgoto || e.head === :symboliclabel && nargs === 1
506501
st_k = e.head === :symbolicgoto ? K"symbolic_label" : K"symbolic_goto"
507502
st_attrs[:name_val] = string(e.args[1])
508503
child_exprs = nothing
509-
elseif e.head in (:inline, :noinline)
510-
@assert nargs === 1 && e.args[1] isa Bool
504+
elseif e.head in (:inline, :noinline) && nargs === 1 && e.args[1] isa Bool
511505
# TODO: JuliaLowering doesn't accept this (non-:meta) form yet
512506
st_k = K"TOMBSTONE"
513507
child_exprs = nothing
514-
elseif e.head === :inbounds
515-
@assert nargs === 1 && typeof(e.args[1]) in (Symbol, Bool)
508+
elseif e.head === :inbounds && nargs === 1 && typeof(e.args[1]) in (Symbol, Bool)
516509
# TODO: JuliaLowering doesn't accept this form yet
517510
st_k = K"TOMBSTONE"
518511
child_exprs = nothing
519-
elseif e.head === :core
520-
@assert nargs === 1
521-
@assert e.args[1] isa Symbol
512+
elseif e.head === :core && nargs === 1 && e.args[1] isa Symbol
522513
st_attrs[:name_val] = string(e.args[1])
523514
child_exprs = nothing
524-
elseif e.head === :islocal || e.head === :isglobal
515+
elseif (e.head === :islocal || e.head === :isglobal) && nargs === 1
525516
st_k = K"extension"
526517
child_exprs = [Expr(:quoted_symbol, e.head), e.args[1]]
527518
elseif e.head === :block && nargs >= 1 &&
@@ -563,10 +554,12 @@ function _insert_convert_expr(@nospecialize(e), graph::SyntaxGraph, src::SourceA
563554
end
564555

565556
#---------------------------------------------------------------------------
566-
# Throw if this function isn't complete. Finally, insert a new node into the
567-
# graph and recurse on child_exprs
557+
# Omit tombstones. Unrecognized expr heads become K"expr_syntax". Finally,
558+
# insert a new node into the graph and recurse on child_exprs
568559
if st_k === K"None"
569-
error("Unknown expr head at $src: `$(e.head)`\n$(sprint(dump, e))")
560+
st_k = K"expr_syntax"
561+
st_attrs[:value] = e
562+
child_exprs = nothing
570563
elseif st_k === K"TOMBSTONE"
571564
return nothing, src
572565
end

src/kinds.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,11 @@ function _register_kinds()
133133
"constdecl"
134134
# Returned from statements that should error if the result is used.
135135
"unused_only"
136+
# Leaf kind denoting unquoted AST where `.value` is an Expr. Only
137+
# used for compatibility with old-style macros called with arguments
138+
# that have no other representation in SyntaxTree (e.g. head mapping
139+
# to no kind, interpolations in strange places, etc).
140+
"expr_syntax"
136141
"END_LOWERING_KINDS"
137142

138143
# The following kinds are emitted by lowering and used in Julia's untyped IR

src/macro_expansion.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,10 @@ function expand_forms_1(ctx::MacroExpansionContext, ex::SyntaxTree)
517517
(expand_forms_1(ctx, a) for a in args)...
518518
]
519519
end
520+
elseif k === K"expr_syntax"
521+
throw(LoweringError(ex, "malformed Expr outside of macrocall or quote: \
522+
check for macros producing unlowerable forms, \
523+
or missing cases in `expr_to_syntaxtree`"))
520524
elseif is_leaf(ex)
521525
ex
522526
elseif k == K"<:" || k == K">:" || k == K"-->"

test/macros.jl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,19 @@ end
360360
"""; expr_compat_mode=true)
361361
@test test_mod.SOME_ENUM <: Enum
362362
@test test_mod.X1 isa Enum
363+
364+
# Completely unknown expr heads are OK to produce from macro expansions as
365+
# long as another macro cleans it up
366+
Base.include_string(test_mod, """
367+
macro unbungle(x)
368+
:(1)
369+
end
370+
371+
macro bungle()
372+
esc(Expr(:macrocall, :var"@unbungle", @__LINE__, Expr(Symbol("what???"), 1, 2, 3)))
373+
end""")
374+
375+
@test JuliaLowering.include_string(test_mod, "@bungle()"; expr_compat_mode=true) === 1
363376
end
364377

365378
@testset "macros producing meta forms" begin

0 commit comments

Comments
 (0)