about summary refs log tree commit diff
path: root/compiler/rustc_resolve/src/diagnostics.rs
diff options
context:
space:
mode:
authorEsteban Küber <esteban@kuber.com.ar>2025-08-24 19:22:51 +0000
committerEsteban Küber <esteban@kuber.com.ar>2025-08-25 15:16:25 +0000
commit8dbdb1760b23112f87aedad37e4dad97559bc750 (patch)
treedd2cd17a9704e56eac5416f6c5c9883ebd6deaf5 /compiler/rustc_resolve/src/diagnostics.rs
parent41a79f1862aa6b81bac674598e275e80e9f09eb9 (diff)
downloadrust-8dbdb1760b23112f87aedad37e4dad97559bc750.tar.gz
rust-8dbdb1760b23112f87aedad37e4dad97559bc750.zip
On binding not present in all patterns, suggest potential typo
```
error[E0408]: variable `Ban` is not bound in all patterns
 --> f12.rs:9:9
  |
9 |         (Foo,Bar)|(Ban,Foo) => {}
  |         ^^^^^^^^^  --- variable not in all patterns
  |         |
  |         pattern doesn't bind `Ban`
  |
help: you might have meant to use the similarly named previously used binding `Bar`
  |
9 -         (Foo,Bar)|(Ban,Foo) => {}
9 +         (Foo,Bar)|(Bar,Foo) => {}
  |
```
Diffstat (limited to 'compiler/rustc_resolve/src/diagnostics.rs')
-rw-r--r--compiler/rustc_resolve/src/diagnostics.rs53
1 files changed, 47 insertions, 6 deletions
diff --git a/compiler/rustc_resolve/src/diagnostics.rs b/compiler/rustc_resolve/src/diagnostics.rs
index 337e7d2dd86..324310ff48b 100644
--- a/compiler/rustc_resolve/src/diagnostics.rs
+++ b/compiler/rustc_resolve/src/diagnostics.rs
@@ -661,8 +661,12 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
             ResolutionError::VariableNotBoundInPattern(binding_error, parent_scope) => {
                 let BindingError { name, target, origin, could_be_path } = binding_error;
 
-                let target_sp = target.iter().copied().collect::<Vec<_>>();
-                let origin_sp = origin.iter().copied().collect::<Vec<_>>();
+                let mut target_sp = target.iter().map(|pat| pat.span).collect::<Vec<_>>();
+                target_sp.sort();
+                target_sp.dedup();
+                let mut origin_sp = origin.iter().map(|(span, _)| *span).collect::<Vec<_>>();
+                origin_sp.sort();
+                origin_sp.dedup();
 
                 let msp = MultiSpan::from_spans(target_sp.clone());
                 let mut err = self
@@ -671,8 +675,29 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
                 for sp in target_sp {
                     err.subdiagnostic(errors::PatternDoesntBindName { span: sp, name });
                 }
-                for sp in origin_sp {
-                    err.subdiagnostic(errors::VariableNotInAllPatterns { span: sp });
+                for sp in &origin_sp {
+                    err.subdiagnostic(errors::VariableNotInAllPatterns { span: *sp });
+                }
+                let mut target_visitor = BindingVisitor::default();
+                for pat in &target {
+                    target_visitor.visit_pat(pat);
+                }
+                target_visitor.identifiers.sort();
+                target_visitor.identifiers.dedup();
+                let mut origin_visitor = BindingVisitor::default();
+                for (_, pat) in &origin {
+                    origin_visitor.visit_pat(pat);
+                }
+                origin_visitor.identifiers.sort();
+                origin_visitor.identifiers.dedup();
+                // Find if the binding could have been a typo
+                let mut suggested_typo = false;
+                if let Some(typo) =
+                    find_best_match_for_name(&target_visitor.identifiers, name.name, None)
+                    && !origin_visitor.identifiers.contains(&typo)
+                {
+                    err.subdiagnostic(errors::PatternBindingTypo { spans: origin_sp, typo });
+                    suggested_typo = true;
                 }
                 if could_be_path {
                     let import_suggestions = self.lookup_import_candidates(
@@ -693,7 +718,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
                         },
                     );
 
-                    if import_suggestions.is_empty() {
+                    if import_suggestions.is_empty() && !suggested_typo {
                         let help_msg = format!(
                             "if you meant to match on a variant or a `const` item, consider \
                              making the path in the pattern qualified: `path::to::ModOrType::{name}`",
@@ -3395,7 +3420,7 @@ impl UsePlacementFinder {
     }
 }
 
-impl<'tcx> visit::Visitor<'tcx> for UsePlacementFinder {
+impl<'tcx> Visitor<'tcx> for UsePlacementFinder {
     fn visit_crate(&mut self, c: &Crate) {
         if self.target_module == CRATE_NODE_ID {
             let inject = c.spans.inject_use_span;
@@ -3423,6 +3448,22 @@ impl<'tcx> visit::Visitor<'tcx> for UsePlacementFinder {
     }
 }
 
+#[derive(Default)]
+struct BindingVisitor {
+    identifiers: Vec<Symbol>,
+    spans: FxHashMap<Symbol, Vec<Span>>,
+}
+
+impl<'tcx> Visitor<'tcx> for BindingVisitor {
+    fn visit_pat(&mut self, pat: &ast::Pat) {
+        if let ast::PatKind::Ident(_, ident, _) = pat.kind {
+            self.identifiers.push(ident.name);
+            self.spans.entry(ident.name).or_default().push(ident.span);
+        }
+        visit::walk_pat(self, pat);
+    }
+}
+
 fn search_for_any_use_in_items(items: &[Box<ast::Item>]) -> Option<Span> {
     for item in items {
         if let ItemKind::Use(..) = item.kind