diff options
| author | Esteban Küber <esteban@kuber.com.ar> | 2025-08-30 01:44:40 +0000 |
|---|---|---|
| committer | Esteban Küber <esteban@kuber.com.ar> | 2025-08-30 02:54:46 +0000 |
| commit | cb9cd8f8306e6e1b16661704aed8d6f09aa5db73 (patch) | |
| tree | 50f3097e6aecf08566feec04f4dcde97bd733d9d | |
| parent | bd0ea615f73e433c43457374e76a16793b73bc9c (diff) | |
| download | rust-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.rs | 113 | ||||
| -rw-r--r-- | tests/ui/or-patterns/binding-typo-2.rs | 98 | ||||
| -rw-r--r-- | tests/ui/or-patterns/binding-typo-2.stderr | 107 | ||||
| -rw-r--r-- | tests/ui/or-patterns/binding-typo.fixed | 12 | ||||
| -rw-r--r-- | tests/ui/or-patterns/binding-typo.rs | 12 | ||||
| -rw-r--r-- | tests/ui/or-patterns/binding-typo.stderr | 24 |
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`. |
