From 1ce0fa98c7766dc9e168b92ff9bc4e0df4fcaaef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Sat, 16 Nov 2024 00:07:58 +0000 Subject: [PATCH 1/3] Detect missing `.` in method chain in let bindings and statements On parse errors where an ident is found where one wasn't expected, see if the next elements might have been meant as method call or field access. ``` error: expected one of `.`, `;`, `?`, `else`, or an operator, found `map` --> $DIR/missing-dot-on-statement-expression.rs:7:29 | LL | let _ = [1, 2, 3].iter()map(|x| x); | ^^^ expected one of `.`, `;`, `?`, `else`, or an operator | help: you might have meant to write a method call | LL | let _ = [1, 2, 3].iter().map(|x| x); | + ``` --- compiler/rustc_parse/src/parser/stmt.rs | 46 ++++++++++++++++++- .../ui/parser/raw/raw-literal-keywords.stderr | 10 ++++ .../missing-dot-on-statement-expression.fixed | 28 +++++++++++ .../missing-dot-on-statement-expression.rs | 28 +++++++++++ ...missing-dot-on-statement-expression.stderr | 46 +++++++++++++++++++ tests/ui/proc-macro/raw-ident.stderr | 4 ++ .../type-ascription-and-other-error.stderr | 5 ++ 7 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 tests/ui/parser/recover/missing-dot-on-statement-expression.fixed create mode 100644 tests/ui/parser/recover/missing-dot-on-statement-expression.rs create mode 100644 tests/ui/parser/recover/missing-dot-on-statement-expression.stderr diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs index 151abf0be9508..259bc3dcd368c 100644 --- a/compiler/rustc_parse/src/parser/stmt.rs +++ b/compiler/rustc_parse/src/parser/stmt.rs @@ -745,6 +745,42 @@ impl<'a> Parser<'a> { Ok(self.mk_block(stmts, s, lo.to(self.prev_token.span))) } + fn recover_missing_dot(&mut self, err: &mut Diag<'_>) { + if !self.token.is_ident() { + return; + } + if self.token.is_reserved_ident() && !self.token.is_ident_named(kw::Await) { + return; + } + if self.prev_token.is_reserved_ident() && self.prev_token.is_ident_named(kw::Await) { + // Likely `foo.await bar` + } else if !self.prev_token.is_reserved_ident() && self.prev_token.is_ident() { + // Likely `foo bar` + } else if self.prev_token.kind == token::Question { + // `foo? bar` + } else if self.prev_token.kind == token::CloseDelim(Delimiter::Parenthesis) { + // `foo() bar` + } else { + return; + } + if self.look_ahead(1, |t| [token::Semi, token::Question, token::Dot].contains(&t.kind)) { + err.span_suggestion_verbose( + self.prev_token.span.between(self.token.span), + "you might have meant to write a field access", + ".".to_string(), + Applicability::MaybeIncorrect, + ); + } + if self.look_ahead(1, |t| t.kind == token::OpenDelim(Delimiter::Parenthesis)) { + err.span_suggestion_verbose( + self.prev_token.span.between(self.token.span), + "you might have meant to write a method call", + ".".to_string(), + Applicability::MaybeIncorrect, + ); + } + } + /// Parses a statement, including the trailing semicolon. pub fn parse_full_stmt( &mut self, @@ -851,7 +887,8 @@ impl<'a> Parser<'a> { Some(if recover.no() { res? } else { - res.unwrap_or_else(|e| { + res.unwrap_or_else(|mut e| { + self.recover_missing_dot(&mut e); let guar = e.emit(); self.recover_stmt(); guar @@ -872,7 +909,12 @@ impl<'a> Parser<'a> { // We might be at the `,` in `let x = foo;`. Try to recover. match &mut local.kind { LocalKind::Init(expr) | LocalKind::InitElse(expr, _) => { - self.check_mistyped_turbofish_with_multiple_type_params(e, expr)?; + self.check_mistyped_turbofish_with_multiple_type_params(e, expr).map_err( + |mut e| { + self.recover_missing_dot(&mut e); + e + }, + )?; // We found `foo`, have we fully recovered? self.expect_semi()?; } diff --git a/tests/ui/parser/raw/raw-literal-keywords.stderr b/tests/ui/parser/raw/raw-literal-keywords.stderr index f7b6c894a90fe..c7d63672661d3 100644 --- a/tests/ui/parser/raw/raw-literal-keywords.stderr +++ b/tests/ui/parser/raw/raw-literal-keywords.stderr @@ -9,12 +9,22 @@ error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found | LL | r#struct Test; | ^^^^ expected one of 8 possible tokens + | +help: you might have meant to write a field access + | +LL | r#struct.Test; + | + error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `Test` --> $DIR/raw-literal-keywords.rs:10:13 | LL | r#union Test; | ^^^^ expected one of 8 possible tokens + | +help: you might have meant to write a field access + | +LL | r#union.Test; + | + error[E0425]: cannot find value `r#if` in this scope --> $DIR/raw-literal-keywords.rs:14:13 diff --git a/tests/ui/parser/recover/missing-dot-on-statement-expression.fixed b/tests/ui/parser/recover/missing-dot-on-statement-expression.fixed new file mode 100644 index 0000000000000..1be4485b47425 --- /dev/null +++ b/tests/ui/parser/recover/missing-dot-on-statement-expression.fixed @@ -0,0 +1,28 @@ +//@ run-rustfix +#![allow(unused_must_use, dead_code)] +struct S { + field: (), +} +fn main() { + let _ = [1, 2, 3].iter().map(|x| x); //~ ERROR expected one of `.`, `;`, `?`, `else`, or an operator, found `map` + //~^ HELP you might have meant to write a method call +} +fn foo() { + let baz = S { + field: () + }; + let _ = baz.field; //~ ERROR expected one of `!`, `.`, `::`, `;`, `?`, `else`, `{`, or an operator, found `field` + //~^ HELP you might have meant to write a field +} + +fn bar() { + [1, 2, 3].iter().map(|x| x); //~ ERROR expected one of `.`, `;`, `?`, `}`, or an operator, found `map` + //~^ HELP you might have meant to write a method call +} +fn baz() { + let baz = S { + field: () + }; + baz.field; //~ ERROR expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `field` + //~^ HELP you might have meant to write a field +} diff --git a/tests/ui/parser/recover/missing-dot-on-statement-expression.rs b/tests/ui/parser/recover/missing-dot-on-statement-expression.rs new file mode 100644 index 0000000000000..5e2b545f4148d --- /dev/null +++ b/tests/ui/parser/recover/missing-dot-on-statement-expression.rs @@ -0,0 +1,28 @@ +//@ run-rustfix +#![allow(unused_must_use, dead_code)] +struct S { + field: (), +} +fn main() { + let _ = [1, 2, 3].iter()map(|x| x); //~ ERROR expected one of `.`, `;`, `?`, `else`, or an operator, found `map` + //~^ HELP you might have meant to write a method call +} +fn foo() { + let baz = S { + field: () + }; + let _ = baz field; //~ ERROR expected one of `!`, `.`, `::`, `;`, `?`, `else`, `{`, or an operator, found `field` + //~^ HELP you might have meant to write a field +} + +fn bar() { + [1, 2, 3].iter()map(|x| x); //~ ERROR expected one of `.`, `;`, `?`, `}`, or an operator, found `map` + //~^ HELP you might have meant to write a method call +} +fn baz() { + let baz = S { + field: () + }; + baz field; //~ ERROR expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `field` + //~^ HELP you might have meant to write a field +} diff --git a/tests/ui/parser/recover/missing-dot-on-statement-expression.stderr b/tests/ui/parser/recover/missing-dot-on-statement-expression.stderr new file mode 100644 index 0000000000000..a04d8bd34e243 --- /dev/null +++ b/tests/ui/parser/recover/missing-dot-on-statement-expression.stderr @@ -0,0 +1,46 @@ +error: expected one of `.`, `;`, `?`, `else`, or an operator, found `map` + --> $DIR/missing-dot-on-statement-expression.rs:7:29 + | +LL | let _ = [1, 2, 3].iter()map(|x| x); + | ^^^ expected one of `.`, `;`, `?`, `else`, or an operator + | +help: you might have meant to write a method call + | +LL | let _ = [1, 2, 3].iter().map(|x| x); + | + + +error: expected one of `!`, `.`, `::`, `;`, `?`, `else`, `{`, or an operator, found `field` + --> $DIR/missing-dot-on-statement-expression.rs:14:17 + | +LL | let _ = baz field; + | ^^^^^ expected one of 8 possible tokens + | +help: you might have meant to write a field access + | +LL | let _ = baz.field; + | + + +error: expected one of `.`, `;`, `?`, `}`, or an operator, found `map` + --> $DIR/missing-dot-on-statement-expression.rs:19:21 + | +LL | [1, 2, 3].iter()map(|x| x); + | ^^^ expected one of `.`, `;`, `?`, `}`, or an operator + | +help: you might have meant to write a method call + | +LL | [1, 2, 3].iter().map(|x| x); + | + + +error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `field` + --> $DIR/missing-dot-on-statement-expression.rs:26:9 + | +LL | baz field; + | ^^^^^ expected one of 8 possible tokens + | +help: you might have meant to write a field access + | +LL | baz.field; + | + + +error: aborting due to 4 previous errors + diff --git a/tests/ui/proc-macro/raw-ident.stderr b/tests/ui/proc-macro/raw-ident.stderr index a72067021cb31..32b36c6d98957 100644 --- a/tests/ui/proc-macro/raw-ident.stderr +++ b/tests/ui/proc-macro/raw-ident.stderr @@ -5,6 +5,10 @@ LL | make_bad_struct!(S); | ^^^^^^^^^^^^^^^^^^^ expected one of 8 possible tokens | = note: this error originates in the macro `make_bad_struct` (in Nightly builds, run with -Z macro-backtrace for more info) +help: you might have meant to write a field access + | +LL | .; + | ~ error: aborting due to 1 previous error diff --git a/tests/ui/suggestions/type-ascription-and-other-error.stderr b/tests/ui/suggestions/type-ascription-and-other-error.stderr index 4efddca4b4714..7f8b8b7470eac 100644 --- a/tests/ui/suggestions/type-ascription-and-other-error.stderr +++ b/tests/ui/suggestions/type-ascription-and-other-error.stderr @@ -3,6 +3,11 @@ error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found | LL | not rust; | ^^^^ expected one of 8 possible tokens + | +help: you might have meant to write a field access + | +LL | not.rust; + | + error: aborting due to 1 previous error From cbbc7becc820e05f64d8a23d1a63a27e51b8ff7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Sat, 16 Nov 2024 00:10:48 +0000 Subject: [PATCH 2/3] Account for missing `.` in macros to avoid incorrect suggestion --- compiler/rustc_parse/src/parser/stmt.rs | 4 ++++ tests/ui/proc-macro/raw-ident.stderr | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs index 259bc3dcd368c..30a150db72891 100644 --- a/compiler/rustc_parse/src/parser/stmt.rs +++ b/compiler/rustc_parse/src/parser/stmt.rs @@ -763,6 +763,10 @@ impl<'a> Parser<'a> { } else { return; } + if self.token.span == self.prev_token.span { + // Account for syntax errors in proc-macros. + return; + } if self.look_ahead(1, |t| [token::Semi, token::Question, token::Dot].contains(&t.kind)) { err.span_suggestion_verbose( self.prev_token.span.between(self.token.span), diff --git a/tests/ui/proc-macro/raw-ident.stderr b/tests/ui/proc-macro/raw-ident.stderr index 32b36c6d98957..a72067021cb31 100644 --- a/tests/ui/proc-macro/raw-ident.stderr +++ b/tests/ui/proc-macro/raw-ident.stderr @@ -5,10 +5,6 @@ LL | make_bad_struct!(S); | ^^^^^^^^^^^^^^^^^^^ expected one of 8 possible tokens | = note: this error originates in the macro `make_bad_struct` (in Nightly builds, run with -Z macro-backtrace for more info) -help: you might have meant to write a field access - | -LL | .; - | ~ error: aborting due to 1 previous error From 1549af29c3fea4c9afde42712b5092d8cc98e140 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Sat, 21 Dec 2024 03:02:07 +0000 Subject: [PATCH 3/3] Do not suggest `foo.Bar` --- compiler/rustc_parse/src/parser/stmt.rs | 7 ++++++- tests/ui/parser/raw/raw-literal-keywords.stderr | 10 ---------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs index 30a150db72891..1ddb5fc0a1169 100644 --- a/compiler/rustc_parse/src/parser/stmt.rs +++ b/compiler/rustc_parse/src/parser/stmt.rs @@ -746,7 +746,12 @@ impl<'a> Parser<'a> { } fn recover_missing_dot(&mut self, err: &mut Diag<'_>) { - if !self.token.is_ident() { + let Some((ident, _)) = self.token.ident() else { + return; + }; + if let Some(c) = ident.name.as_str().chars().next() + && c.is_uppercase() + { return; } if self.token.is_reserved_ident() && !self.token.is_ident_named(kw::Await) { diff --git a/tests/ui/parser/raw/raw-literal-keywords.stderr b/tests/ui/parser/raw/raw-literal-keywords.stderr index c7d63672661d3..f7b6c894a90fe 100644 --- a/tests/ui/parser/raw/raw-literal-keywords.stderr +++ b/tests/ui/parser/raw/raw-literal-keywords.stderr @@ -9,22 +9,12 @@ error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found | LL | r#struct Test; | ^^^^ expected one of 8 possible tokens - | -help: you might have meant to write a field access - | -LL | r#struct.Test; - | + error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `Test` --> $DIR/raw-literal-keywords.rs:10:13 | LL | r#union Test; | ^^^^ expected one of 8 possible tokens - | -help: you might have meant to write a field access - | -LL | r#union.Test; - | + error[E0425]: cannot find value `r#if` in this scope --> $DIR/raw-literal-keywords.rs:14:13