about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEsteban Küber <esteban@kuber.com.ar>2025-08-30 01:44:40 +0000
committerEsteban Küber <esteban@kuber.com.ar>2025-08-30 02:54:46 +0000
commitcb9cd8f8306e6e1b16661704aed8d6f09aa5db73 (patch)
tree50f3097e6aecf08566feec04f4dcde97bd733d9d
parentbd0ea615f73e433c43457374e76a16793b73bc9c (diff)
downloadrust-cb9cd8f8306e6e1b16661704aed8d6f09aa5db73.tar.gz
rust-cb9cd8f8306e6e1b16661704aed8d6f09aa5db73.zip
On binding not present in all patterns, look at consts and unit structs/variants for suggestions
When encountering an or-pattern with a binding not available in all patterns, look for consts and unit struct/variants that have similar names as the binding to detect typos.

```
error[E0408]: variable `Ban` is not bound in all patterns
  --> $DIR/binding-typo.rs:22:9
   |
LL |         (Foo, _) | (Ban, Foo) => {}
   |         ^^^^^^^^    --- variable not in all patterns
   |         |
   |         pattern doesn't bind `Ban`
   |
help: you might have meant to use the similarly named unit variant `Bar`
   |
LL -         (Foo, _) | (Ban, Foo) => {}
LL +         (Foo, _) | (Bar, Foo) => {}
   |
```

For items that are not in the immedate scope, suggest the full path for them:

```
error[E0408]: variable `Non` is not bound in all patterns
  --> $DIR/binding-typo-2.rs:51:16
   |
LL |         (Non | Some(_))=> {}
   |          ---   ^^^^^^^ pattern doesn't bind `Non`
   |          |
   |          variable not in all patterns
   |
help: you might have meant to use the similarly named unit variant `None`
   |
LL -         (Non | Some(_))=> {}
LL +         (core::option::Option::None | Some(_))=> {}
   |
```
-rw-r--r--compiler/rustc_resolve/src/diagnostics.rs113
-rw-r--r--tests/ui/or-patterns/binding-typo-2.rs98
-rw-r--r--tests/ui/or-patterns/binding-typo-2.stderr107
-rw-r--r--tests/ui/or-patterns/binding-typo.fixed12
-rw-r--r--tests/ui/or-patterns/binding-typo.rs12
-rw-r--r--tests/ui/or-patterns/binding-typo.stderr24
6 files changed, 361 insertions, 5 deletions
diff --git a/compiler/rustc_resolve/src/diagnostics.rs b/compiler/rustc_resolve/src/diagnostics.rs
index 324310ff48b..beb882a325f 100644
--- a/compiler/rustc_resolve/src/diagnostics.rs
+++ b/compiler/rustc_resolve/src/diagnostics.rs
@@ -719,9 +719,85 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
                     );
 
                     if import_suggestions.is_empty() && !suggested_typo {
+                        let kinds = [
+                            DefKind::Ctor(CtorOf::Variant, CtorKind::Const),
+                            DefKind::Ctor(CtorOf::Struct, CtorKind::Const),
+                            DefKind::Const,
+                            DefKind::AssocConst,
+                        ];
+                        let mut local_names = vec![];
+                        self.add_module_candidates(
+                            parent_scope.module,
+                            &mut local_names,
+                            &|res| matches!(res, Res::Def(_, _)),
+                            None,
+                        );
+                        let local_names: FxHashSet<_> = local_names
+                            .into_iter()
+                            .filter_map(|s| match s.res {
+                                Res::Def(_, def_id) => Some(def_id),
+                                _ => None,
+                            })
+                            .collect();
+
+                        let mut local_suggestions = vec![];
+                        let mut suggestions = vec![];
+                        for kind in kinds {
+                            if let Some(suggestion) = self.early_lookup_typo_candidate(
+                                ScopeSet::All(Namespace::ValueNS),
+                                &parent_scope,
+                                name,
+                                &|res: Res| match res {
+                                    Res::Def(k, _) => k == kind,
+                                    _ => false,
+                                },
+                            ) && let Res::Def(kind, mut def_id) = suggestion.res
+                            {
+                                if let DefKind::Ctor(_, _) = kind {
+                                    def_id = self.tcx.parent(def_id);
+                                }
+                                let kind = kind.descr(def_id);
+                                if local_names.contains(&def_id) {
+                                    // The item is available in the current scope. Very likely to
+                                    // be a typo. Don't use the full path.
+                                    local_suggestions.push((
+                                        suggestion.candidate,
+                                        suggestion.candidate.to_string(),
+                                        kind,
+                                    ));
+                                } else {
+                                    suggestions.push((
+                                        suggestion.candidate,
+                                        self.def_path_str(def_id),
+                                        kind,
+                                    ));
+                                }
+                            }
+                        }
+                        let suggestions = if !local_suggestions.is_empty() {
+                            // There is at least one item available in the current scope that is a
+                            // likely typo. We only show those.
+                            local_suggestions
+                        } else {
+                            suggestions
+                        };
+                        for (name, sugg, kind) in suggestions {
+                            err.span_suggestion_verbose(
+                                span,
+                                format!(
+                                    "you might have meant to use the similarly named {kind} `{name}`",
+                                ),
+                                sugg,
+                                Applicability::MaybeIncorrect,
+                            );
+                            suggested_typo = true;
+                        }
+                    }
+                    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}`",
+                            "if you meant to match on a unit struct, unit variant or a `const` \
+                             item, consider making the path in the pattern qualified: \
+                             `path::to::ModOrType::{name}`",
                         );
                         err.span_help(span, help_msg);
                     }
@@ -1041,6 +1117,39 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
         .emit()
     }
 
+    fn def_path_str(&self, mut def_id: DefId) -> String {
+        // We can't use `def_path_str` in resolve.
+        let mut path = vec![def_id];
+        while let Some(parent) = self.tcx.opt_parent(def_id) {
+            def_id = parent;
+            path.push(def_id);
+            if def_id.is_top_level_module() {
+                break;
+            }
+        }
+        // We will only suggest importing directly if it is accessible through that path.
+        path.into_iter()
+            .rev()
+            .map(|def_id| {
+                self.tcx
+                    .opt_item_name(def_id)
+                    .map(|name| {
+                        match (
+                            def_id.is_top_level_module(),
+                            def_id.is_local(),
+                            self.tcx.sess.edition(),
+                        ) {
+                            (true, true, Edition::Edition2015) => String::new(),
+                            (true, true, _) => kw::Crate.to_string(),
+                            (true, false, _) | (false, _, _) => name.to_string(),
+                        }
+                    })
+                    .unwrap_or_else(|| "_".to_string())
+            })
+            .collect::<Vec<String>>()
+            .join("::")
+    }
+
     pub(crate) fn add_scope_set_candidates(
         &mut self,
         suggestions: &mut Vec<TypoSuggestion>,
diff --git a/tests/ui/or-patterns/binding-typo-2.rs b/tests/ui/or-patterns/binding-typo-2.rs
new file mode 100644
index 00000000000..4dfb84d4f0c
--- /dev/null
+++ b/tests/ui/or-patterns/binding-typo-2.rs
@@ -0,0 +1,98 @@
+// Issue #51976
+#![deny(unused_variables)] //~ NOTE: the lint level is defined here
+enum Lol {
+    Foo,
+    Bar,
+}
+const Bat: () = ();
+struct Bay;
+
+fn foo(x: (Lol, Lol)) {
+    use Lol::*;
+    match &x {
+        (Foo, Bar) | (Ban, Foo) => {}
+        //~^ ERROR: variable `Ban` is not bound in all patterns
+        //~| HELP: you might have meant to use the similarly named previously used binding `Bar`
+        //~| NOTE: pattern doesn't bind `Ban`
+        //~| NOTE: variable not in all patterns
+        //~| ERROR: variable `Ban` is assigned to, but never used
+        //~| NOTE: consider using `_Ban` instead
+        _ => {}
+    }
+    match &x {
+        (Foo, _) | (Ban, Foo) => {}
+        //~^ ERROR: variable `Ban` is not bound in all patterns
+        //~| HELP: you might have meant to use the similarly named unit variant `Bar`
+        //~| NOTE: pattern doesn't bind `Ban`
+        //~| NOTE: variable not in all patterns
+        //~| ERROR: variable `Ban` is assigned to, but never used
+        //~| NOTE: consider using `_Ban` instead
+        _ => {}
+    }
+    match Some(42) {
+        Non => {}
+        //~^ ERROR: unused variable: `Non`
+        //~| HELP: if this is intentional, prefix it with an underscore
+        _ => {}
+    }
+    match Some(42) {
+        Non | None => {}
+        //~^ ERROR: unused variable: `Non`
+        //~| HELP: if this is intentional, prefix it with an underscore
+        //~| ERROR: variable `Non` is not bound in all patterns [E0408]
+        //~| NOTE: pattern doesn't bind `Non`
+        //~| NOTE: variable not in all patterns
+        //~| HELP: you might have meant to use the similarly named previously used binding `None`
+        _ => {}
+    }
+    match Some(42) {
+        Non | Some(_) => {}
+        //~^ ERROR: unused variable: `Non`
+        //~| HELP: if this is intentional, prefix it with an underscore
+        //~| ERROR: variable `Non` is not bound in all patterns [E0408]
+        //~| NOTE: pattern doesn't bind `Non`
+        //~| NOTE: variable not in all patterns
+        //~| HELP: you might have meant to use the similarly named unit variant `None`
+        _ => {}
+    }
+}
+fn bar(x: (Lol, Lol)) {
+    use Lol::*;
+    use Bat;
+    use Bay;
+    use std::option::Option::None;
+    match &x {
+        (Foo, _) | (Ban, Foo) => {}
+        //~^ ERROR: variable `Ban` is not bound in all patterns
+        //~| HELP: you might have meant to use the similarly named unit variant `Bar`
+        //~| HELP: you might have meant to use the similarly named unit struct `Bay`
+        //~| HELP: you might have meant to use the similarly named constant `Bat`
+        //~| NOTE: pattern doesn't bind `Ban`
+        //~| NOTE: variable not in all patterns
+        //~| ERROR: variable `Ban` is assigned to, but never used
+        //~| NOTE: consider using `_Ban` instead
+        _ => {}
+    }
+}
+fn baz(x: (Lol, Lol)) {
+    use Lol::*;
+    use Bat;
+    match &x {
+        (Foo, _) | (Ban, Foo) => {}
+        //~^ ERROR: variable `Ban` is not bound in all patterns
+        //~| HELP: you might have meant to use the similarly named unit variant `Bar`
+        //~| HELP: you might have meant to use the similarly named constant `Bat`
+        //~| NOTE: pattern doesn't bind `Ban`
+        //~| NOTE: variable not in all patterns
+        //~| ERROR: variable `Ban` is assigned to, but never used
+        //~| NOTE: consider using `_Ban` instead
+        _ => {}
+    }
+}
+
+fn main() {
+    use Lol::*;
+    foo((Foo, Bar));
+    bar((Foo, Bar));
+    baz((Foo, Bar));
+}
diff --git a/tests/ui/or-patterns/binding-typo-2.stderr b/tests/ui/or-patterns/binding-typo-2.stderr
new file mode 100644
index 00000000000..a2099572485
--- /dev/null
+++ b/tests/ui/or-patterns/binding-typo-2.stderr
@@ -0,0 +1,107 @@
+error[E0408]: variable `Ban` is not bound in all patterns
+  --> $DIR/binding-typo-2.rs:13:9
+   |
+LL |         (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`
+   |
+LL -         (Foo, Bar) | (Ban, Foo) => {}
+LL +         (Foo, Bar) | (Bar, Foo) => {}
+   |
+
+error[E0408]: variable `Ban` is not bound in all patterns
+  --> $DIR/binding-typo-2.rs:23:9
+   |
+LL |         (Foo, _) | (Ban, Foo) => {}
+   |         ^^^^^^^^    --- variable not in all patterns
+   |         |
+   |         pattern doesn't bind `Ban`
+   |
+help: you might have meant to use the similarly named unit variant `Bar`
+   |
+LL -         (Foo, _) | (Ban, Foo) => {}
+LL +         (Foo, _) | (Bar, Foo) => {}
+   |
+help: you might have meant to use the similarly named unit struct `Bay`
+   |
+LL -         (Foo, _) | (Ban, Foo) => {}
+LL +         (Foo, _) | (::Bay, Foo) => {}
+   |
+help: you might have meant to use the similarly named constant `Bat`
+   |
+LL -         (Foo, _) | (Ban, Foo) => {}
+LL +         (Foo, _) | (::Bat, Foo) => {}
+   |
+
+error[E0408]: variable `Non` is not bound in all patterns
+  --> $DIR/binding-typo-2.rs:41:16
+   |
+LL |         (Non | None)=> {}
+   |          ---   ^^^^ pattern doesn't bind `Non`
+   |          |
+   |          variable not in all patterns
+   |
+help: you might have meant to use the similarly named previously used binding `None`
+   |
+LL |         (None | None)=> {}
+   |             +
+
+error[E0408]: variable `Non` is not bound in all patterns
+  --> $DIR/binding-typo-2.rs:51:16
+   |
+LL |         (Non | Some(_))=> {}
+   |          ---   ^^^^^^^ pattern doesn't bind `Non`
+   |          |
+   |          variable not in all patterns
+   |
+help: you might have meant to use the similarly named unit variant `None`
+   |
+LL -         (Non | Some(_))=> {}
+LL +         (core::option::Option::None | Some(_))=> {}
+   |
+
+error: variable `Ban` is assigned to, but never used
+  --> $DIR/binding-typo-2.rs:13:23
+   |
+LL |         (Foo, Bar) | (Ban, Foo) => {}
+   |                       ^^^
+   |
+   = note: consider using `_Ban` instead
+note: the lint level is defined here
+  --> $DIR/binding-typo-2.rs:2:9
+   |
+LL | #![deny(unused_variables)]
+   |         ^^^^^^^^^^^^^^^^
+
+error: variable `Ban` is assigned to, but never used
+  --> $DIR/binding-typo-2.rs:23:21
+   |
+LL |         (Foo, _) | (Ban, Foo) => {}
+   |                     ^^^
+   |
+   = note: consider using `_Ban` instead
+
+error: unused variable: `Non`
+  --> $DIR/binding-typo-2.rs:35:9
+   |
+LL |         Non => {}
+   |         ^^^ help: if this is intentional, prefix it with an underscore: `_Non`
+
+error: unused variable: `Non`
+  --> $DIR/binding-typo-2.rs:41:10
+   |
+LL |         (Non | None)=> {}
+   |          ^^^ help: if this is intentional, prefix it with an underscore: `_Non`
+
+error: unused variable: `Non`
+  --> $DIR/binding-typo-2.rs:51:10
+   |
+LL |         (Non | Some(_))=> {}
+   |          ^^^ help: if this is intentional, prefix it with an underscore: `_Non`
+
+error: aborting due to 9 previous errors
+
+For more information about this error, try `rustc --explain E0408`.
diff --git a/tests/ui/or-patterns/binding-typo.fixed b/tests/ui/or-patterns/binding-typo.fixed
index 84637862484..2902ca30393 100644
--- a/tests/ui/or-patterns/binding-typo.fixed
+++ b/tests/ui/or-patterns/binding-typo.fixed
@@ -8,7 +8,7 @@ enum Lol {
 
 fn foo(x: (Lol, Lol)) {
     use Lol::*;
-    match x {
+    match &x {
         (Foo, Bar) | (Bar, Foo) => {}
         //~^ ERROR: variable `Ban` is not bound in all patterns
         //~| HELP: you might have meant to use the similarly named previously used binding `Bar`
@@ -18,6 +18,16 @@ fn foo(x: (Lol, Lol)) {
         //~| NOTE: consider using `_Ban` instead
         _ => {}
     }
+    match &x {
+        (Foo, _) | (Bar, Foo) => {}
+        //~^ ERROR: variable `Ban` is not bound in all patterns
+        //~| HELP: you might have meant to use the similarly named unit variant `Bar`
+        //~| NOTE: pattern doesn't bind `Ban`
+        //~| NOTE: variable not in all patterns
+        //~| ERROR: variable `Ban` is assigned to, but never used
+        //~| NOTE: consider using `_Ban` instead
+        _ => {}
+    }
 }
 
 fn main() {
diff --git a/tests/ui/or-patterns/binding-typo.rs b/tests/ui/or-patterns/binding-typo.rs
index 1c5f4d87619..2ffe0e45c83 100644
--- a/tests/ui/or-patterns/binding-typo.rs
+++ b/tests/ui/or-patterns/binding-typo.rs
@@ -8,7 +8,7 @@ enum Lol {
 
 fn foo(x: (Lol, Lol)) {
     use Lol::*;
-    match x {
+    match &x {
         (Foo, Bar) | (Ban, Foo) => {}
         //~^ ERROR: variable `Ban` is not bound in all patterns
         //~| HELP: you might have meant to use the similarly named previously used binding `Bar`
@@ -18,6 +18,16 @@ fn foo(x: (Lol, Lol)) {
         //~| NOTE: consider using `_Ban` instead
         _ => {}
     }
+    match &x {
+        (Foo, _) | (Ban, Foo) => {}
+        //~^ ERROR: variable `Ban` is not bound in all patterns
+        //~| HELP: you might have meant to use the similarly named unit variant `Bar`
+        //~| NOTE: pattern doesn't bind `Ban`
+        //~| NOTE: variable not in all patterns
+        //~| ERROR: variable `Ban` is assigned to, but never used
+        //~| NOTE: consider using `_Ban` instead
+        _ => {}
+    }
 }
 
 fn main() {
diff --git a/tests/ui/or-patterns/binding-typo.stderr b/tests/ui/or-patterns/binding-typo.stderr
index 31a409bbc5c..f90301d2cee 100644
--- a/tests/ui/or-patterns/binding-typo.stderr
+++ b/tests/ui/or-patterns/binding-typo.stderr
@@ -12,6 +12,20 @@ LL -         (Foo, Bar) | (Ban, Foo) => {}
 LL +         (Foo, Bar) | (Bar, Foo) => {}
    |
 
+error[E0408]: variable `Ban` is not bound in all patterns
+  --> $DIR/binding-typo.rs:22:9
+   |
+LL |         (Foo, _) | (Ban, Foo) => {}
+   |         ^^^^^^^^    --- variable not in all patterns
+   |         |
+   |         pattern doesn't bind `Ban`
+   |
+help: you might have meant to use the similarly named unit variant `Bar`
+   |
+LL -         (Foo, _) | (Ban, Foo) => {}
+LL +         (Foo, _) | (Bar, Foo) => {}
+   |
+
 error: variable `Ban` is assigned to, but never used
   --> $DIR/binding-typo.rs:12:23
    |
@@ -25,6 +39,14 @@ note: the lint level is defined here
 LL | #![deny(unused_variables)]
    |         ^^^^^^^^^^^^^^^^
 
-error: aborting due to 2 previous errors
+error: variable `Ban` is assigned to, but never used
+  --> $DIR/binding-typo.rs:22:21
+   |
+LL |         (Foo, _) | (Ban, Foo) => {}
+   |                     ^^^
+   |
+   = note: consider using `_Ban` instead
+
+error: aborting due to 4 previous errors
 
 For more information about this error, try `rustc --explain E0408`.