From 8dbdb1760b23112f87aedad37e4dad97559bc750 Mon Sep 17 00:00:00 2001 From: Esteban Küber Date: Sun, 24 Aug 2025 19:22:51 +0000 Subject: 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) => {} | ``` --- compiler/rustc_resolve/src/diagnostics.rs | 53 +++++++++++++++++++++++++++---- compiler/rustc_resolve/src/errors.rs | 12 +++++++ compiler/rustc_resolve/src/late.rs | 52 +++++++++++++++--------------- compiler/rustc_resolve/src/lib.rs | 4 +-- 4 files changed, 86 insertions(+), 35 deletions(-) (limited to 'compiler/rustc_resolve/src') 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::>(); - let origin_sp = origin.iter().copied().collect::>(); + let mut target_sp = target.iter().map(|pat| pat.span).collect::>(); + target_sp.sort(); + target_sp.dedup(); + let mut origin_sp = origin.iter().map(|(span, _)| *span).collect::>(); + 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, + spans: FxHashMap>, +} + +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]) -> Option { for item in items { if let ItemKind::Use(..) = item.kind diff --git a/compiler/rustc_resolve/src/errors.rs b/compiler/rustc_resolve/src/errors.rs index 63d6fa23a14..72be94e58db 100644 --- a/compiler/rustc_resolve/src/errors.rs +++ b/compiler/rustc_resolve/src/errors.rs @@ -986,6 +986,18 @@ pub(crate) struct VariableNotInAllPatterns { pub(crate) span: Span, } +#[derive(Subdiagnostic)] +#[multipart_suggestion( + resolve_variable_is_a_typo, + applicability = "maybe-incorrect", + style = "verbose" +)] +pub(crate) struct PatternBindingTypo { + #[suggestion_part(code = "{typo}")] + pub(crate) spans: Vec, + pub(crate) typo: Symbol, +} + #[derive(Diagnostic)] #[diag(resolve_name_defined_multiple_time)] #[note] diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index 679e663f886..6f1bd4572dd 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -8,7 +8,6 @@ use std::assert_matches::debug_assert_matches; use std::borrow::Cow; -use std::collections::BTreeSet; use std::collections::hash_map::Entry; use std::mem::{replace, swap, take}; @@ -3682,31 +3681,30 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { // 2) Record any missing bindings or binding mode inconsistencies. for (map_outer, pat_outer) in not_never_pats.iter() { // Check against all arms except for the same pattern which is always self-consistent. - let inners = not_never_pats - .iter() - .filter(|(_, pat)| pat.id != pat_outer.id) - .flat_map(|(map, _)| map); - - for (&name, binding_inner) in inners { - match map_outer.get(&name) { - None => { - // The inner binding is missing in the outer. - let binding_error = - missing_vars.entry(name).or_insert_with(|| BindingError { - name, - origin: BTreeSet::new(), - target: BTreeSet::new(), - could_be_path: name.as_str().starts_with(char::is_uppercase), - }); - binding_error.origin.insert(binding_inner.span); - binding_error.target.insert(pat_outer.span); - } - Some(binding_outer) => { - if binding_outer.annotation != binding_inner.annotation { - // The binding modes in the outer and inner bindings differ. - inconsistent_vars - .entry(name) - .or_insert((binding_inner.span, binding_outer.span)); + let inners = not_never_pats.iter().filter(|(_, pat)| pat.id != pat_outer.id); + + for (map, pat) in inners { + for (&name, binding_inner) in map { + match map_outer.get(&name) { + None => { + // The inner binding is missing in the outer. + let binding_error = + missing_vars.entry(name).or_insert_with(|| BindingError { + name, + origin: Default::default(), + target: Default::default(), + could_be_path: name.as_str().starts_with(char::is_uppercase), + }); + binding_error.origin.push((binding_inner.span, (***pat).clone())); + binding_error.target.push((***pat_outer).clone()); + } + Some(binding_outer) => { + if binding_outer.annotation != binding_inner.annotation { + // The binding modes in the outer and inner bindings differ. + inconsistent_vars + .entry(name) + .or_insert((binding_inner.span, binding_outer.span)); + } } } } @@ -3719,7 +3717,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { v.could_be_path = false; } self.report_error( - *v.origin.iter().next().unwrap(), + v.origin.iter().next().unwrap().0, ResolutionError::VariableNotBoundInPattern(v, self.parent_scope), ); } diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index 2afb52ef4d4..c36a6883300 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -230,8 +230,8 @@ enum Used { #[derive(Debug)] struct BindingError { name: Ident, - origin: BTreeSet, - target: BTreeSet, + origin: Vec<(Span, ast::Pat)>, + target: Vec, could_be_path: bool, } -- cgit 1.4.1-3-g733a5 From cb9cd8f8306e6e1b16661704aed8d6f09aa5db73 Mon Sep 17 00:00:00 2001 From: Esteban Küber Date: Sat, 30 Aug 2025 01:44:40 +0000 Subject: 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(_))=> {} | ``` --- compiler/rustc_resolve/src/diagnostics.rs | 113 ++++++++++++++++++++++++++++- tests/ui/or-patterns/binding-typo-2.rs | 98 +++++++++++++++++++++++++ tests/ui/or-patterns/binding-typo-2.stderr | 107 +++++++++++++++++++++++++++ tests/ui/or-patterns/binding-typo.fixed | 12 ++- tests/ui/or-patterns/binding-typo.rs | 12 ++- tests/ui/or-patterns/binding-typo.stderr | 24 +++++- 6 files changed, 361 insertions(+), 5 deletions(-) create mode 100644 tests/ui/or-patterns/binding-typo-2.rs create mode 100644 tests/ui/or-patterns/binding-typo-2.stderr (limited to 'compiler/rustc_resolve/src') 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 @@ -718,10 +718,86 @@ 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::>() + .join("::") + } + pub(crate) fn add_scope_set_candidates( &mut self, suggestions: &mut Vec, 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`. -- cgit 1.4.1-3-g733a5 From 86085b4f65fae0ebc456202584e7e04b58a871f3 Mon Sep 17 00:00:00 2001 From: Esteban Küber Date: Sat, 30 Aug 2025 17:44:23 +0000 Subject: Avoid unnecessary suggestion in or-pattern --- compiler/rustc_resolve/src/diagnostics.rs | 42 ++++++++++++---------- tests/ui/or-patterns/binding-typo-2.rs | 2 +- tests/ui/or-patterns/binding-typo-2.stderr | 7 ++-- .../mismatched-bindings-async-fn.stderr | 24 ------------- .../nested-undelimited-precedence.stderr | 6 ---- tests/ui/resolve/resolve-inconsistent-names.rs | 2 -- tests/ui/resolve/resolve-inconsistent-names.stderr | 26 ++++---------- 7 files changed, 36 insertions(+), 73 deletions(-) (limited to 'compiler/rustc_resolve/src') diff --git a/compiler/rustc_resolve/src/diagnostics.rs b/compiler/rustc_resolve/src/diagnostics.rs index beb882a325f..b7042045242 100644 --- a/compiler/rustc_resolve/src/diagnostics.rs +++ b/compiler/rustc_resolve/src/diagnostics.rs @@ -678,26 +678,32 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { 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) + if !target.iter().all(|pat| matches!(pat.kind, ast::PatKind::Ident(..))) + && !origin.iter().all(|(_, pat)| matches!(pat.kind, ast::PatKind::Ident(..))) { - err.subdiagnostic(errors::PatternBindingTypo { spans: origin_sp, typo }); - suggested_typo = true; + // The check above is so that when we encounter `match foo { (a | b) => {} }`, + // we don't suggest `(a | a) => {}`, which would never be what the user wants. + 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 + 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( diff --git a/tests/ui/or-patterns/binding-typo-2.rs b/tests/ui/or-patterns/binding-typo-2.rs index fe4a20536d7..275b68a22e2 100644 --- a/tests/ui/or-patterns/binding-typo-2.rs +++ b/tests/ui/or-patterns/binding-typo-2.rs @@ -47,7 +47,7 @@ fn foo(x: (Lol, Lol)) { //~| 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` + //~| HELP: you might have meant to use the similarly named unit variant `None` //~| HELP: you might have meant to pattern match on the similarly named } match Some(42) { diff --git a/tests/ui/or-patterns/binding-typo-2.stderr b/tests/ui/or-patterns/binding-typo-2.stderr index fc6d02d65a4..81ea6e6b1bc 100644 --- a/tests/ui/or-patterns/binding-typo-2.stderr +++ b/tests/ui/or-patterns/binding-typo-2.stderr @@ -34,10 +34,11 @@ LL | Non | None => {} | | | variable not in all patterns | -help: you might have meant to use the similarly named previously used binding `None` +help: you might have meant to use the similarly named unit variant `None` + | +LL - Non | None => {} +LL + core::option::Option::None | None => {} | -LL | None | None => {} - | + error[E0408]: variable `Non` is not bound in all patterns --> $DIR/binding-typo-2.rs:54:15 diff --git a/tests/ui/or-patterns/mismatched-bindings-async-fn.stderr b/tests/ui/or-patterns/mismatched-bindings-async-fn.stderr index 523cdf959e7..81602fffa8d 100644 --- a/tests/ui/or-patterns/mismatched-bindings-async-fn.stderr +++ b/tests/ui/or-patterns/mismatched-bindings-async-fn.stderr @@ -5,12 +5,6 @@ LL | async fn a((x | s): String) {} | ^ - variable not in all patterns | | | pattern doesn't bind `s` - | -help: you might have meant to use the similarly named previously used binding `x` - | -LL - async fn a((x | s): String) {} -LL + async fn a((x | x): String) {} - | error[E0408]: variable `x` is not bound in all patterns --> $DIR/mismatched-bindings-async-fn.rs:4:17 @@ -19,12 +13,6 @@ LL | async fn a((x | s): String) {} | - ^ pattern doesn't bind `x` | | | variable not in all patterns - | -help: you might have meant to use the similarly named previously used binding `s` - | -LL - async fn a((x | s): String) {} -LL + async fn a((s | s): String) {} - | error[E0408]: variable `s` is not bound in all patterns --> $DIR/mismatched-bindings-async-fn.rs:9:10 @@ -33,12 +21,6 @@ LL | let (x | s) = String::new(); | ^ - variable not in all patterns | | | pattern doesn't bind `s` - | -help: you might have meant to use the similarly named previously used binding `x` - | -LL - let (x | s) = String::new(); -LL + let (x | x) = String::new(); - | error[E0408]: variable `x` is not bound in all patterns --> $DIR/mismatched-bindings-async-fn.rs:9:14 @@ -47,12 +29,6 @@ LL | let (x | s) = String::new(); | - ^ pattern doesn't bind `x` | | | variable not in all patterns - | -help: you might have meant to use the similarly named previously used binding `s` - | -LL - let (x | s) = String::new(); -LL + let (s | s) = String::new(); - | error: aborting due to 4 previous errors diff --git a/tests/ui/or-patterns/nested-undelimited-precedence.stderr b/tests/ui/or-patterns/nested-undelimited-precedence.stderr index 10dbc9d16c0..0835ca1929f 100644 --- a/tests/ui/or-patterns/nested-undelimited-precedence.stderr +++ b/tests/ui/or-patterns/nested-undelimited-precedence.stderr @@ -60,12 +60,6 @@ LL | let b @ A | B: E = A; | - ^ pattern doesn't bind `b` | | | variable not in all patterns - | -help: you might have meant to use the similarly named previously used binding `B` - | -LL - let b @ A | B: E = A; -LL + let B @ A | B: E = A; - | error[E0308]: mismatched types --> $DIR/nested-undelimited-precedence.rs:34:9 diff --git a/tests/ui/resolve/resolve-inconsistent-names.rs b/tests/ui/resolve/resolve-inconsistent-names.rs index bf74a4ba0f3..96504720e83 100644 --- a/tests/ui/resolve/resolve-inconsistent-names.rs +++ b/tests/ui/resolve/resolve-inconsistent-names.rs @@ -12,8 +12,6 @@ fn main() { match y { a | b => {} //~ ERROR variable `a` is not bound in all patterns //~| ERROR variable `b` is not bound in all patterns - //~| HELP you might have meant to use the similarly named previously used binding `a` - //~| HELP you might have meant to use the similarly named previously used binding `b` } let x = (E::A, E::B); diff --git a/tests/ui/resolve/resolve-inconsistent-names.stderr b/tests/ui/resolve/resolve-inconsistent-names.stderr index 152c16ad404..d2333150961 100644 --- a/tests/ui/resolve/resolve-inconsistent-names.stderr +++ b/tests/ui/resolve/resolve-inconsistent-names.stderr @@ -5,12 +5,6 @@ LL | a | b => {} | ^ - variable not in all patterns | | | pattern doesn't bind `b` - | -help: you might have meant to use the similarly named previously used binding `a` - | -LL - a | b => {} -LL + a | a => {} - | error[E0408]: variable `a` is not bound in all patterns --> $DIR/resolve-inconsistent-names.rs:13:13 @@ -19,15 +13,9 @@ LL | a | b => {} | - ^ pattern doesn't bind `a` | | | variable not in all patterns - | -help: you might have meant to use the similarly named previously used binding `b` - | -LL - a | b => {} -LL + b | b => {} - | error[E0408]: variable `c` is not bound in all patterns - --> $DIR/resolve-inconsistent-names.rs:21:9 + --> $DIR/resolve-inconsistent-names.rs:19:9 | LL | (A, B) | (ref B, c) | (c, A) => () | ^^^^^^ - - variable not in all patterns @@ -36,7 +24,7 @@ LL | (A, B) | (ref B, c) | (c, A) => () | pattern doesn't bind `c` error[E0408]: variable `A` is not bound in all patterns - --> $DIR/resolve-inconsistent-names.rs:21:18 + --> $DIR/resolve-inconsistent-names.rs:19:18 | LL | (A, B) | (ref B, c) | (c, A) => () | - ^^^^^^^^^^ - variable not in all patterns @@ -50,7 +38,7 @@ LL | (E::A, B) | (ref B, c) | (c, A) => () | +++ error[E0408]: variable `B` is not bound in all patterns - --> $DIR/resolve-inconsistent-names.rs:21:31 + --> $DIR/resolve-inconsistent-names.rs:19:31 | LL | (A, B) | (ref B, c) | (c, A) => () | - - ^^^^^^ pattern doesn't bind `B` @@ -59,7 +47,7 @@ LL | (A, B) | (ref B, c) | (c, A) => () | variable not in all patterns error[E0409]: variable `B` is bound inconsistently across alternatives separated by `|` - --> $DIR/resolve-inconsistent-names.rs:21:23 + --> $DIR/resolve-inconsistent-names.rs:19:23 | LL | (A, B) | (ref B, c) | (c, A) => () | - ^ bound in different ways @@ -67,7 +55,7 @@ LL | (A, B) | (ref B, c) | (c, A) => () | first binding error[E0408]: variable `Const2` is not bound in all patterns - --> $DIR/resolve-inconsistent-names.rs:33:9 + --> $DIR/resolve-inconsistent-names.rs:31:9 | LL | (CONST1, _) | (_, Const2) => () | ^^^^^^^^^^^ ------ variable not in all patterns @@ -80,7 +68,7 @@ LL | (CONST1, _) | (_, m::Const2) => () | +++ error[E0408]: variable `CONST1` is not bound in all patterns - --> $DIR/resolve-inconsistent-names.rs:33:23 + --> $DIR/resolve-inconsistent-names.rs:31:23 | LL | (CONST1, _) | (_, Const2) => () | ------ ^^^^^^^^^^^ pattern doesn't bind `CONST1` @@ -94,7 +82,7 @@ LL | const CONST1: usize = 10; | ^^^^^^^^^^^^^^^^^^^^^^^^^ not accessible error[E0308]: mismatched types - --> $DIR/resolve-inconsistent-names.rs:21:19 + --> $DIR/resolve-inconsistent-names.rs:19:19 | LL | match x { | - this expression has type `(E, E)` -- cgit 1.4.1-3-g733a5