Unnamed repository; edit this file 'description' to name the repository.
Merge pull request #18608 from Veykril/push-rrozmpnyknkx
Better parser recovery for paths
Lukas Wirth 2024-12-04
parent 308f5e2 · parent 83f5150 · commit ab652f7
-rw-r--r--crates/hir-def/src/macro_expansion_tests/mbe.rs5
-rw-r--r--crates/parser/src/grammar.rs4
-rw-r--r--crates/parser/src/grammar/attributes.rs10
-rw-r--r--crates/parser/src/grammar/generic_args.rs4
-rw-r--r--crates/parser/src/grammar/paths.rs40
-rw-r--r--crates/parser/src/parser.rs19
-rw-r--r--crates/parser/test_data/generated/runner.rs2
-rw-r--r--crates/parser/test_data/parser/err/0004_use_path_bad_segment.rast5
-rw-r--r--crates/parser/test_data/parser/err/0048_double_fish.rast10
-rw-r--r--crates/parser/test_data/parser/inline/err/crate_visibility_empty_recover.rast6
-rw-r--r--crates/parser/test_data/parser/inline/err/meta_recovery.rast83
-rw-r--r--crates/parser/test_data/parser/inline/err/meta_recovery.rs6
12 files changed, 154 insertions, 40 deletions
diff --git a/crates/hir-def/src/macro_expansion_tests/mbe.rs b/crates/hir-def/src/macro_expansion_tests/mbe.rs
index d568f6faa7..5c03fad613 100644
--- a/crates/hir-def/src/macro_expansion_tests/mbe.rs
+++ b/crates/hir-def/src/macro_expansion_tests/mbe.rs
@@ -1759,8 +1759,9 @@ fn f() {
diff --git a/crates/parser/src/grammar.rs b/crates/parser/src/grammar.rs
index a50a2182a7..c402c49855 100644
--- a/crates/parser/src/grammar.rs
+++ b/crates/parser/src/grammar.rs
@@ -242,7 +242,7 @@ fn opt_visibility(p: &mut Parser<'_>, in_tuple_field: bool) -> bool {
// struct MyStruct(pub ());
if !(in_tuple_field && matches!(p.nth(1), T![ident] | T![')'])) {
p.bump(T!['(']);
- paths::use_path(p);
+ paths::vis_path(p);
p.expect(T![')']);
}
}
@@ -252,7 +252,7 @@ fn opt_visibility(p: &mut Parser<'_>, in_tuple_field: bool) -> bool {
T![in] => {
p.bump(T!['(']);
p.bump(T![in]);
- paths::use_path(p);
+ paths::vis_path(p);
p.expect(T![')']);
}
_ => {}
diff --git a/crates/parser/src/grammar/attributes.rs b/crates/parser/src/grammar/attributes.rs
index 82e4d66148..ccb556b2cc 100644
--- a/crates/parser/src/grammar/attributes.rs
+++ b/crates/parser/src/grammar/attributes.rs
@@ -36,6 +36,14 @@ fn attr(p: &mut Parser<'_>, inner: bool) {
attr.complete(p, ATTR);
}
+// test_err meta_recovery
+// #![]
+// #![p = ]
+// #![p::]
+// #![p:: =]
+// #![unsafe]
+// #![unsafe =]
+
// test metas
// #![simple_ident]
// #![simple::path]
@@ -63,7 +71,7 @@ pub(super) fn meta(p: &mut Parser<'_>) {
if is_unsafe {
p.expect(T!['(']);
}
- paths::use_path(p);
+ paths::attr_path(p);
match p.current() {
T![=] => {
diff --git a/crates/parser/src/grammar/generic_args.rs b/crates/parser/src/grammar/generic_args.rs
index c62c8a9d3f..77379ef147 100644
--- a/crates/parser/src/grammar/generic_args.rs
+++ b/crates/parser/src/grammar/generic_args.rs
@@ -168,10 +168,10 @@ pub(super) fn const_arg_expr(p: &mut Parser<'_>) {
expressions::literal(p);
lm.complete(p, PREFIX_EXPR);
}
- _ if paths::is_use_path_start(p) => {
+ _ if paths::is_path_start(p) => {
// This shouldn't be hit by `const_arg`
let lm = p.start();
- paths::use_path(p);
+ paths::expr_path(p);
lm.complete(p, PATH_EXPR);
}
_ => {
diff --git a/crates/parser/src/grammar/paths.rs b/crates/parser/src/grammar/paths.rs
index 09db921803..15b3529642 100644
--- a/crates/parser/src/grammar/paths.rs
+++ b/crates/parser/src/grammar/paths.rs
@@ -19,6 +19,14 @@ pub(super) fn use_path(p: &mut Parser<'_>) {
path(p, Mode::Use);
}
+pub(super) fn vis_path(p: &mut Parser<'_>) {
+ path(p, Mode::Vis);
+}
+
+pub(super) fn attr_path(p: &mut Parser<'_>) {
+ path(p, Mode::Attr);
+}
+
pub(crate) fn type_path(p: &mut Parser<'_>) {
path(p, Mode::Type);
}
@@ -37,15 +45,20 @@ pub(crate) fn type_path_for_qualifier(
#[derive(Clone, Copy, Eq, PartialEq)]
enum Mode {
Use,
+ Attr,
Type,
Expr,
+ Vis,
}
-fn path(p: &mut Parser<'_>, mode: Mode) {
+fn path(p: &mut Parser<'_>, mode: Mode) -> Option<CompletedMarker> {
let path = p.start();
- path_segment(p, mode, true);
+ if path_segment(p, mode, true).is_none() {
+ path.abandon(p);
+ return None;
+ }
let qual = path.complete(p, PATH);
- path_for_qualifier(p, mode, qual);
+ Some(path_for_qualifier(p, mode, qual))
}
fn path_for_qualifier(
@@ -71,7 +84,7 @@ const EXPR_PATH_SEGMENT_RECOVERY_SET: TokenSet =
items::ITEM_RECOVERY_SET.union(TokenSet::new(&[T![')'], T![,], T![let]]));
const TYPE_PATH_SEGMENT_RECOVERY_SET: TokenSet = types::TYPE_RECOVERY_SET;
-fn path_segment(p: &mut Parser<'_>, mode: Mode, first: bool) {
+fn path_segment(p: &mut Parser<'_>, mode: Mode, first: bool) -> Option<CompletedMarker> {
let m = p.start();
// test qual_paths
// type X = <A as B>::Output;
@@ -93,12 +106,7 @@ fn path_segment(p: &mut Parser<'_>, mode: Mode, first: bool) {
p.error("expected `::`");
}
} else {
- let empty = if first {
- p.eat(T![::]);
- false
- } else {
- true
- };
+ let mut empty = if first { !p.eat(T![::]) } else { true };
match p.current() {
IDENT => {
name_ref(p);
@@ -114,25 +122,29 @@ fn path_segment(p: &mut Parser<'_>, mode: Mode, first: bool) {
_ => {
let recover_set = match mode {
Mode::Use => items::ITEM_RECOVERY_SET,
+ Mode::Attr => {
+ items::ITEM_RECOVERY_SET.union(TokenSet::new(&[T![']'], T![=], T![#]]))
+ }
+ Mode::Vis => items::ITEM_RECOVERY_SET.union(TokenSet::new(&[T![')']])),
Mode::Type => TYPE_PATH_SEGMENT_RECOVERY_SET,
Mode::Expr => EXPR_PATH_SEGMENT_RECOVERY_SET,
};
- p.err_recover("expected identifier", recover_set);
+ empty &= p.err_recover("expected identifier", recover_set);
if empty {
// test_err empty_segment
// use crate::;
m.abandon(p);
- return;
+ return None;
}
}
};
}
- m.complete(p, PATH_SEGMENT);
+ Some(m.complete(p, PATH_SEGMENT))
}
fn opt_path_type_args(p: &mut Parser<'_>, mode: Mode) {
match mode {
- Mode::Use => {}
+ Mode::Use | Mode::Attr | Mode::Vis => {}
Mode::Type => {
// test typepathfn_with_coloncolon
// type F = Start::(Middle) -> (Middle)::End;
diff --git a/crates/parser/src/parser.rs b/crates/parser/src/parser.rs
index f6b3783d1c..75a75f601c 100644
--- a/crates/parser/src/parser.rs
+++ b/crates/parser/src/parser.rs
@@ -258,22 +258,25 @@ impl<'t> Parser<'t> {
self.err_recover(message, TokenSet::EMPTY);
}
- /// Create an error node and consume the next token.
- pub(crate) fn err_recover(&mut self, message: &str, recovery: TokenSet) {
+ /// Create an error node and consume the next token unless it is in the recovery set.
+ ///
+ /// Returns true if recovery kicked in.
+ pub(crate) fn err_recover(&mut self, message: &str, recovery: TokenSet) -> bool {
if matches!(self.current(), T!['{'] | T!['}']) {
self.error(message);
- return;
+ return true;
}
if self.at_ts(recovery) {
self.error(message);
- return;
+ return true;
}
let m = self.start();
self.error(message);
self.bump_any();
m.complete(self, ERROR);
+ false
}
fn do_bump(&mut self, kind: SyntaxKind, n_raw_tokens: u8) {
@@ -324,10 +327,10 @@ impl Marker {
self.bomb.defuse();
let idx = self.pos as usize;
if idx == p.events.len() - 1 {
- match p.events.pop() {
- Some(Event::Start { kind: TOMBSTONE, forward_parent: None }) => (),
- _ => unreachable!(),
- }
+ assert!(matches!(
+ p.events.pop(),
+ Some(Event::Start { kind: TOMBSTONE, forward_parent: None })
+ ));
}
}
}
diff --git a/crates/parser/test_data/generated/runner.rs b/crates/parser/test_data/generated/runner.rs
index 62b381b668..3db8b51a4b 100644
--- a/crates/parser/test_data/generated/runner.rs
+++ b/crates/parser/test_data/generated/runner.rs
@@ -772,6 +772,8 @@ mod err {
run_and_expect_errors("test_data/parser/inline/err/match_arms_recovery.rs");
}
#[test]
+ fn meta_recovery() { run_and_expect_errors("test_data/parser/inline/err/meta_recovery.rs"); }
+ #[test]
fn method_call_missing_argument_list() {
run_and_expect_errors("test_data/parser/inline/err/method_call_missing_argument_list.rs");
}
diff --git a/crates/parser/test_data/parser/err/0004_use_path_bad_segment.rast b/crates/parser/test_data/parser/err/0004_use_path_bad_segment.rast
index 44e192a5fc..cf455934e9 100644
--- a/crates/parser/test_data/parser/err/0004_use_path_bad_segment.rast
+++ b/crates/parser/test_data/parser/err/0004_use_path_bad_segment.rast
@@ -9,7 +9,8 @@ SOURCE_FILE
NAME_REF
IDENT "foo"
COLON2 "::"
- ERROR
- INT_NUMBER "92"
+ PATH_SEGMENT
+ ERROR
+ INT_NUMBER "92"
SEMICOLON ";"
error 9: expected identifier
diff --git a/crates/parser/test_data/parser/err/0048_double_fish.rast b/crates/parser/test_data/parser/err/0048_double_fish.rast
index 207a5c24df..7ef1eb98fc 100644
--- a/crates/parser/test_data/parser/err/0048_double_fish.rast
+++ b/crates/parser/test_data/parser/err/0048_double_fish.rast
@@ -39,8 +39,9 @@ SOURCE_FILE
IDENT "lol"
R_ANGLE ">"
COLON2 "::"
- ERROR
- L_ANGLE "<"
+ PATH_SEGMENT
+ ERROR
+ L_ANGLE "<"
TYPE_ARG
PATH_TYPE
PATH
@@ -91,8 +92,9 @@ SOURCE_FILE
IDENT "lol"
R_ANGLE ">"
COLON2 "::"
- ERROR
- L_ANGLE "<"
+ PATH_SEGMENT
+ ERROR
+ L_ANGLE "<"
EXPR_STMT
BIN_EXPR
PATH_EXPR
diff --git a/crates/parser/test_data/parser/inline/err/crate_visibility_empty_recover.rast b/crates/parser/test_data/parser/inline/err/crate_visibility_empty_recover.rast
index 0fe4ca42d7..681ca6b6e0 100644
--- a/crates/parser/test_data/parser/inline/err/crate_visibility_empty_recover.rast
+++ b/crates/parser/test_data/parser/inline/err/crate_visibility_empty_recover.rast
@@ -3,10 +3,7 @@ SOURCE_FILE
VISIBILITY
PUB_KW "pub"
L_PAREN "("
- PATH
- PATH_SEGMENT
- ERROR
- R_PAREN ")"
+ R_PAREN ")"
WHITESPACE " "
STRUCT_KW "struct"
WHITESPACE " "
@@ -15,4 +12,3 @@ SOURCE_FILE
SEMICOLON ";"
WHITESPACE "\n"
error 4: expected identifier
-error 5: expected R_PAREN
diff --git a/crates/parser/test_data/parser/inline/err/meta_recovery.rast b/crates/parser/test_data/parser/inline/err/meta_recovery.rast
new file mode 100644
index 0000000000..926dd50fc8
--- /dev/null
+++ b/crates/parser/test_data/parser/inline/err/meta_recovery.rast
@@ -0,0 +1,83 @@
+SOURCE_FILE
+ ATTR
+ POUND "#"
+ BANG "!"
+ L_BRACK "["
+ META
+ R_BRACK "]"
+ WHITESPACE "\n"
+ ATTR
+ POUND "#"
+ BANG "!"
+ L_BRACK "["
+ META
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "p"
+ WHITESPACE " "
+ EQ "="
+ WHITESPACE " "
+ R_BRACK "]"
+ WHITESPACE "\n"
+ ATTR
+ POUND "#"
+ BANG "!"
+ L_BRACK "["
+ META
+ PATH
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "p"
+ COLON2 "::"
+ R_BRACK "]"
+ WHITESPACE "\n"
+ ATTR
+ POUND "#"
+ BANG "!"
+ L_BRACK "["
+ META
+ PATH
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "p"
+ COLON2 "::"
+ WHITESPACE " "
+ EQ "="
+ R_BRACK "]"
+ WHITESPACE "\n"
+ ATTR
+ POUND "#"
+ BANG "!"
+ L_BRACK "["
+ META
+ UNSAFE_KW "unsafe"
+ R_BRACK "]"
+ WHITESPACE "\n"
+ ATTR
+ POUND "#"
+ BANG "!"
+ L_BRACK "["
+ META
+ UNSAFE_KW "unsafe"
+ WHITESPACE " "
+ EQ "="
+ R_BRACK "]"
+ WHITESPACE "\n"
+error 3: expected identifier
+error 11: expected expression
+error 11: expected expression
+error 20: expected identifier
+error 28: expected identifier
+error 30: expected expression
+error 30: expected expression
+error 41: expected L_PAREN
+error 41: expected identifier
+error 41: expected R_PAREN
+error 52: expected L_PAREN
+error 52: expected identifier
+error 54: expected expression
+error 54: expected expression
+error 54: expected R_PAREN
diff --git a/crates/parser/test_data/parser/inline/err/meta_recovery.rs b/crates/parser/test_data/parser/inline/err/meta_recovery.rs
new file mode 100644
index 0000000000..51d30adf8b
--- /dev/null
+++ b/crates/parser/test_data/parser/inline/err/meta_recovery.rs
@@ -0,0 +1,6 @@
+#![]
+#![p = ]
+#![p::]
+#![p:: =]
+#![unsafe]
+#![unsafe =]