From 1ce0fa98c7766dc9e168b92ff9bc4e0df4fcaaef Mon Sep 17 00:00:00 2001 From: Esteban Küber Date: Sat, 16 Nov 2024 00:07:58 +0000 Subject: 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 +++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) (limited to 'compiler/rustc_parse/src/parser') diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs index 151abf0be95..259bc3dcd36 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()?; } -- cgit 1.4.1-3-g733a5 From cbbc7becc820e05f64d8a23d1a63a27e51b8ff7a Mon Sep 17 00:00:00 2001 From: Esteban Küber Date: Sat, 16 Nov 2024 00:10:48 +0000 Subject: 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(-) (limited to 'compiler/rustc_parse/src/parser') diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs index 259bc3dcd36..30a150db728 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 32b36c6d989..a72067021cb 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 -- cgit 1.4.1-3-g733a5 From 1549af29c3fea4c9afde42712b5092d8cc98e140 Mon Sep 17 00:00:00 2001 From: Esteban Küber Date: Sat, 21 Dec 2024 03:02:07 +0000 Subject: 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(-) (limited to 'compiler/rustc_parse/src/parser') diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs index 30a150db728..1ddb5fc0a11 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 c7d63672661..f7b6c894a90 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 -- cgit 1.4.1-3-g733a5 From fe65e886f30ab4e045e8e492e08f7c5e3a73129a Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Fri, 20 Dec 2024 19:50:42 -0800 Subject: Change comparison operators to have Fixity::None --- compiler/rustc_ast/src/util/parser.rs | 7 ++++--- compiler/rustc_parse/src/parser/expr.rs | 12 ++---------- tests/ui-fulldeps/pprust-parenthesis-insertion.rs | 6 ++---- 3 files changed, 8 insertions(+), 17 deletions(-) (limited to 'compiler/rustc_parse/src/parser') diff --git a/compiler/rustc_ast/src/util/parser.rs b/compiler/rustc_ast/src/util/parser.rs index 1d4b01aa94c..8f2b7a23c01 100644 --- a/compiler/rustc_ast/src/util/parser.rs +++ b/compiler/rustc_ast/src/util/parser.rs @@ -153,9 +153,10 @@ impl AssocOp { match *self { Assign | AssignOp(_) => Fixity::Right, As | Multiply | Divide | Modulus | Add | Subtract | ShiftLeft | ShiftRight | BitAnd - | BitXor | BitOr | Less | Greater | LessEqual | GreaterEqual | Equal | NotEqual - | LAnd | LOr => Fixity::Left, - DotDot | DotDotEq => Fixity::None, + | BitXor | BitOr | LAnd | LOr => Fixity::Left, + Less | Greater | LessEqual | GreaterEqual | Equal | NotEqual | DotDot | DotDotEq => { + Fixity::None + } } } diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 2f4adf2af9e..7533e75ffe2 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -279,13 +279,9 @@ impl<'a> Parser<'a> { break; } - let fixity = op.fixity(); - let min_prec = match fixity { + let min_prec = match op.fixity() { Fixity::Right => Bound::Included(prec), - Fixity::Left => Bound::Excluded(prec), - // We currently have no non-associative operators that are not handled above by - // the special cases. The code is here only for future convenience. - Fixity::None => Bound::Excluded(prec), + Fixity::Left | Fixity::None => Bound::Excluded(prec), }; let (rhs, _) = self.with_res(restrictions - Restrictions::STMT_EXPR, |this| { let attrs = this.parse_outer_attributes()?; @@ -337,10 +333,6 @@ impl<'a> Parser<'a> { self.dcx().span_bug(span, "AssocOp should have been handled by special case") } }; - - if let Fixity::None = fixity { - break; - } } Ok((lhs, parsed_something)) diff --git a/tests/ui-fulldeps/pprust-parenthesis-insertion.rs b/tests/ui-fulldeps/pprust-parenthesis-insertion.rs index 8b76eb7b606..258a1fdb1d3 100644 --- a/tests/ui-fulldeps/pprust-parenthesis-insertion.rs +++ b/tests/ui-fulldeps/pprust-parenthesis-insertion.rs @@ -97,6 +97,8 @@ static EXPRS: &[&str] = &[ "2..(2..2)", "(2..2)..", "..(2..2)", + // Grammar restriction: comparison operators cannot be chained (1 < 2 == false). + "((1 < 2) == false) as usize", // Grammar restriction: the value in let-else is not allowed to end in a // curly brace. "{ let _ = 1 + 1 else {}; }", @@ -121,10 +123,6 @@ static EXPRS: &[&str] = &[ "if let _ = () && (Struct {}).x {}", */ /* - // FIXME: pretty-printer produces invalid syntax. `(1 < 2 == false) as usize` - "((1 < 2) == false) as usize", - */ - /* // FIXME: pretty-printer produces invalid syntax. `for _ in 1..{ 2 } {}` "for _ in (1..{ 2 }) {}", */ -- cgit 1.4.1-3-g733a5