Skip to content

Commit cb9cd8f

Browse files
committed
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(_))=> {} | ```
1 parent bd0ea61 commit cb9cd8f

File tree

6 files changed

+361
-5
lines changed

6 files changed

+361
-5
lines changed

compiler/rustc_resolve/src/diagnostics.rs

Lines changed: 111 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -718,10 +718,86 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
718718
},
719719
);
720720

721+
if import_suggestions.is_empty() && !suggested_typo {
722+
let kinds = [
723+
DefKind::Ctor(CtorOf::Variant, CtorKind::Const),
724+
DefKind::Ctor(CtorOf::Struct, CtorKind::Const),
725+
DefKind::Const,
726+
DefKind::AssocConst,
727+
];
728+
let mut local_names = vec![];
729+
self.add_module_candidates(
730+
parent_scope.module,
731+
&mut local_names,
732+
&|res| matches!(res, Res::Def(_, _)),
733+
None,
734+
);
735+
let local_names: FxHashSet<_> = local_names
736+
.into_iter()
737+
.filter_map(|s| match s.res {
738+
Res::Def(_, def_id) => Some(def_id),
739+
_ => None,
740+
})
741+
.collect();
742+
743+
let mut local_suggestions = vec![];
744+
let mut suggestions = vec![];
745+
for kind in kinds {
746+
if let Some(suggestion) = self.early_lookup_typo_candidate(
747+
ScopeSet::All(Namespace::ValueNS),
748+
&parent_scope,
749+
name,
750+
&|res: Res| match res {
751+
Res::Def(k, _) => k == kind,
752+
_ => false,
753+
},
754+
) && let Res::Def(kind, mut def_id) = suggestion.res
755+
{
756+
if let DefKind::Ctor(_, _) = kind {
757+
def_id = self.tcx.parent(def_id);
758+
}
759+
let kind = kind.descr(def_id);
760+
if local_names.contains(&def_id) {
761+
// The item is available in the current scope. Very likely to
762+
// be a typo. Don't use the full path.
763+
local_suggestions.push((
764+
suggestion.candidate,
765+
suggestion.candidate.to_string(),
766+
kind,
767+
));
768+
} else {
769+
suggestions.push((
770+
suggestion.candidate,
771+
self.def_path_str(def_id),
772+
kind,
773+
));
774+
}
775+
}
776+
}
777+
let suggestions = if !local_suggestions.is_empty() {
778+
// There is at least one item available in the current scope that is a
779+
// likely typo. We only show those.
780+
local_suggestions
781+
} else {
782+
suggestions
783+
};
784+
for (name, sugg, kind) in suggestions {
785+
err.span_suggestion_verbose(
786+
span,
787+
format!(
788+
"you might have meant to use the similarly named {kind} `{name}`",
789+
),
790+
sugg,
791+
Applicability::MaybeIncorrect,
792+
);
793+
suggested_typo = true;
794+
}
795+
}
721796
if import_suggestions.is_empty() && !suggested_typo {
722797
let help_msg = format!(
723-
"if you meant to match on a variant or a `const` item, consider \
724-
making the path in the pattern qualified: `path::to::ModOrType::{name}`",
798+
"if you meant to match on a unit struct, unit variant or a `const` \
799+
item, consider making the path in the pattern qualified: \
800+
`path::to::ModOrType::{name}`",
725801
);
726802
err.span_help(span, help_msg);
727803
}
@@ -1041,6 +1117,39 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
10411117
.emit()
10421118
}
10431119

1120+
fn def_path_str(&self, mut def_id: DefId) -> String {
1121+
// We can't use `def_path_str` in resolve.
1122+
let mut path = vec![def_id];
1123+
while let Some(parent) = self.tcx.opt_parent(def_id) {
1124+
def_id = parent;
1125+
path.push(def_id);
1126+
if def_id.is_top_level_module() {
1127+
break;
1128+
}
1129+
}
1130+
// We will only suggest importing directly if it is accessible through that path.
1131+
path.into_iter()
1132+
.rev()
1133+
.map(|def_id| {
1134+
self.tcx
1135+
.opt_item_name(def_id)
1136+
.map(|name| {
1137+
match (
1138+
def_id.is_top_level_module(),
1139+
def_id.is_local(),
1140+
self.tcx.sess.edition(),
1141+
) {
1142+
(true, true, Edition::Edition2015) => String::new(),
1143+
(true, true, _) => kw::Crate.to_string(),
1144+
(true, false, _) | (false, _, _) => name.to_string(),
1145+
}
1146+
})
1147+
.unwrap_or_else(|| "_".to_string())
1148+
})
1149+
.collect::<Vec<String>>()
1150+
.join("::")
1151+
}
1152+
10441153
pub(crate) fn add_scope_set_candidates(
10451154
&mut self,
10461155
suggestions: &mut Vec<TypoSuggestion>,
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Issue #51976
2+
#![deny(unused_variables)] //~ NOTE: the lint level is defined here
3+
enum Lol {
4+
Foo,
5+
Bar,
6+
}
7+
const Bat: () = ();
8+
struct Bay;
9+
10+
fn foo(x: (Lol, Lol)) {
11+
use Lol::*;
12+
match &x {
13+
(Foo, Bar) | (Ban, Foo) => {}
14+
//~^ ERROR: variable `Ban` is not bound in all patterns
15+
//~| HELP: you might have meant to use the similarly named previously used binding `Bar`
16+
//~| NOTE: pattern doesn't bind `Ban`
17+
//~| NOTE: variable not in all patterns
18+
//~| ERROR: variable `Ban` is assigned to, but never used
19+
//~| NOTE: consider using `_Ban` instead
20+
_ => {}
21+
}
22+
match &x {
23+
(Foo, _) | (Ban, Foo) => {}
24+
//~^ ERROR: variable `Ban` is not bound in all patterns
25+
//~| HELP: you might have meant to use the similarly named unit variant `Bar`
26+
//~| NOTE: pattern doesn't bind `Ban`
27+
//~| NOTE: variable not in all patterns
28+
//~| ERROR: variable `Ban` is assigned to, but never used
29+
//~| NOTE: consider using `_Ban` instead
30+
_ => {}
31+
}
32+
match Some(42) {
33+
Non => {}
34+
//~^ ERROR: unused variable: `Non`
35+
//~| HELP: if this is intentional, prefix it with an underscore
36+
_ => {}
37+
}
38+
match Some(42) {
39+
Non | None => {}
40+
//~^ ERROR: unused variable: `Non`
41+
//~| HELP: if this is intentional, prefix it with an underscore
42+
//~| ERROR: variable `Non` is not bound in all patterns [E0408]
43+
//~| NOTE: pattern doesn't bind `Non`
44+
//~| NOTE: variable not in all patterns
45+
//~| HELP: you might have meant to use the similarly named previously used binding `None`
46+
_ => {}
47+
}
48+
match Some(42) {
49+
Non | Some(_) => {}
50+
//~^ ERROR: unused variable: `Non`
51+
//~| HELP: if this is intentional, prefix it with an underscore
52+
//~| ERROR: variable `Non` is not bound in all patterns [E0408]
53+
//~| NOTE: pattern doesn't bind `Non`
54+
//~| NOTE: variable not in all patterns
55+
//~| HELP: you might have meant to use the similarly named unit variant `None`
56+
_ => {}
57+
}
58+
}
59+
fn bar(x: (Lol, Lol)) {
60+
use Lol::*;
61+
use Bat;
62+
use Bay;
63+
use std::option::Option::None;
64+
match &x {
65+
(Foo, _) | (Ban, Foo) => {}
66+
//~^ ERROR: variable `Ban` is not bound in all patterns
67+
//~| HELP: you might have meant to use the similarly named unit variant `Bar`
68+
//~| HELP: you might have meant to use the similarly named unit struct `Bay`
69+
//~| HELP: you might have meant to use the similarly named constant `Bat`
70+
//~| NOTE: pattern doesn't bind `Ban`
71+
//~| NOTE: variable not in all patterns
72+
//~| ERROR: variable `Ban` is assigned to, but never used
73+
//~| NOTE: consider using `_Ban` instead
74+
_ => {}
75+
}
76+
}
77+
fn baz(x: (Lol, Lol)) {
78+
use Lol::*;
79+
use Bat;
80+
match &x {
81+
(Foo, _) | (Ban, Foo) => {}
82+
//~^ ERROR: variable `Ban` is not bound in all patterns
83+
//~| HELP: you might have meant to use the similarly named unit variant `Bar`
84+
//~| HELP: you might have meant to use the similarly named constant `Bat`
85+
//~| NOTE: pattern doesn't bind `Ban`
86+
//~| NOTE: variable not in all patterns
87+
//~| ERROR: variable `Ban` is assigned to, but never used
88+
//~| NOTE: consider using `_Ban` instead
89+
_ => {}
90+
}
91+
}
92+
93+
fn main() {
94+
use Lol::*;
95+
foo((Foo, Bar));
96+
bar((Foo, Bar));
97+
baz((Foo, Bar));
98+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
error[E0408]: variable `Ban` is not bound in all patterns
2+
--> $DIR/binding-typo-2.rs:13:9
3+
|
4+
LL | (Foo, Bar) | (Ban, Foo) => {}
5+
| ^^^^^^^^^^ --- variable not in all patterns
6+
| |
7+
| pattern doesn't bind `Ban`
8+
|
9+
help: you might have meant to use the similarly named previously used binding `Bar`
10+
|
11+
LL - (Foo, Bar) | (Ban, Foo) => {}
12+
LL + (Foo, Bar) | (Bar, Foo) => {}
13+
|
14+
15+
error[E0408]: variable `Ban` is not bound in all patterns
16+
--> $DIR/binding-typo-2.rs:23:9
17+
|
18+
LL | (Foo, _) | (Ban, Foo) => {}
19+
| ^^^^^^^^ --- variable not in all patterns
20+
| |
21+
| pattern doesn't bind `Ban`
22+
|
23+
help: you might have meant to use the similarly named unit variant `Bar`
24+
|
25+
LL - (Foo, _) | (Ban, Foo) => {}
26+
LL + (Foo, _) | (Bar, Foo) => {}
27+
|
28+
help: you might have meant to use the similarly named unit struct `Bay`
29+
|
30+
LL - (Foo, _) | (Ban, Foo) => {}
31+
LL + (Foo, _) | (::Bay, Foo) => {}
32+
|
33+
help: you might have meant to use the similarly named constant `Bat`
34+
|
35+
LL - (Foo, _) | (Ban, Foo) => {}
36+
LL + (Foo, _) | (::Bat, Foo) => {}
37+
|
38+
39+
error[E0408]: variable `Non` is not bound in all patterns
40+
--> $DIR/binding-typo-2.rs:41:16
41+
|
42+
LL | (Non | None)=> {}
43+
| --- ^^^^ pattern doesn't bind `Non`
44+
| |
45+
| variable not in all patterns
46+
|
47+
help: you might have meant to use the similarly named previously used binding `None`
48+
|
49+
LL | (None | None)=> {}
50+
| +
51+
52+
error[E0408]: variable `Non` is not bound in all patterns
53+
--> $DIR/binding-typo-2.rs:51:16
54+
|
55+
LL | (Non | Some(_))=> {}
56+
| --- ^^^^^^^ pattern doesn't bind `Non`
57+
| |
58+
| variable not in all patterns
59+
|
60+
help: you might have meant to use the similarly named unit variant `None`
61+
|
62+
LL - (Non | Some(_))=> {}
63+
LL + (core::option::Option::None | Some(_))=> {}
64+
|
65+
66+
error: variable `Ban` is assigned to, but never used
67+
--> $DIR/binding-typo-2.rs:13:23
68+
|
69+
LL | (Foo, Bar) | (Ban, Foo) => {}
70+
| ^^^
71+
|
72+
= note: consider using `_Ban` instead
73+
note: the lint level is defined here
74+
--> $DIR/binding-typo-2.rs:2:9
75+
|
76+
LL | #![deny(unused_variables)]
77+
| ^^^^^^^^^^^^^^^^
78+
79+
error: variable `Ban` is assigned to, but never used
80+
--> $DIR/binding-typo-2.rs:23:21
81+
|
82+
LL | (Foo, _) | (Ban, Foo) => {}
83+
| ^^^
84+
|
85+
= note: consider using `_Ban` instead
86+
87+
error: unused variable: `Non`
88+
--> $DIR/binding-typo-2.rs:35:9
89+
|
90+
LL | Non => {}
91+
| ^^^ help: if this is intentional, prefix it with an underscore: `_Non`
92+
93+
error: unused variable: `Non`
94+
--> $DIR/binding-typo-2.rs:41:10
95+
|
96+
LL | (Non | None)=> {}
97+
| ^^^ help: if this is intentional, prefix it with an underscore: `_Non`
98+
99+
error: unused variable: `Non`
100+
--> $DIR/binding-typo-2.rs:51:10
101+
|
102+
LL | (Non | Some(_))=> {}
103+
| ^^^ help: if this is intentional, prefix it with an underscore: `_Non`
104+
105+
error: aborting due to 9 previous errors
106+
107+
For more information about this error, try `rustc --explain E0408`.

tests/ui/or-patterns/binding-typo.fixed

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ enum Lol {
88

99
fn foo(x: (Lol, Lol)) {
1010
use Lol::*;
11-
match x {
11+
match &x {
1212
(Foo, Bar) | (Bar, Foo) => {}
1313
//~^ ERROR: variable `Ban` is not bound in all patterns
1414
//~| HELP: you might have meant to use the similarly named previously used binding `Bar`
@@ -18,6 +18,16 @@ fn foo(x: (Lol, Lol)) {
1818
//~| NOTE: consider using `_Ban` instead
1919
_ => {}
2020
}
21+
match &x {
22+
(Foo, _) | (Bar, Foo) => {}
23+
//~^ ERROR: variable `Ban` is not bound in all patterns
24+
//~| HELP: you might have meant to use the similarly named unit variant `Bar`
25+
//~| NOTE: pattern doesn't bind `Ban`
26+
//~| NOTE: variable not in all patterns
27+
//~| ERROR: variable `Ban` is assigned to, but never used
28+
//~| NOTE: consider using `_Ban` instead
29+
_ => {}
30+
}
2131
}
2232

2333
fn main() {

tests/ui/or-patterns/binding-typo.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ enum Lol {
88

99
fn foo(x: (Lol, Lol)) {
1010
use Lol::*;
11-
match x {
11+
match &x {
1212
(Foo, Bar) | (Ban, Foo) => {}
1313
//~^ ERROR: variable `Ban` is not bound in all patterns
1414
//~| HELP: you might have meant to use the similarly named previously used binding `Bar`
@@ -18,6 +18,16 @@ fn foo(x: (Lol, Lol)) {
1818
//~| NOTE: consider using `_Ban` instead
1919
_ => {}
2020
}
21+
match &x {
22+
(Foo, _) | (Ban, Foo) => {}
23+
//~^ ERROR: variable `Ban` is not bound in all patterns
24+
//~| HELP: you might have meant to use the similarly named unit variant `Bar`
25+
//~| NOTE: pattern doesn't bind `Ban`
26+
//~| NOTE: variable not in all patterns
27+
//~| ERROR: variable `Ban` is assigned to, but never used
28+
//~| NOTE: consider using `_Ban` instead
29+
_ => {}
30+
}
2131
}
2232

2333
fn main() {

0 commit comments

Comments
 (0)