Skip to content

Commit 422c9a0

Browse files
committed
wildcard_enum_match_arm gives suggestions
And is also more robust
1 parent 2139fbd commit 422c9a0

File tree

3 files changed

+171
-58
lines changed

3 files changed

+171
-58
lines changed

clippy_lints/src/matches.rs

Lines changed: 81 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@ use crate::utils::{
66
snippet_with_applicability, span_lint_and_sugg, span_lint_and_then, span_note_and_lint, walk_ptrs_ty,
77
};
88
use if_chain::if_chain;
9+
use rustc::hir::def::CtorKind;
910
use rustc::hir::*;
1011
use rustc::lint::{in_external_macro, LateContext, LateLintPass, LintArray, LintContext, LintPass};
11-
use rustc::ty::{self, Ty};
12+
use rustc::ty::{self, Ty, TyKind};
1213
use rustc::{declare_tool_lint, lint_array};
1314
use rustc_errors::Applicability;
1415
use std::cmp::Ordering;
1516
use std::collections::Bound;
17+
use std::ops::Deref;
1618
use syntax::ast::LitKind;
1719
use syntax::source_map::Span;
1820

@@ -191,7 +193,8 @@ declare_clippy_lint! {
191193
///
192194
/// **Why is this bad?** New enum variants added by library updates can be missed.
193195
///
194-
/// **Known problems:** Nested wildcards a la `Foo(_)` are currently not detected.
196+
/// **Known problems:** Suggested replacements may be incorrect if guards exhaustively cover some
197+
/// variants, and also may not use correct path to enum if it's not present in the current scope.
195198
///
196199
/// **Example:**
197200
/// ```rust
@@ -464,19 +467,85 @@ fn check_wild_err_arm(cx: &LateContext<'_, '_>, ex: &Expr, arms: &[Arm]) {
464467
}
465468

466469
fn check_wild_enum_match(cx: &LateContext<'_, '_>, ex: &Expr, arms: &[Arm]) {
467-
if cx.tables.expr_ty(ex).is_enum() {
470+
let ty = cx.tables.expr_ty(ex);
471+
if !ty.is_enum() {
472+
// If there isn't a nice closed set of possible values that can be conveniently enumerated,
473+
// don't complain about not enumerating the mall.
474+
return;
475+
}
476+
477+
// First pass - check for violation, but don't do much book-keeping because this is hopefully
478+
// the uncommon case, and the book-keeping is slightly expensive.
479+
let mut wildcard_span = None;
480+
let mut wildcard_ident = None;
481+
for arm in arms {
482+
for pat in &arm.pats {
483+
if let PatKind::Wild = pat.node {
484+
wildcard_span = Some(pat.span);
485+
} else if let PatKind::Binding(_, _, ident, None) = pat.node {
486+
wildcard_span = Some(pat.span);
487+
wildcard_ident = Some(ident);
488+
}
489+
}
490+
}
491+
492+
if let Some(wildcard_span) = wildcard_span {
493+
// Accumulate the variants which should be put in place of the wildcard because they're not
494+
// already covered.
495+
496+
let mut missing_variants = vec![];
497+
if let TyKind::Adt(def, _) = ty.sty {
498+
for variant in &def.variants {
499+
missing_variants.push(variant);
500+
}
501+
}
502+
468503
for arm in arms {
469-
if is_wild(&arm.pats[0]) {
470-
span_note_and_lint(
471-
cx,
472-
WILDCARD_ENUM_MATCH_ARM,
473-
arm.pats[0].span,
474-
"wildcard match will miss any future added variants.",
475-
arm.pats[0].span,
476-
"to resolve, match each variant explicitly",
477-
);
504+
if arm.guard.is_some() {
505+
// Guards mean that this case probably isn't exhaustively covered. Technically
506+
// this is incorrect, as we should really check whether each variant is exhaustively
507+
// covered by the set of guards that cover it, but that's really hard to do.
508+
continue;
509+
}
510+
for pat in &arm.pats {
511+
if let PatKind::Path(ref path) = pat.deref().node {
512+
if let QPath::Resolved(_, p) = path {
513+
missing_variants.retain(|e| e.did != p.def.def_id());
514+
}
515+
} else if let PatKind::TupleStruct(ref path, ..) = pat.deref().node {
516+
if let QPath::Resolved(_, p) = path {
517+
missing_variants.retain(|e| e.did != p.def.def_id());
518+
}
519+
}
478520
}
479521
}
522+
523+
let suggestion: Vec<String> = missing_variants
524+
.iter()
525+
.map(|v| {
526+
let suffix = match v.ctor_kind {
527+
CtorKind::Fn => "(..)",
528+
CtorKind::Const | CtorKind::Fictive => "",
529+
};
530+
let ident_str = if let Some(ident) = wildcard_ident {
531+
format!("{} @ ", ident.name)
532+
} else {
533+
String::new()
534+
};
535+
// This path assumes that the enum type is imported into scope.
536+
format!("{}{}{}", ident_str, cx.tcx.item_path_str(v.did), suffix)
537+
})
538+
.collect();
539+
540+
span_lint_and_sugg(
541+
cx,
542+
WILDCARD_ENUM_MATCH_ARM,
543+
wildcard_span,
544+
"wildcard match will miss any future added variants.",
545+
"try this",
546+
suggestion.join(" | "),
547+
Applicability::MachineApplicable,
548+
)
480549
}
481550
}
482551

tests/ui/wildcard_enum_match_arm.rs

Lines changed: 68 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,73 @@
1-
#![deny(clippy::wildcard_enum_match_arm)]
2-
3-
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
4-
enum Color {
5-
Red,
6-
Green,
7-
Blue,
8-
Rgb(u8, u8, u8),
9-
Cyan,
10-
}
11-
12-
impl Color {
13-
fn is_monochrome(self) -> bool {
14-
match self {
15-
Color::Red | Color::Green | Color::Blue => true,
16-
Color::Rgb(r, g, b) => r | g == 0 || r | b == 0 || g | b == 0,
17-
Color::Cyan => false,
18-
}
1+
#![warn(clippy::wildcard_enum_match_arm)]
2+
3+
#[derive(Debug)]
4+
enum Maybe<T> {
5+
Some(T),
6+
Probably(T),
7+
None,
8+
}
9+
10+
fn is_it_wildcard<T>(m: Maybe<T>) -> &'static str {
11+
match m {
12+
Maybe::Some(_) => "Some",
13+
_ => "Could be",
14+
}
15+
}
16+
17+
fn is_it_bound<T>(m: Maybe<T>) -> &'static str {
18+
match m {
19+
Maybe::None => "None",
20+
_other => "Could be",
21+
}
22+
}
23+
24+
fn is_it_binding(m: Maybe<u32>) -> String {
25+
match m {
26+
Maybe::Some(v) => "Large".to_string(),
27+
n => format!("{:?}", n),
28+
}
29+
}
30+
31+
fn is_it_binding_exhaustive(m: Maybe<u32>) -> String {
32+
match m {
33+
Maybe::Some(v) => "Large".to_string(),
34+
n @ Maybe::Probably(_) | n @ Maybe::None => format!("{:?}", n),
35+
}
36+
}
37+
38+
fn is_it_with_guard(m: Maybe<u32>) -> &'static str {
39+
match m {
40+
Maybe::Some(v) if v > 100 => "Large",
41+
_ => "Who knows",
42+
}
43+
}
44+
45+
fn is_it_exhaustive<T>(m: Maybe<T>) -> &'static str {
46+
match m {
47+
Maybe::None => "None",
48+
Maybe::Some(_) | Maybe::Probably(..) => "Could be",
49+
}
50+
}
51+
52+
fn is_one_or_three(i: i32) -> bool {
53+
match i {
54+
1 | 3 => true,
55+
_ => false,
1956
}
2057
}
2158

2259
fn main() {
23-
let color = Color::Rgb(0, 0, 127);
24-
match color {
25-
Color::Red => println!("Red"),
26-
_ => eprintln!("Not red"),
27-
};
28-
match color {
29-
Color::Red => {},
30-
Color::Green => {},
31-
Color::Blue => {},
32-
Color::Cyan => {},
33-
c if c.is_monochrome() => {},
34-
Color::Rgb(_, _, _) => {},
35-
};
36-
let x: u8 = unimplemented!();
37-
match x {
38-
0 => {},
39-
140 => {},
40-
_ => {},
41-
};
60+
println!("{}", is_it_wildcard(Maybe::Some("foo")));
61+
62+
println!("{}", is_it_bound(Maybe::Some("foo")));
63+
64+
println!("{}", is_it_binding(Maybe::Some(1)));
65+
66+
println!("{}", is_it_binding_exhaustive(Maybe::Some(1)));
67+
68+
println!("{}", is_it_with_guard(Maybe::Some(1)));
69+
70+
println!("{}", is_it_exhaustive(Maybe::Some("foo")));
71+
72+
println!("{}", is_one_or_three(2));
4273
}
Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,28 @@
11
error: wildcard match will miss any future added variants.
2-
--> $DIR/wildcard_enum_match_arm.rs:26:9
2+
--> $DIR/wildcard_enum_match_arm.rs:13:9
33
|
4-
LL | _ => eprintln!("Not red"),
5-
| ^
4+
LL | _ => "Could be",
5+
| ^ help: try this: `Maybe::Probably(..) | Maybe::None`
66
|
7-
note: lint level defined here
8-
--> $DIR/wildcard_enum_match_arm.rs:1:9
7+
= note: `-D clippy::wildcard-enum-match-arm` implied by `-D warnings`
8+
9+
error: wildcard match will miss any future added variants.
10+
--> $DIR/wildcard_enum_match_arm.rs:20:9
11+
|
12+
LL | _other => "Could be",
13+
| ^^^^^^ help: try this: `_other @ Maybe::Some(..) | _other @ Maybe::Probably(..)`
14+
15+
error: wildcard match will miss any future added variants.
16+
--> $DIR/wildcard_enum_match_arm.rs:27:9
17+
|
18+
LL | n => format!("{:?}", n),
19+
| ^ help: try this: `n @ Maybe::Probably(..) | n @ Maybe::None`
20+
21+
error: wildcard match will miss any future added variants.
22+
--> $DIR/wildcard_enum_match_arm.rs:41:9
923
|
10-
LL | #![deny(clippy::wildcard_enum_match_arm)]
11-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
12-
= note: to resolve, match each variant explicitly
24+
LL | _ => "Who knows",
25+
| ^ help: try this: `Maybe::Some(..) | Maybe::Probably(..) | Maybe::None`
1326

14-
error: aborting due to previous error
27+
error: aborting due to 4 previous errors
1528

0 commit comments

Comments
 (0)