Skip to content

Commit e0b35c3

Browse files
committed
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) => {} | ```
1 parent 41a79f1 commit e0b35c3

File tree

12 files changed

+243
-54
lines changed

12 files changed

+243
-54
lines changed

compiler/rustc_resolve/messages.ftl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,8 @@ resolve_variable_bound_with_different_mode =
470470
.label = bound in different ways
471471
.first_binding_span = first binding
472472
473+
resolve_variable_is_a_typo = you might have meant to use the similarly named previously used binding `{$typo}`
474+
473475
resolve_variable_is_not_bound_in_all_patterns =
474476
variable `{$name}` is not bound in all patterns
475477

compiler/rustc_resolve/src/diagnostics.rs

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -661,8 +661,12 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
661661
ResolutionError::VariableNotBoundInPattern(binding_error, parent_scope) => {
662662
let BindingError { name, target, origin, could_be_path } = binding_error;
663663

664-
let target_sp = target.iter().copied().collect::<Vec<_>>();
665-
let origin_sp = origin.iter().copied().collect::<Vec<_>>();
664+
let mut target_sp = target.iter().map(|pat| pat.span).collect::<Vec<_>>();
665+
target_sp.sort();
666+
target_sp.dedup();
667+
let mut origin_sp = origin.iter().map(|(span, _)| *span).collect::<Vec<_>>();
668+
origin_sp.sort();
669+
origin_sp.dedup();
666670

667671
let msp = MultiSpan::from_spans(target_sp.clone());
668672
let mut err = self
@@ -671,8 +675,28 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
671675
for sp in target_sp {
672676
err.subdiagnostic(errors::PatternDoesntBindName { span: sp, name });
673677
}
674-
for sp in origin_sp {
675-
err.subdiagnostic(errors::VariableNotInAllPatterns { span: sp });
678+
for sp in &origin_sp {
679+
err.subdiagnostic(errors::VariableNotInAllPatterns { span: *sp });
680+
}
681+
let mut target_visitor = BindingVisitor::default();
682+
for pat in &target {
683+
target_visitor.visit_pat(pat);
684+
}
685+
let mut origin_visitor = BindingVisitor::default();
686+
for (_, pat) in &origin {
687+
origin_visitor.visit_pat(pat);
688+
}
689+
// Find if the binding could have been a typo
690+
let mut suggested_typo = false;
691+
if let Some(typo) = find_best_match_for_name(
692+
#[allow(rustc::potential_query_instability)]
693+
&target_visitor.identifiers.iter().map(|name| *name).collect::<Vec<Symbol>>(),
694+
name.name,
695+
None,
696+
) && !origin_visitor.identifiers.contains(&typo)
697+
{
698+
err.subdiagnostic(errors::PatternBindingTypo { spans: origin_sp, typo });
699+
suggested_typo = true;
676700
}
677701
if could_be_path {
678702
let import_suggestions = self.lookup_import_candidates(
@@ -693,7 +717,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
693717
},
694718
);
695719

696-
if import_suggestions.is_empty() {
720+
if import_suggestions.is_empty() && !suggested_typo {
697721
let help_msg = format!(
698722
"if you meant to match on a variant or a `const` item, consider \
699723
making the path in the pattern qualified: `path::to::ModOrType::{name}`",
@@ -3395,7 +3419,7 @@ impl UsePlacementFinder {
33953419
}
33963420
}
33973421

3398-
impl<'tcx> visit::Visitor<'tcx> for UsePlacementFinder {
3422+
impl<'tcx> Visitor<'tcx> for UsePlacementFinder {
33993423
fn visit_crate(&mut self, c: &Crate) {
34003424
if self.target_module == CRATE_NODE_ID {
34013425
let inject = c.spans.inject_use_span;
@@ -3423,6 +3447,22 @@ impl<'tcx> visit::Visitor<'tcx> for UsePlacementFinder {
34233447
}
34243448
}
34253449

3450+
#[derive(Default)]
3451+
struct BindingVisitor {
3452+
identifiers: FxHashSet<Symbol>,
3453+
spans: FxHashMap<Symbol, Vec<Span>>,
3454+
}
3455+
3456+
impl<'tcx> Visitor<'tcx> for BindingVisitor {
3457+
fn visit_pat(&mut self, pat: &ast::Pat) {
3458+
if let ast::PatKind::Ident(_, ident, _) = pat.kind {
3459+
self.identifiers.insert(ident.name);
3460+
self.spans.entry(ident.name).or_default().push(ident.span);
3461+
}
3462+
visit::walk_pat(self, pat);
3463+
}
3464+
}
3465+
34263466
fn search_for_any_use_in_items(items: &[Box<ast::Item>]) -> Option<Span> {
34273467
for item in items {
34283468
if let ItemKind::Use(..) = item.kind

compiler/rustc_resolve/src/errors.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -986,6 +986,18 @@ pub(crate) struct VariableNotInAllPatterns {
986986
pub(crate) span: Span,
987987
}
988988

989+
#[derive(Subdiagnostic)]
990+
#[multipart_suggestion(
991+
resolve_variable_is_a_typo,
992+
applicability = "maybe-incorrect",
993+
style = "verbose"
994+
)]
995+
pub(crate) struct PatternBindingTypo {
996+
#[suggestion_part(code = "{typo}")]
997+
pub(crate) spans: Vec<Span>,
998+
pub(crate) typo: Symbol,
999+
}
1000+
9891001
#[derive(Diagnostic)]
9901002
#[diag(resolve_name_defined_multiple_time)]
9911003
#[note]

compiler/rustc_resolve/src/late.rs

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
99
use std::assert_matches::debug_assert_matches;
1010
use std::borrow::Cow;
11-
use std::collections::BTreeSet;
1211
use std::collections::hash_map::Entry;
1312
use std::mem::{replace, swap, take};
1413

@@ -3682,31 +3681,30 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
36823681
// 2) Record any missing bindings or binding mode inconsistencies.
36833682
for (map_outer, pat_outer) in not_never_pats.iter() {
36843683
// Check against all arms except for the same pattern which is always self-consistent.
3685-
let inners = not_never_pats
3686-
.iter()
3687-
.filter(|(_, pat)| pat.id != pat_outer.id)
3688-
.flat_map(|(map, _)| map);
3689-
3690-
for (&name, binding_inner) in inners {
3691-
match map_outer.get(&name) {
3692-
None => {
3693-
// The inner binding is missing in the outer.
3694-
let binding_error =
3695-
missing_vars.entry(name).or_insert_with(|| BindingError {
3696-
name,
3697-
origin: BTreeSet::new(),
3698-
target: BTreeSet::new(),
3699-
could_be_path: name.as_str().starts_with(char::is_uppercase),
3700-
});
3701-
binding_error.origin.insert(binding_inner.span);
3702-
binding_error.target.insert(pat_outer.span);
3703-
}
3704-
Some(binding_outer) => {
3705-
if binding_outer.annotation != binding_inner.annotation {
3706-
// The binding modes in the outer and inner bindings differ.
3707-
inconsistent_vars
3708-
.entry(name)
3709-
.or_insert((binding_inner.span, binding_outer.span));
3684+
let inners = not_never_pats.iter().filter(|(_, pat)| pat.id != pat_outer.id);
3685+
3686+
for (map, pat) in inners {
3687+
for (&name, binding_inner) in map {
3688+
match map_outer.get(&name) {
3689+
None => {
3690+
// The inner binding is missing in the outer.
3691+
let binding_error =
3692+
missing_vars.entry(name).or_insert_with(|| BindingError {
3693+
name,
3694+
origin: Default::default(),
3695+
target: Default::default(),
3696+
could_be_path: name.as_str().starts_with(char::is_uppercase),
3697+
});
3698+
binding_error.origin.push((binding_inner.span, (***pat).clone()));
3699+
binding_error.target.push((***pat_outer).clone());
3700+
}
3701+
Some(binding_outer) => {
3702+
if binding_outer.annotation != binding_inner.annotation {
3703+
// The binding modes in the outer and inner bindings differ.
3704+
inconsistent_vars
3705+
.entry(name)
3706+
.or_insert((binding_inner.span, binding_outer.span));
3707+
}
37103708
}
37113709
}
37123710
}
@@ -3719,7 +3717,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
37193717
v.could_be_path = false;
37203718
}
37213719
self.report_error(
3722-
*v.origin.iter().next().unwrap(),
3720+
v.origin.iter().next().unwrap().0,
37233721
ResolutionError::VariableNotBoundInPattern(v, self.parent_scope),
37243722
);
37253723
}

compiler/rustc_resolve/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,8 +230,8 @@ enum Used {
230230
#[derive(Debug)]
231231
struct BindingError {
232232
name: Ident,
233-
origin: BTreeSet<Span>,
234-
target: BTreeSet<Span>,
233+
origin: Vec<(Span, ast::Pat)>,
234+
target: Vec<ast::Pat>,
235235
could_be_path: bool,
236236
}
237237

tests/ui/or-patterns/mismatched-bindings-async-fn.stderr

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ LL | async fn a((x | s): String) {}
55
| ^ - variable not in all patterns
66
| |
77
| pattern doesn't bind `s`
8+
|
9+
help: you might have meant to use the similarly named previously used binding `x`
10+
|
11+
LL - async fn a((x | s): String) {}
12+
LL + async fn a((x | x): String) {}
13+
|
814

915
error[E0408]: variable `x` is not bound in all patterns
1016
--> $DIR/mismatched-bindings-async-fn.rs:4:17
@@ -13,6 +19,12 @@ LL | async fn a((x | s): String) {}
1319
| - ^ pattern doesn't bind `x`
1420
| |
1521
| variable not in all patterns
22+
|
23+
help: you might have meant to use the similarly named previously used binding `s`
24+
|
25+
LL - async fn a((x | s): String) {}
26+
LL + async fn a((s | s): String) {}
27+
|
1628

1729
error[E0408]: variable `s` is not bound in all patterns
1830
--> $DIR/mismatched-bindings-async-fn.rs:9:10
@@ -21,6 +33,12 @@ LL | let (x | s) = String::new();
2133
| ^ - variable not in all patterns
2234
| |
2335
| pattern doesn't bind `s`
36+
|
37+
help: you might have meant to use the similarly named previously used binding `x`
38+
|
39+
LL - let (x | s) = String::new();
40+
LL + let (x | x) = String::new();
41+
|
2442

2543
error[E0408]: variable `x` is not bound in all patterns
2644
--> $DIR/mismatched-bindings-async-fn.rs:9:14
@@ -29,6 +47,12 @@ LL | let (x | s) = String::new();
2947
| - ^ pattern doesn't bind `x`
3048
| |
3149
| variable not in all patterns
50+
|
51+
help: you might have meant to use the similarly named previously used binding `s`
52+
|
53+
LL - let (x | s) = String::new();
54+
LL + let (s | s) = String::new();
55+
|
3256

3357
error: aborting due to 4 previous errors
3458

0 commit comments

Comments
 (0)