From 05aac8cd69ff15c6353dab1c27ad47e9def2d96f Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 4 Dec 2024 06:27:43 +0100 Subject: [PATCH] Better parser recovery for incomplete attributes --- .../crates/parser/src/grammar/attributes.rs | 10 ++- .../crates/parser/src/grammar/paths.rs | 19 ++-- .../rust-analyzer/crates/parser/src/parser.rs | 11 ++- .../parser/test_data/generated/runner.rs | 2 + .../parser/err/0004_use_path_bad_segment.rast | 5 +- .../parser/err/0048_double_fish.rast | 10 ++- .../parser/inline/err/meta_recovery.rast | 86 +++++++++++++++++++ .../parser/inline/err/meta_recovery.rs | 6 ++ 8 files changed, 130 insertions(+), 19 deletions(-) create mode 100644 src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/meta_recovery.rast create mode 100644 src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/meta_recovery.rs diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/attributes.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/attributes.rs index 82e4d6614880..ccb556b2ccac 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar/attributes.rs +++ b/src/tools/rust-analyzer/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/src/tools/rust-analyzer/crates/parser/src/grammar/paths.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/paths.rs index 09db921803f9..057a691ef033 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar/paths.rs +++ b/src/tools/rust-analyzer/crates/parser/src/grammar/paths.rs @@ -19,6 +19,10 @@ pub(super) fn use_path(p: &mut Parser<'_>) { path(p, Mode::Use); } +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,6 +41,7 @@ pub(crate) fn type_path_for_qualifier( #[derive(Clone, Copy, Eq, PartialEq)] enum Mode { Use, + Attr, Type, Expr, } @@ -93,12 +98,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,10 +114,13 @@ 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::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::; @@ -132,7 +135,7 @@ fn path_segment(p: &mut Parser<'_>, mode: Mode, first: bool) { fn opt_path_type_args(p: &mut Parser<'_>, mode: Mode) { match mode { - Mode::Use => {} + Mode::Use | Mode::Attr => {} Mode::Type => { // test typepathfn_with_coloncolon // type F = Start::(Middle) -> (Middle)::End; diff --git a/src/tools/rust-analyzer/crates/parser/src/parser.rs b/src/tools/rust-analyzer/crates/parser/src/parser.rs index f6b3783d1cac..8078532e0bb9 100644 --- a/src/tools/rust-analyzer/crates/parser/src/parser.rs +++ b/src/tools/rust-analyzer/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) { diff --git a/src/tools/rust-analyzer/crates/parser/test_data/generated/runner.rs b/src/tools/rust-analyzer/crates/parser/test_data/generated/runner.rs index 62b381b6688d..3db8b51a4baf 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/generated/runner.rs +++ b/src/tools/rust-analyzer/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/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0004_use_path_bad_segment.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0004_use_path_bad_segment.rast index 44e192a5fcbc..cf455934e91d 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0004_use_path_bad_segment.rast +++ b/src/tools/rust-analyzer/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/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0048_double_fish.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0048_double_fish.rast index 207a5c24dffd..7ef1eb98fcd9 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0048_double_fish.rast +++ b/src/tools/rust-analyzer/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/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/meta_recovery.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/meta_recovery.rast new file mode 100644 index 000000000000..c4bec849e7b9 --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/meta_recovery.rast @@ -0,0 +1,86 @@ +SOURCE_FILE + ATTR + POUND "#" + BANG "!" + L_BRACK "[" + META + PATH + 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" + PATH + R_BRACK "]" + WHITESPACE "\n" + ATTR + POUND "#" + BANG "!" + L_BRACK "[" + META + UNSAFE_KW "unsafe" + WHITESPACE " " + PATH + 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 53: expected identifier +error 54: expected expression +error 54: expected expression +error 54: expected R_PAREN diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/meta_recovery.rs b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/meta_recovery.rs new file mode 100644 index 000000000000..51d30adf8b49 --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/meta_recovery.rs @@ -0,0 +1,6 @@ +#![] +#![p = ] +#![p::] +#![p:: =] +#![unsafe] +#![unsafe =]