Skip to content

Commit 00f6fe1

Browse files
committed
On unused binding in pattern, suggest unit struct/variant with similar name
When encountering a typo in a pattern that gets interpreted as an unused binding, look for unit struct/variant of the same type as the binding: ``` error: unused variable: `Non` --> $DIR/binding-typo-2.rs:36:9 | LL | Non => {} | ^^^ | help: if this is intentional, prefix it with an underscore | LL | _Non => {} | + help: you might have meant to pattern match on the similarly named variant `None` | LL - Non => {} LL + std::prelude::v1::None => {} | ```
1 parent cb9cd8f commit 00f6fe1

12 files changed

+279
-105
lines changed

compiler/rustc_passes/messages.ftl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,7 @@ passes_unused_var_maybe_capture_ref = unused variable: `{$name}`
681681
682682
passes_unused_var_remove_field = unused variable: `{$name}`
683683
passes_unused_var_remove_field_suggestion = try removing the field
684+
passes_unused_var_typo = you might have meant to pattern match on the similarly named {$kind} `{$item_name}`
684685
685686
passes_unused_variable_args_in_macro = `{$name}` is captured in macro and introduced a unused variable
686687

compiler/rustc_passes/src/errors.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1346,6 +1346,22 @@ pub(crate) struct UnusedVarRemoveFieldSugg {
13461346
#[note]
13471347
pub(crate) struct UnusedVarAssignedOnly {
13481348
pub name: String,
1349+
#[subdiagnostic]
1350+
pub typo: Option<PatternTypo>,
1351+
}
1352+
1353+
#[derive(Subdiagnostic)]
1354+
#[multipart_suggestion(
1355+
passes_unused_var_typo,
1356+
style = "verbose",
1357+
applicability = "machine-applicable"
1358+
)]
1359+
pub(crate) struct PatternTypo {
1360+
#[suggestion_part(code = "{code}")]
1361+
pub span: Span,
1362+
pub code: String,
1363+
pub item_name: String,
1364+
pub kind: String,
13491365
}
13501366

13511367
#[derive(LintDiagnostic)]
@@ -1413,6 +1429,8 @@ pub(crate) struct UnusedVariableTryPrefix {
14131429
#[subdiagnostic]
14141430
pub sugg: UnusedVariableSugg,
14151431
pub name: String,
1432+
#[subdiagnostic]
1433+
pub typo: Option<PatternTypo>,
14161434
}
14171435

14181436
#[derive(Subdiagnostic)]

compiler/rustc_passes/src/liveness.rs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,10 @@ use rustc_hir::{Expr, HirId, HirIdMap, HirIdSet, find_attr};
9595
use rustc_index::IndexVec;
9696
use rustc_middle::query::Providers;
9797
use rustc_middle::span_bug;
98+
use rustc_middle::ty::print::with_no_trimmed_paths;
9899
use rustc_middle::ty::{self, RootVariableMinCaptureList, Ty, TyCtxt};
99100
use rustc_session::lint;
101+
use rustc_span::edit_distance::find_best_match_for_name;
100102
use rustc_span::{BytePos, Span, Symbol};
101103
use tracing::{debug, instrument};
102104

@@ -1688,6 +1690,34 @@ impl<'tcx> Liveness<'_, 'tcx> {
16881690
let is_assigned =
16891691
if ln == self.exit_ln { false } else { self.assigned_on_exit(ln, var) };
16901692

1693+
let mut typo = None;
1694+
for (hir_id, _, span) in &hir_ids_and_spans {
1695+
let ty = self.typeck_results.node_type(*hir_id);
1696+
if let ty::Adt(adt, _) = ty.peel_refs().kind() {
1697+
let name = Symbol::intern(&name);
1698+
let adt_def = self.ir.tcx.adt_def(adt.did());
1699+
let variant_names: Vec<_> = adt_def
1700+
.variants()
1701+
.iter()
1702+
.filter(|v| matches!(v.ctor, Some((CtorKind::Const, _))))
1703+
.map(|v| v.name)
1704+
.collect();
1705+
if let Some(name) = find_best_match_for_name(&variant_names, name, None)
1706+
&& let Some(variant) = adt_def.variants().iter().find(|v| {
1707+
v.name == name && matches!(v.ctor, Some((CtorKind::Const, _)))
1708+
})
1709+
{
1710+
typo = Some(errors::PatternTypo {
1711+
span: *span,
1712+
code: with_no_trimmed_paths!(self.ir.tcx.def_path_str(variant.def_id)),
1713+
kind: self.ir.tcx.def_descr(variant.def_id).to_string(),
1714+
item_name: variant.name.to_string(),
1715+
});
1716+
}
1717+
}
1718+
}
1719+
// FIXME(estebank): look for consts of the same type with similar names as well, not
1720+
// just unit structs and variants.
16911721
if is_assigned {
16921722
self.ir.tcx.emit_node_span_lint(
16931723
lint::builtin::UNUSED_VARIABLES,
@@ -1696,7 +1726,7 @@ impl<'tcx> Liveness<'_, 'tcx> {
16961726
.into_iter()
16971727
.map(|(_, _, ident_span)| ident_span)
16981728
.collect::<Vec<_>>(),
1699-
errors::UnusedVarAssignedOnly { name },
1729+
errors::UnusedVarAssignedOnly { name, typo },
17001730
)
17011731
} else if can_remove {
17021732
let spans = hir_ids_and_spans
@@ -1788,6 +1818,7 @@ impl<'tcx> Liveness<'_, 'tcx> {
17881818
name,
17891819
sugg,
17901820
string_interp: suggestions,
1821+
typo,
17911822
},
17921823
);
17931824
}

tests/ui/fn/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.fixed

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//@ run-rustfix
2-
#![deny(unused_assignments, unused_variables)]
2+
#![deny(unused_assignments)]
33
#![allow(unused_mut)]
44
struct Object;
55

@@ -8,15 +8,15 @@ fn change_object(object: &mut Object) { //~ HELP you might have meant to mutate
88
*object = object2; //~ ERROR mismatched types
99
}
1010

11-
fn change_object2(object: &mut Object) { //~ ERROR variable `object` is assigned to, but never used
12-
//~^ HELP you might have meant to mutate
11+
fn change_object2(object: &mut Object) {
12+
//~^ HELP you might have meant to mutate
1313
let object2 = Object;
1414
*object = object2;
1515
//~^ ERROR `object2` does not live long enough
1616
//~| ERROR value assigned to `object` is never read
1717
}
1818

19-
fn change_object3(object: &mut Object) { //~ ERROR variable `object` is assigned to, but never used
19+
fn change_object3(object: &mut Object) {
2020
//~^ HELP you might have meant to mutate
2121
let mut object2 = Object; //~ HELP consider changing this to be mutable
2222
*object = object2;

tests/ui/fn/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//@ run-rustfix
2-
#![deny(unused_assignments, unused_variables)]
2+
#![deny(unused_assignments)]
33
#![allow(unused_mut)]
44
struct Object;
55

@@ -8,15 +8,15 @@ fn change_object(mut object: &Object) { //~ HELP you might have meant to mutate
88
object = object2; //~ ERROR mismatched types
99
}
1010

11-
fn change_object2(mut object: &Object) { //~ ERROR variable `object` is assigned to, but never used
12-
//~^ HELP you might have meant to mutate
11+
fn change_object2(mut object: &Object) {
12+
//~^ HELP you might have meant to mutate
1313
let object2 = Object;
1414
object = &object2;
1515
//~^ ERROR `object2` does not live long enough
1616
//~| ERROR value assigned to `object` is never read
1717
}
1818

19-
fn change_object3(mut object: &mut Object) { //~ ERROR variable `object` is assigned to, but never used
19+
fn change_object3(mut object: &mut Object) {
2020
//~^ HELP you might have meant to mutate
2121
let object2 = Object; //~ HELP consider changing this to be mutable
2222
object = &mut object2;

tests/ui/fn/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.stderr

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ LL | object = &object2;
2323
note: the lint level is defined here
2424
--> $DIR/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs:2:9
2525
|
26-
LL | #![deny(unused_assignments, unused_variables)]
26+
LL | #![deny(unused_assignments)]
2727
| ^^^^^^^^^^^^^^^^^^
2828
help: you might have meant to mutate the pointed at value being passed in, instead of changing the reference in the local binding
2929
|
@@ -33,19 +33,6 @@ LL | let object2 = Object;
3333
LL ~ *object = object2;
3434
|
3535

36-
error: variable `object` is assigned to, but never used
37-
--> $DIR/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs:11:23
38-
|
39-
LL | fn change_object2(mut object: &Object) {
40-
| ^^^^^^
41-
|
42-
= note: consider using `_object` instead
43-
note: the lint level is defined here
44-
--> $DIR/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs:2:29
45-
|
46-
LL | #![deny(unused_assignments, unused_variables)]
47-
| ^^^^^^^^^^^^^^^^
48-
4936
error[E0597]: `object2` does not live long enough
5037
--> $DIR/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs:14:13
5138
|
@@ -77,14 +64,6 @@ LL | let object2 = Object;
7764
LL ~ *object = object2;
7865
|
7966

80-
error: variable `object` is assigned to, but never used
81-
--> $DIR/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs:19:23
82-
|
83-
LL | fn change_object3(mut object: &mut Object) {
84-
| ^^^^^^
85-
|
86-
= note: consider using `_object` instead
87-
8867
error[E0596]: cannot borrow `object2` as mutable, as it is not declared as mutable
8968
--> $DIR/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs:22:14
9069
|
@@ -96,7 +75,7 @@ help: consider changing this to be mutable
9675
LL | let mut object2 = Object;
9776
| +++
9877

99-
error: aborting due to 7 previous errors
78+
error: aborting due to 5 previous errors
10079

10180
Some errors have detailed explanations: E0308, E0596, E0597.
10281
For more information about an error, try `rustc --explain E0308`.

tests/ui/lint/non-snake-case/lint-uppercase-variables.stderr

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,37 @@ warning: unused variable: `Foo`
1616
--> $DIR/lint-uppercase-variables.rs:22:9
1717
|
1818
LL | Foo => {}
19-
| ^^^ help: if this is intentional, prefix it with an underscore: `_Foo`
19+
| ^^^
2020
|
2121
note: the lint level is defined here
2222
--> $DIR/lint-uppercase-variables.rs:1:9
2323
|
2424
LL | #![warn(unused)]
2525
| ^^^^^^
2626
= note: `#[warn(unused_variables)]` implied by `#[warn(unused)]`
27+
help: if this is intentional, prefix it with an underscore
28+
|
29+
LL | _Foo => {}
30+
| +
31+
help: you might have meant to pattern match on the similarly named variant `Foo`
32+
|
33+
LL | foo::Foo::Foo => {}
34+
| ++++++++++
2735

2836
warning: unused variable: `Foo`
2937
--> $DIR/lint-uppercase-variables.rs:28:9
3038
|
3139
LL | let Foo = foo::Foo::Foo;
32-
| ^^^ help: if this is intentional, prefix it with an underscore: `_Foo`
40+
| ^^^
41+
|
42+
help: if this is intentional, prefix it with an underscore
43+
|
44+
LL | let _Foo = foo::Foo::Foo;
45+
| +
46+
help: you might have meant to pattern match on the similarly named variant `Foo`
47+
|
48+
LL | let foo::Foo::Foo = foo::Foo::Foo;
49+
| ++++++++++
3350

3451
error[E0170]: pattern binding `Foo` is named the same as one of the variants of the type `foo::Foo`
3552
--> $DIR/lint-uppercase-variables.rs:33:17
@@ -41,7 +58,16 @@ warning: unused variable: `Foo`
4158
--> $DIR/lint-uppercase-variables.rs:33:17
4259
|
4360
LL | fn in_param(Foo: foo::Foo) {}
44-
| ^^^ help: if this is intentional, prefix it with an underscore: `_Foo`
61+
| ^^^
62+
|
63+
help: if this is intentional, prefix it with an underscore
64+
|
65+
LL | fn in_param(_Foo: foo::Foo) {}
66+
| +
67+
help: you might have meant to pattern match on the similarly named variant `Foo`
68+
|
69+
LL | fn in_param(foo::Foo::Foo: foo::Foo) {}
70+
| ++++++++++
4571

4672
error: structure field `X` should have a snake case name
4773
--> $DIR/lint-uppercase-variables.rs:10:5

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

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ fn foo(x: (Lol, Lol)) {
1717
//~| NOTE: variable not in all patterns
1818
//~| ERROR: variable `Ban` is assigned to, but never used
1919
//~| NOTE: consider using `_Ban` instead
20+
//~| HELP: you might have meant to pattern match on the similarly named
2021
_ => {}
2122
}
2223
match &x {
@@ -27,23 +28,26 @@ fn foo(x: (Lol, Lol)) {
2728
//~| NOTE: variable not in all patterns
2829
//~| ERROR: variable `Ban` is assigned to, but never used
2930
//~| NOTE: consider using `_Ban` instead
31+
//~| HELP: you might have meant to pattern match on the similarly named
3032
_ => {}
3133
}
3234
match Some(42) {
35+
Some(_) => {}
3336
Non => {}
3437
//~^ ERROR: unused variable: `Non`
3538
//~| HELP: if this is intentional, prefix it with an underscore
36-
_ => {}
39+
//~| HELP: you might have meant to pattern match on the similarly named
3740
}
3841
match Some(42) {
42+
Some(_) => {}
3943
Non | None => {}
4044
//~^ ERROR: unused variable: `Non`
4145
//~| HELP: if this is intentional, prefix it with an underscore
4246
//~| ERROR: variable `Non` is not bound in all patterns [E0408]
4347
//~| NOTE: pattern doesn't bind `Non`
4448
//~| NOTE: variable not in all patterns
4549
//~| HELP: you might have meant to use the similarly named previously used binding `None`
46-
_ => {}
50+
//~| HELP: you might have meant to pattern match on the similarly named
4751
}
4852
match Some(42) {
4953
Non | Some(_) => {}
@@ -53,14 +57,13 @@ fn foo(x: (Lol, Lol)) {
5357
//~| NOTE: pattern doesn't bind `Non`
5458
//~| NOTE: variable not in all patterns
5559
//~| HELP: you might have meant to use the similarly named unit variant `None`
56-
_ => {}
60+
//~| HELP: you might have meant to pattern match on the similarly named
5761
}
5862
}
5963
fn bar(x: (Lol, Lol)) {
6064
use Lol::*;
6165
use Bat;
6266
use Bay;
63-
use std::option::Option::None;
6467
match &x {
6568
(Foo, _) | (Ban, Foo) => {}
6669
//~^ ERROR: variable `Ban` is not bound in all patterns
@@ -71,6 +74,7 @@ fn bar(x: (Lol, Lol)) {
7174
//~| NOTE: variable not in all patterns
7275
//~| ERROR: variable `Ban` is assigned to, but never used
7376
//~| NOTE: consider using `_Ban` instead
77+
//~| HELP: you might have meant to pattern match on the similarly named
7478
_ => {}
7579
}
7680
}
@@ -86,8 +90,21 @@ fn baz(x: (Lol, Lol)) {
8690
//~| NOTE: variable not in all patterns
8791
//~| ERROR: variable `Ban` is assigned to, but never used
8892
//~| NOTE: consider using `_Ban` instead
93+
//~| HELP: you might have meant to pattern match on the similarly named
8994
_ => {}
9095
}
96+
match &x {
97+
(Ban, _) => {}
98+
//~^ ERROR: unused variable: `Ban`
99+
//~| HELP: if this is intentional, prefix it with an underscore
100+
//~| HELP: you might have meant to pattern match on the similarly named
101+
}
102+
match Bay {
103+
Ban => {}
104+
//~^ ERROR: unused variable: `Ban`
105+
//~| HELP: if this is intentional, prefix it with an underscore
106+
//~| HELP: you might have meant to pattern match on the similarly named
107+
}
91108
}
92109

93110
fn main() {

0 commit comments

Comments
 (0)