Skip to content

Commit 6a12fff

Browse files
authored
Report bad macro names more clearly (#147)
This change prevents a crash for various cases of bad syntax involving macro names. In particular things like `@[x] a` and `@A.x a`. The latter case where the @ is separated from the actual macro name is a pain to deal with and is only approximately correct in this PR. But I'm not sure why we even support this syntax and maybe we could warn about it or deprecate it. So unclear whether going to greater lengths emitting K"TOMBSTONE" to be precise about error reporting in that case is worth it.
1 parent 6d92ef3 commit 6a12fff

File tree

3 files changed

+38
-4
lines changed

3 files changed

+38
-4
lines changed

src/hooks.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,9 @@ function _core_parser_hook(code, filename, lineno, offset, options)
186186
end
187187
ex = options === :all ? Expr(:toplevel, error_ex) : error_ex
188188
else
189+
# FIXME: Unilaterally showing any warnings to stdout here is far
190+
# from ideal. But Meta.parse() has no API for communicating this.
191+
show_diagnostics(stdout, stream.diagnostics, code)
189192
# FIXME: Add support to lineno to this tree build (via SourceFile?)
190193
ex = build_tree(Expr, stream; filename=filename, wrap_toplevel_as_kind=K"None")
191194
if Meta.isexpr(ex, :None)

src/parser.jl

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1546,10 +1546,12 @@ function parse_call_chain(ps::ParseState, mark, is_macrocall=false)
15461546
# f.$x ==> (. f (inert ($ x)))
15471547
# f.$(x+y) ==> (. f (inert ($ (call + x y))))
15481548
# A.$B.@x ==> (macrocall (. (. A (inert ($ B))) (quote @x)))
1549+
# @A.$x a ==> (macrocall (. A (inert (error x))) a)
15491550
m = position(ps)
15501551
bump(ps, TRIVIA_FLAG)
15511552
parse_atom(ps)
15521553
emit(ps, m, K"$")
1554+
macro_name_position = position(ps)
15531555
emit(ps, m, K"inert")
15541556
emit(ps, mark, K".")
15551557
elseif k == K"@"
@@ -2235,10 +2237,31 @@ function fix_macro_name_kind!(ps::ParseState, macro_name_position, name_kind=not
22352237
if k == K"var"
22362238
macro_name_position = first_child_position(ps, macro_name_position)
22372239
k = peek_behind(ps, macro_name_position).kind
2240+
elseif k == K")"
2241+
# @(A) x => (macrocall @A x)
2242+
# TODO: Clean this up when K"parens" is implemented
2243+
while true
2244+
macro_name_position = ParseStreamPosition(macro_name_position.token_index-1,
2245+
macro_name_position.range_index)
2246+
b = peek_behind(ps, macro_name_position)
2247+
k = b.kind
2248+
if !has_flags(b.flags, TRIVIA_FLAG)
2249+
break
2250+
end
2251+
end
2252+
elseif k == K"error"
2253+
# Error already reported in parse_macro_name
2254+
return
22382255
end
22392256
if isnothing(name_kind)
2240-
name_kind = k == K"Identifier" ? K"MacroName" :
2241-
internal_error("unrecognized source kind for macro name ", k)
2257+
name_kind = (k == K"Identifier") ? K"MacroName" : K"error"
2258+
if name_kind == K"error"
2259+
# Hack to handle bad but unusual syntax like `@A.$x a`
2260+
ri = macro_name_position.range_index
2261+
startpos = ParseStreamPosition(ps.stream.ranges[ri].first_token, ri)
2262+
# This isn't quite accurate
2263+
emit_diagnostic(ps, startpos, macro_name_position, error="Invalid macro name")
2264+
end
22422265
end
22432266
reset_node!(ps, macro_name_position, kind=name_kind)
22442267
end
@@ -2253,8 +2276,14 @@ function parse_macro_name(ps::ParseState)
22532276
# @$ x ==> (macrocall @$ x)
22542277
# @var"#" x ==> (macrocall (var #) @$ x)
22552278
bump_disallowed_space(ps)
2256-
let ps = with_space_sensitive(ps)
2257-
parse_atom(ps, false)
2279+
mark = position(ps)
2280+
k = peek(ps)
2281+
parse_atom(ps, false)
2282+
if k == K"("
2283+
emit_diagnostic(ps, mark, warning="parenthesizing macro names is unnecessary")
2284+
elseif !(peek_behind(ps).kind in KSet"Identifier var")
2285+
# @[x] y z ==> (macrocall (error (vect x)) y z)
2286+
emit(ps, mark, K"error", error="Invalid macro name")
22582287
end
22592288
end
22602289

test/parser.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ tests = [
261261
"@! x" => "(macrocall @! x)"
262262
"@.. x" => "(macrocall @.. x)"
263263
"@\$ y" => "(macrocall @\$ y)"
264+
"@[x] y z" => "(macrocall (error (vect x)) y z)"
264265
# Special @doc parsing rules
265266
"@doc x\ny" => "(macrocall @doc x y)"
266267
"A.@doc x\ny" => "(macrocall (. A (quote @doc)) x y)"
@@ -326,6 +327,7 @@ tests = [
326327
"f.\$x" => "(. f (inert (\$ x)))"
327328
"f.\$(x+y)" => "(. f (inert (\$ (call-i x + y))))"
328329
"A.\$B.@x" => "(macrocall (. (. A (inert (\$ B))) (quote @x)))"
330+
"@A.\$x a" => "(macrocall (. A (inert (error x))) a)"
329331
"A.@x" => "(macrocall (. A (quote @x)))"
330332
"A.@x a" => "(macrocall (. A (quote @x)) a)"
331333
"@A.B.@x a" => "(macrocall (. (. A (quote B)) (quote (error-t) @x)) a)"

0 commit comments

Comments
 (0)