Skip to content

Commit 498fd6d

Browse files
committed
Tweak the error recovery for [email protected]
1 parent 02c709b commit 498fd6d

File tree

2 files changed

+48
-25
lines changed

2 files changed

+48
-25
lines changed

src/julia/parser.jl

Lines changed: 47 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1488,6 +1488,13 @@ function parse_unary_prefix(ps::ParseState, has_unary_prefix=false)
14881488
end
14891489
end
14901490

1491+
function maybe_parsed_macro_name(ps, processing_macro_name, mark)
1492+
if processing_macro_name
1493+
emit(ps, mark, K"macro_name")
1494+
end
1495+
return false
1496+
end
1497+
14911498
# Parses a chain of suffixes at function call precedence, leftmost binding
14921499
# tightest. This handles
14931500
# * Bracketed calls like a() b[] c{}
@@ -1506,13 +1513,13 @@ function parse_call_chain(ps::ParseState, mark, is_macrocall=false)
15061513
return
15071514
end
15081515
processing_macro_name = is_macrocall
1516+
saw_misplaced_atsym = false
1517+
misplaced_atsym_mark = nothing
15091518
# source range of the @-prefixed part of a macro
15101519
macro_atname_range = nothing
15111520
# $A.@x ==> (macrocall (. ($ A) (macro_name x)))
15121521
maybe_strmac = true
1513-
# We record the last component of chains of dot-separated identifiers so we
1514-
# know which identifier was the macro name.
1515-
macro_name_position = position(ps) # points to same output span as peek_behind
1522+
last_identifier_orig_kind = peek_behind(ps).orig_kind
15161523
while true
15171524
maybe_strmac_1 = false
15181525
t = peek_token(ps)
@@ -1534,14 +1541,14 @@ function parse_call_chain(ps::ParseState, mark, is_macrocall=false)
15341541
# A.@var"#" a ==> (macrocall (. A (macro_name (var #))) a)
15351542
# @+x y ==> (macrocall (macro_name +) x y)
15361543
# [email protected] ==> (macrocall (. A (macro_name .)) x)
1537-
processing_macro_name && emit(ps, mark, K"macro_name")
1538-
processing_macro_name = false
1544+
processing_macro_name = maybe_parsed_macro_name(
1545+
ps, processing_macro_name, mark)
15391546
let ps = with_space_sensitive(ps)
15401547
# Space separated macro arguments
15411548
# A.@foo a b ==> (macrocall (. A (macro_name foo)) a b)
15421549
# @A.foo a b ==> (macrocall (macro_name (. A foo)) a b)
15431550
n_args = parse_space_separated_exprs(ps)
1544-
is_doc_macro = peek_behind(ps, macro_name_position).orig_kind == K"doc"
1551+
is_doc_macro = last_identifier_orig_kind == K"doc"
15451552
if is_doc_macro && n_args == 1
15461553
# Parse extended @doc args on next line
15471554
# @doc x\ny ==> (macrocall (macro_name doc) x y)
@@ -1568,7 +1575,8 @@ function parse_call_chain(ps::ParseState, mark, is_macrocall=false)
15681575
# f(a; b; c) ==> (call f a (parameters b) (parameters c))
15691576
# (a=1)() ==> (call (parens (= a 1)))
15701577
# f (a) ==> (call f (error-t) a)
1571-
processing_macro_name && emit(ps, mark, K"macro_name")
1578+
processing_macro_name = maybe_parsed_macro_name(
1579+
ps, processing_macro_name, mark)
15721580
processing_macro_name = false
15731581
bump_disallowed_space(ps)
15741582
bump(ps, TRIVIA_FLAG)
@@ -1586,12 +1594,11 @@ function parse_call_chain(ps::ParseState, mark, is_macrocall=false)
15861594
# A.@x(y).z ==> (. (macrocall-p (. A (macro_name x)) y) z)
15871595
is_macrocall = false
15881596
# @f()() ==> (call (macrocall-p (macro_name f)))
1589-
is_macrocall_on_entry = false
15901597
macro_atname_range = nothing
15911598
end
15921599
elseif k == K"["
1593-
processing_macro_name && emit(ps, mark, K"macro_name")
1594-
processing_macro_name = false
1600+
processing_macro_name = maybe_parsed_macro_name(
1601+
ps, processing_macro_name, mark)
15951602
m = position(ps)
15961603
# a [i] ==> (ref a (error-t) i)
15971604
bump_disallowed_space(ps)
@@ -1645,22 +1652,21 @@ function parse_call_chain(ps::ParseState, mark, is_macrocall=false)
16451652
# Allow `@` in macrocall only in first and last position
16461653
# A.B.@x ==> (macrocall (. (. A B) (macro_name x)))
16471654
# @A.B.x ==> (macrocall (macro_name (. (. A B) x)))
1648-
# [email protected] ==> (macrocall (macro_name (. (. A B (error-t))))
1655+
# [email protected] ==> (macrocall (. (. A (error-t) B) (macro_name (error-t) x)))
16491656
emit_diagnostic(ps, macro_atname_range...,
16501657
error="`@` must appear on first or last macro name component")
1651-
bump(ps, TRIVIA_FLAG, error="Unexpected `.` after macro name")
1652-
# Recover by treating the `@` as if it had been on the wole thing
1658+
# Recover by treating the `@` as if it had been on the last identifier
1659+
saw_misplaced_atsym = true
16531660
reset_node!(ps, macro_atname_range[2], kind=K"TOMBSTONE")
1654-
processing_macro_name = true
1655-
else
1656-
bump(ps, TRIVIA_FLAG)
1661+
reset_node!(ps, macro_atname_range[1], kind=K"error")
16571662
end
1663+
bump(ps, TRIVIA_FLAG)
16581664
k = peek(ps)
16591665
if k == K"("
16601666
if is_macrocall
1661-
processing_macro_name && emit(ps, mark, K"macro_name")
16621667
# Recover by pretending we do have the syntax
1663-
processing_macro_name = false
1668+
processing_macro_name = maybe_parsed_macro_name(
1669+
ps, processing_macro_name, mark)
16641670
# @M.(x) ==> (macrocall (dotcall (macro_name M) (error-t) x))
16651671
bump_invisible(ps, K"error", TRIVIA_FLAG)
16661672
emit_diagnostic(ps, mark,
@@ -1694,7 +1700,7 @@ function parse_call_chain(ps::ParseState, mark, is_macrocall=false)
16941700
else
16951701
emit(ps, m, K"$")
16961702
end
1697-
macro_name_position = position(ps)
1703+
last_identifier_orig_kind = K"$"
16981704
emit(ps, mark, K".")
16991705
elseif k == K"@"
17001706
# A macro call after some prefix A has been consumed
@@ -1708,7 +1714,7 @@ function parse_call_chain(ps::ParseState, mark, is_macrocall=false)
17081714
bump(ps, TRIVIA_FLAG)
17091715
end
17101716
parse_macro_name(ps)
1711-
macro_name_position = position(ps)
1717+
last_identifier_orig_kind = peek_behind(ps).orig_kind
17121718
!is_macrocall && emit(ps, m, K"macro_name")
17131719
macro_atname_range = (m, position(ps))
17141720
is_macrocall = true
@@ -1721,10 +1727,27 @@ function parse_call_chain(ps::ParseState, mark, is_macrocall=false)
17211727
error="the .' operator for transpose is discontinued")
17221728
emit(ps, mark, K"dotcall", POSTFIX_OP_FLAG)
17231729
else
1730+
if saw_misplaced_atsym
1731+
# If we saw a misplaced `@` earlier, this might be the place
1732+
# where it should have been. Opportunistically bump the
1733+
# zero-width error token here. If that's not right, we'll
1734+
# reset it later.
1735+
if misplaced_atsym_mark !== nothing
1736+
reset_node!(ps, misplaced_atsym_mark[1], kind=K"TOMBSTONE")
1737+
reset_node!(ps, misplaced_atsym_mark[2], kind=K"TOMBSTONE")
1738+
end
1739+
macro_name_mark = position(ps)
1740+
bump_invisible(ps, K"error", TRIVIA_FLAG)
1741+
aterror_mark = position(ps)
1742+
end
17241743
# Field/property syntax
17251744
# f.x.y ==> (. (. f x) y)
17261745
parse_atom(ps, false)
1727-
macro_name_position = position(ps)
1746+
if saw_misplaced_atsym
1747+
emit(ps, macro_name_mark, K"macro_name")
1748+
misplaced_atsym_mark = (aterror_mark, position(ps))
1749+
end
1750+
last_identifier_orig_kind = peek_behind(ps).orig_kind
17281751
maybe_strmac_1 = true
17291752
emit(ps, mark, K".")
17301753
end
@@ -1734,8 +1757,8 @@ function parse_call_chain(ps::ParseState, mark, is_macrocall=false)
17341757
bump(ps, remap_kind=K"Identifier")
17351758
emit(ps, mark, K"call", POSTFIX_OP_FLAG)
17361759
elseif k == K"{"
1737-
processing_macro_name && emit(ps, mark, K"macro_name")
1738-
processing_macro_name = false
1760+
processing_macro_name = maybe_parsed_macro_name(
1761+
ps, processing_macro_name, mark)
17391762
# Type parameter curlies and macro calls
17401763
m = position(ps)
17411764
# S {a} ==> (curly S (error-t) a)
@@ -1758,7 +1781,7 @@ function parse_call_chain(ps::ParseState, mark, is_macrocall=false)
17581781
elseif k in KSet" \" \"\"\" ` ``` " &&
17591782
!preceding_whitespace(t) && maybe_strmac &&
17601783
(# Must mirror the logic in lex_quote() for consistency
1761-
origk = peek_behind(ps, macro_name_position).orig_kind;
1784+
origk = last_identifier_orig_kind;
17621785
origk == K"Identifier" || is_contextual_keyword(origk) || is_word_operator(origk))
17631786
# Custom string and command literals
17641787
# x"str" ==> (macrocall (macro_name_str x) (string-r "str"))

test/parser.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,7 @@ tests = [
421421
# Allow `@` in macrocall only in first and last position
422422
"A.B.@x" => "(macrocall (. (. A B) (macro_name x)))"
423423
"@A.B.x" => "(macrocall (macro_name (. (. A B) x)))"
424-
"[email protected]" => "(macrocall (macro_name (. (. A B) (error-t) x)))"
424+
"[email protected]" => "(macrocall (. (. A (error-t) B) (macro_name (error-t) x)))"
425425
"@M.(x)" => "(macrocall (dotcall (macro_name M) (error-t) x))"
426426
"f.(a,b)" => "(dotcall f a b)"
427427
"f.(a,b,)" => "(dotcall-, f a b)"

0 commit comments

Comments
 (0)