about summary refs log tree commit diff
path: root/compiler/rustc_parse/src
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2025-08-31 03:00:54 +0000
committerbors <bors@rust-lang.org>2025-08-31 03:00:54 +0000
commit64a99db105f45ea3304732ffb51066c3b5193bc7 (patch)
treea731ff2123ba6ff288f43d842c9724e38ed6d4cf /compiler/rustc_parse/src
parentcd60c60d9f6bf13ca96ecde7392327c3caf6f162 (diff)
parent3af81cf0b7bd394dac89cbacec303b5937b5519a (diff)
downloadrust-64a99db105f45ea3304732ffb51066c3b5193bc7.tar.gz
rust-64a99db105f45ea3304732ffb51066c3b5193bc7.zip
Auto merge of #145582 - estebank:issue-107806, r=chenyukang
Detect missing `if let` or `let-else`

During `let` binding parse error and encountering a block, detect if there is a likely missing `if` or `else`:

```
error: expected one of `.`, `;`, `?`, `else`, or an operator, found `{`
  --> $DIR/missing-if-let-or-let-else.rs:14:25
   |
LL |     let Some(x) = foo() {
   |                         ^ expected one of `.`, `;`, `?`, `else`, or an operator
   |
help: you might have meant to use `if let`
   |
LL |     if let Some(x) = foo() {
   |     ++
help: alternatively, you might have meant to use `let else`
   |
LL |     let Some(x) = foo() else {
   |                         ++++
```

Fix rust-lang/rust#107806.
Diffstat (limited to 'compiler/rustc_parse/src')
-rw-r--r--compiler/rustc_parse/src/lib.rs1
-rw-r--r--compiler/rustc_parse/src/parser/stmt.rs96
2 files changed, 97 insertions, 0 deletions
diff --git a/compiler/rustc_parse/src/lib.rs b/compiler/rustc_parse/src/lib.rs
index 48289b2e8ab..197333d942d 100644
--- a/compiler/rustc_parse/src/lib.rs
+++ b/compiler/rustc_parse/src/lib.rs
@@ -6,6 +6,7 @@
 #![feature(assert_matches)]
 #![feature(box_patterns)]
 #![feature(debug_closure_helpers)]
+#![feature(default_field_values)]
 #![feature(if_let_guard)]
 #![feature(iter_intersperse)]
 #![recursion_limit = "256"]
diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs
index b4943ff7de6..ad5ab6e6b77 100644
--- a/compiler/rustc_parse/src/parser/stmt.rs
+++ b/compiler/rustc_parse/src/parser/stmt.rs
@@ -6,6 +6,7 @@ use ast::Label;
 use rustc_ast as ast;
 use rustc_ast::token::{self, Delimiter, InvisibleOrigin, MetaVarKind, TokenKind};
 use rustc_ast::util::classify::{self, TrailingBrace};
+use rustc_ast::visit::{Visitor, walk_expr};
 use rustc_ast::{
     AttrStyle, AttrVec, Block, BlockCheckMode, DUMMY_NODE_ID, Expr, ExprKind, HasAttrs, Local,
     LocalKind, MacCall, MacCallStmt, MacStmtStyle, Recovered, Stmt, StmtKind,
@@ -783,6 +784,100 @@ impl<'a> Parser<'a> {
         Ok(self.mk_block(stmts, s, lo.to(self.prev_token.span)))
     }
 
+    fn recover_missing_let_else(&mut self, err: &mut Diag<'_>, pat: &ast::Pat, stmt_span: Span) {
+        if self.token.kind != token::OpenBrace {
+            return;
+        }
+        match pat.kind {
+            ast::PatKind::Ident(..) | ast::PatKind::Missing | ast::PatKind::Wild => {
+                // Not if let or let else
+                return;
+            }
+            _ => {}
+        }
+        let snapshot = self.create_snapshot_for_diagnostic();
+        let block_span = self.token.span;
+        let (if_let, let_else) = match self.parse_block() {
+            Ok(block) => {
+                let mut idents = vec![];
+                pat.walk(&mut |pat: &ast::Pat| {
+                    if let ast::PatKind::Ident(_, ident, _) = pat.kind {
+                        idents.push(ident);
+                    }
+                    true
+                });
+
+                struct IdentFinder {
+                    idents: Vec<Ident>,
+                    /// If a block references one of the bindings introduced by the let pattern,
+                    /// we likely meant to use `if let`.
+                    /// This is pre-expansion, so if we encounter
+                    /// `let Some(x) = foo() { println!("{x}") }` we won't find it.
+                    references_ident: bool = false,
+                    /// If a block has a `return`, then we know with high certainty that it was
+                    /// meant to be let-else.
+                    has_return: bool = false,
+                }
+
+                impl<'a> Visitor<'a> for IdentFinder {
+                    fn visit_ident(&mut self, ident: &Ident) {
+                        for i in &self.idents {
+                            if ident.name == i.name {
+                                self.references_ident = true;
+                            }
+                        }
+                    }
+                    fn visit_expr(&mut self, node: &'a Expr) {
+                        if let ExprKind::Ret(..) = node.kind {
+                            self.has_return = true;
+                        }
+                        walk_expr(self, node);
+                    }
+                }
+
+                // Collect all bindings in pattern and see if they appear in the block. Likely meant
+                // to write `if let`. See if the block has a return. Likely meant to write
+                // `let else`.
+                let mut visitor = IdentFinder { idents, .. };
+                visitor.visit_block(&block);
+
+                (visitor.references_ident, visitor.has_return)
+            }
+            Err(e) => {
+                e.cancel();
+                self.restore_snapshot(snapshot);
+                (false, false)
+            }
+        };
+
+        let mut alternatively = "";
+        if if_let || !let_else {
+            alternatively = "alternatively, ";
+            err.span_suggestion_verbose(
+                stmt_span.shrink_to_lo(),
+                "you might have meant to use `if let`",
+                "if ".to_string(),
+                if if_let {
+                    Applicability::MachineApplicable
+                } else {
+                    Applicability::MaybeIncorrect
+                },
+            );
+        }
+        if let_else || !if_let {
+            err.span_suggestion_verbose(
+                block_span.shrink_to_lo(),
+                format!("{alternatively}you might have meant to use `let else`"),
+                "else ".to_string(),
+                if let_else {
+                    Applicability::MachineApplicable
+                } else {
+                    Applicability::MaybeIncorrect
+                },
+            );
+        }
+    }
+
     fn recover_missing_dot(&mut self, err: &mut Diag<'_>) {
         let Some((ident, _)) = self.token.ident() else {
             return;
@@ -977,6 +1072,7 @@ impl<'a> Parser<'a> {
                         self.check_mistyped_turbofish_with_multiple_type_params(e, expr).map_err(
                             |mut e| {
                                 self.recover_missing_dot(&mut e);
+                                self.recover_missing_let_else(&mut e, &local.pat, stmt.span);
                                 e
                             },
                         )?;