about summary refs log tree commit diff
path: root/compiler/rustc_parse/src
diff options
context:
space:
mode:
authorJacob Pratt <jacob@jhpratt.dev>2024-12-21 01:18:40 -0500
committerGitHub <noreply@github.com>2024-12-21 01:18:40 -0500
commit36485acdac216c45fcdf8cacd5b762ffe2d58af7 (patch)
treed999f8a2664d8e106514d3f64410870c0f050d98 /compiler/rustc_parse/src
parent13170cd787cb733ed24842ee825bcbd98dc01476 (diff)
parent1549af29c3fea4c9afde42712b5092d8cc98e140 (diff)
downloadrust-36485acdac216c45fcdf8cacd5b762ffe2d58af7.tar.gz
rust-36485acdac216c45fcdf8cacd5b762ffe2d58af7.zip
Rollup merge of #133087 - estebank:stmt-misparse, r=chenyukang
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);
   |                             +
```
Diffstat (limited to 'compiler/rustc_parse/src')
-rw-r--r--compiler/rustc_parse/src/parser/stmt.rs55
1 files changed, 53 insertions, 2 deletions
diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs
index 151abf0be95..1ddb5fc0a11 100644
--- a/compiler/rustc_parse/src/parser/stmt.rs
+++ b/compiler/rustc_parse/src/parser/stmt.rs
@@ -745,6 +745,51 @@ 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<'_>) {
+        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) {
+            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.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),
+                "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 +896,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 +918,12 @@ impl<'a> Parser<'a> {
                 // We might be at the `,` in `let x = foo<bar, baz>;`. 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<bar, baz>`, have we fully recovered?
                         self.expect_semi()?;
                     }