diff options
| author | bors <bors@rust-lang.org> | 2020-05-20 12:51:28 +0000 |
|---|---|---|
| committer | bors <bors@rust-lang.org> | 2020-05-20 12:51:28 +0000 |
| commit | cafa94662ce3ffc1c8c1edca86e328fcc26ad3db (patch) | |
| tree | b5326dbb19148d0e9e9fabfc4656f23225cb0dba | |
| parent | 34ba597ccb92e290c9385d6bc891d06963f974f6 (diff) | |
| parent | d90625385e8ed0a9030e3ab2ea0990fce39c28bf (diff) | |
| download | rust-cafa94662ce3ffc1c8c1edca86e328fcc26ad3db.tar.gz rust-cafa94662ce3ffc1c8c1edca86e328fcc26ad3db.zip | |
Auto merge of #5582 - vtmargaryan:match_wildcard_for_single_variants, r=flip1995
New lint: `match_wildcard_for_single_variants` changelog: Added a new lint match_wildcard_for_single_variants to warn on enum matches where a wildcard is used to match a single variant Closes #5556
| -rw-r--r-- | CHANGELOG.md | 1 | ||||
| -rw-r--r-- | clippy_lints/src/float_literal.rs | 2 | ||||
| -rw-r--r-- | clippy_lints/src/lib.rs | 2 | ||||
| -rw-r--r-- | clippy_lints/src/matches.rs | 68 | ||||
| -rw-r--r-- | clippy_lints/src/misc_early.rs | 2 | ||||
| -rw-r--r-- | clippy_lints/src/missing_const_for_fn.rs | 2 | ||||
| -rw-r--r-- | clippy_lints/src/needless_pass_by_value.rs | 2 | ||||
| -rw-r--r-- | clippy_lints/src/trivially_copy_pass_by_ref.rs | 2 | ||||
| -rw-r--r-- | clippy_lints/src/utils/mod.rs | 2 | ||||
| -rw-r--r-- | src/lintlist/mod.rs | 7 | ||||
| -rw-r--r-- | tests/compile-test.rs | 2 | ||||
| -rw-r--r-- | tests/ui/match_wildcard_for_single_variants.fixed | 59 | ||||
| -rw-r--r-- | tests/ui/match_wildcard_for_single_variants.rs | 59 | ||||
| -rw-r--r-- | tests/ui/match_wildcard_for_single_variants.stderr | 28 |
14 files changed, 227 insertions, 11 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 583c32ca9e0..2ac9057199f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1439,6 +1439,7 @@ Released 2018-09-13 [`match_same_arms`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_same_arms [`match_single_binding`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_single_binding [`match_wild_err_arm`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_wild_err_arm +[`match_wildcard_for_single_variants`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_wildcard_for_single_variants [`maybe_infinite_iter`]: https://rust-lang.github.io/rust-clippy/master/index.html#maybe_infinite_iter [`mem_discriminant_non_enum`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_discriminant_non_enum [`mem_forget`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_forget diff --git a/clippy_lints/src/float_literal.rs b/clippy_lints/src/float_literal.rs index 3a52b1d3fc2..4c604cd0107 100644 --- a/clippy_lints/src/float_literal.rs +++ b/clippy_lints/src/float_literal.rs @@ -77,7 +77,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for FloatLiteral { let type_suffix = match lit_float_ty { LitFloatType::Suffixed(FloatTy::F32) => Some("f32"), LitFloatType::Suffixed(FloatTy::F64) => Some("f64"), - _ => None + LitFloatType::Unsuffixed => None }; let (is_whole, mut float_str) = match fty { FloatTy::F32 => { diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index e0787ac0887..4d4fff883b3 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -638,6 +638,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &matches::MATCH_OVERLAPPING_ARM, &matches::MATCH_REF_PATS, &matches::MATCH_SINGLE_BINDING, + &matches::MATCH_WILDCARD_FOR_SINGLE_VARIANTS, &matches::MATCH_WILD_ERR_ARM, &matches::REST_PAT_IN_FULLY_BOUND_STRUCTS, &matches::SINGLE_MATCH, @@ -1139,6 +1140,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(¯o_use::MACRO_USE_IMPORTS), LintId::of(&match_on_vec_items::MATCH_ON_VEC_ITEMS), LintId::of(&matches::MATCH_BOOL), + LintId::of(&matches::MATCH_WILDCARD_FOR_SINGLE_VARIANTS), LintId::of(&matches::SINGLE_MATCH_ELSE), LintId::of(&methods::FILTER_MAP), LintId::of(&methods::FILTER_MAP_NEXT), diff --git a/clippy_lints/src/matches.rs b/clippy_lints/src/matches.rs index bbf14374a1f..4106e5013b9 100644 --- a/clippy_lints/src/matches.rs +++ b/clippy_lints/src/matches.rs @@ -220,7 +220,7 @@ declare_clippy_lint! { /// # enum Foo { A(usize), B(usize) } /// # let x = Foo::B(1); /// match x { - /// A => {}, + /// Foo::A(_) => {}, /// _ => {}, /// } /// ``` @@ -230,6 +230,40 @@ declare_clippy_lint! { } declare_clippy_lint! { + /// **What it does:** Checks for wildcard enum matches for a single variant. + /// + /// **Why is this bad?** New enum variants added by library updates can be missed. + /// + /// **Known problems:** Suggested replacements may not use correct path to enum + /// if it's not present in the current scope. + /// + /// **Example:** + /// + /// ```rust + /// # enum Foo { A, B, C } + /// # let x = Foo::B; + /// match x { + /// Foo::A => {}, + /// Foo::B => {}, + /// _ => {}, + /// } + /// ``` + /// Use instead: + /// ```rust + /// # enum Foo { A, B, C } + /// # let x = Foo::B; + /// match x { + /// Foo::A => {}, + /// Foo::B => {}, + /// Foo::C => {}, + /// } + /// ``` + pub MATCH_WILDCARD_FOR_SINGLE_VARIANTS, + pedantic, + "a wildcard enum match for a single variant" +} + +declare_clippy_lint! { /// **What it does:** Checks for wildcard pattern used with others patterns in same match arm. /// /// **Why is this bad?** Wildcard pattern already covers any other pattern as it will match anyway. @@ -356,6 +390,7 @@ impl_lint_pass!(Matches => [ MATCH_WILD_ERR_ARM, MATCH_AS_REF, WILDCARD_ENUM_MATCH_ARM, + MATCH_WILDCARD_FOR_SINGLE_VARIANTS, WILDCARD_IN_OR_PATTERNS, MATCH_SINGLE_BINDING, INFALLIBLE_DESTRUCTURING_MATCH, @@ -729,9 +764,21 @@ fn check_wild_enum_match(cx: &LateContext<'_, '_>, ex: &Expr<'_>, arms: &[Arm<'_ if let QPath::Resolved(_, p) = path { missing_variants.retain(|e| e.ctor_def_id != Some(p.res.def_id())); } - } else if let PatKind::TupleStruct(ref path, ..) = arm.pat.kind { + } else if let PatKind::TupleStruct(ref path, ref patterns, ..) = arm.pat.kind { if let QPath::Resolved(_, p) = path { - missing_variants.retain(|e| e.ctor_def_id != Some(p.res.def_id())); + // Some simple checks for exhaustive patterns. + // There is a room for improvements to detect more cases, + // but it can be more expensive to do so. + let is_pattern_exhaustive = |pat: &&Pat<'_>| { + if let PatKind::Wild | PatKind::Binding(.., None) = pat.kind { + true + } else { + false + } + }; + if patterns.iter().all(is_pattern_exhaustive) { + missing_variants.retain(|e| e.ctor_def_id != Some(p.res.def_id())); + } } } } @@ -766,6 +813,19 @@ fn check_wild_enum_match(cx: &LateContext<'_, '_>, ex: &Expr<'_>, arms: &[Arm<'_ } } + if suggestion.len() == 1 { + // No need to check for non-exhaustive enum as in that case len would be greater than 1 + span_lint_and_sugg( + cx, + MATCH_WILDCARD_FOR_SINGLE_VARIANTS, + wildcard_span, + message, + "try this", + suggestion[0].clone(), + Applicability::MaybeIncorrect, + ) + }; + span_lint_and_sugg( cx, WILDCARD_ENUM_MATCH_ARM, @@ -773,7 +833,7 @@ fn check_wild_enum_match(cx: &LateContext<'_, '_>, ex: &Expr<'_>, arms: &[Arm<'_ message, "try this", suggestion.join(" | "), - Applicability::MachineApplicable, + Applicability::MaybeIncorrect, ) } } diff --git a/clippy_lints/src/misc_early.rs b/clippy_lints/src/misc_early.rs index 62ee051624b..552222eba2e 100644 --- a/clippy_lints/src/misc_early.rs +++ b/clippy_lints/src/misc_early.rs @@ -379,7 +379,7 @@ impl EarlyLintPass for MiscEarlyLints { let left_binding = match left { BindingMode::ByRef(Mutability::Mut) => "ref mut ", BindingMode::ByRef(Mutability::Not) => "ref ", - _ => "", + BindingMode::ByValue(..) => "", }; if let PatKind::Wild = right.kind { diff --git a/clippy_lints/src/missing_const_for_fn.rs b/clippy_lints/src/missing_const_for_fn.rs index 4301157e164..9cfc8d19134 100644 --- a/clippy_lints/src/missing_const_for_fn.rs +++ b/clippy_lints/src/missing_const_for_fn.rs @@ -113,7 +113,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for MissingConstForFn { return; } }, - _ => return, + FnKind::Closure(..) => return, } let mir = cx.tcx.optimized_mir(def_id); diff --git a/clippy_lints/src/needless_pass_by_value.rs b/clippy_lints/src/needless_pass_by_value.rs index ed48ab54897..dc5dbdc5ba7 100644 --- a/clippy_lints/src/needless_pass_by_value.rs +++ b/clippy_lints/src/needless_pass_by_value.rs @@ -86,7 +86,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for NeedlessPassByValue { } }, FnKind::Method(..) => (), - _ => return, + FnKind::Closure(..) => return, } // Exclude non-inherent impls diff --git a/clippy_lints/src/trivially_copy_pass_by_ref.rs b/clippy_lints/src/trivially_copy_pass_by_ref.rs index 2c101220c5d..8e0cb94317a 100644 --- a/clippy_lints/src/trivially_copy_pass_by_ref.rs +++ b/clippy_lints/src/trivially_copy_pass_by_ref.rs @@ -161,7 +161,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for TriviallyCopyPassByRef { } }, FnKind::Method(..) => (), - _ => return, + FnKind::Closure(..) => return, } // Exclude non-inherent impls diff --git a/clippy_lints/src/utils/mod.rs b/clippy_lints/src/utils/mod.rs index 438a9f42ccd..0291a2b549e 100644 --- a/clippy_lints/src/utils/mod.rs +++ b/clippy_lints/src/utils/mod.rs @@ -358,7 +358,7 @@ pub fn trait_ref_of_method<'tcx>(cx: &LateContext<'_, 'tcx>, hir_id: HirId) -> O pub fn has_drop<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, ty: Ty<'tcx>) -> bool { match ty.ty_adt_def() { Some(def) => def.has_dtor(cx.tcx), - _ => false, + None => false, } } diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs index 9457a64f9c6..0bf46491d31 100644 --- a/src/lintlist/mod.rs +++ b/src/lintlist/mod.rs @@ -1201,6 +1201,13 @@ pub static ref ALL_LINTS: Vec<Lint> = vec![ module: "matches", }, Lint { + name: "match_wildcard_for_single_variants", + group: "pedantic", + desc: "a wildcard enum match for a single variant", + deprecation: None, + module: "matches", + }, + Lint { name: "maybe_infinite_iter", group: "pedantic", desc: "possible infinite iteration", diff --git a/tests/compile-test.rs b/tests/compile-test.rs index de2cf6d7873..a3df9d5ccbd 100644 --- a/tests/compile-test.rs +++ b/tests/compile-test.rs @@ -44,7 +44,7 @@ fn third_party_crates() -> String { for entry in fs::read_dir(dep_dir).unwrap() { let path = match entry { Ok(entry) => entry.path(), - _ => continue, + Err(_) => continue, }; if let Some(name) = path.file_name().and_then(OsStr::to_str) { for dep in CRATES { diff --git a/tests/ui/match_wildcard_for_single_variants.fixed b/tests/ui/match_wildcard_for_single_variants.fixed new file mode 100644 index 00000000000..519200977a7 --- /dev/null +++ b/tests/ui/match_wildcard_for_single_variants.fixed @@ -0,0 +1,59 @@ +// run-rustfix + +#![warn(clippy::match_wildcard_for_single_variants)] +#![allow(dead_code)] + +enum Foo { + A, + B, + C, +} + +enum Color { + Red, + Green, + Blue, + Rgb(u8, u8, u8), +} + +fn main() { + let f = Foo::A; + match f { + Foo::A => {}, + Foo::B => {}, + Foo::C => {}, + } + + let color = Color::Red; + + // check exhaustive bindings + match color { + Color::Red => {}, + Color::Green => {}, + Color::Rgb(_r, _g, _b) => {}, + Color::Blue => {}, + } + + // check exhaustive wild + match color { + Color::Red => {}, + Color::Green => {}, + Color::Rgb(..) => {}, + Color::Blue => {}, + } + match color { + Color::Red => {}, + Color::Green => {}, + Color::Rgb(_, _, _) => {}, + Color::Blue => {}, + } + + // shouldn't lint as there is one missing variant + // and one that isn't exhaustively covered + match color { + Color::Red => {}, + Color::Green => {}, + Color::Rgb(255, _, _) => {}, + _ => {}, + } +} diff --git a/tests/ui/match_wildcard_for_single_variants.rs b/tests/ui/match_wildcard_for_single_variants.rs new file mode 100644 index 00000000000..1df917e085c --- /dev/null +++ b/tests/ui/match_wildcard_for_single_variants.rs @@ -0,0 +1,59 @@ +// run-rustfix + +#![warn(clippy::match_wildcard_for_single_variants)] +#![allow(dead_code)] + +enum Foo { + A, + B, + C, +} + +enum Color { + Red, + Green, + Blue, + Rgb(u8, u8, u8), +} + +fn main() { + let f = Foo::A; + match f { + Foo::A => {}, + Foo::B => {}, + _ => {}, + } + + let color = Color::Red; + + // check exhaustive bindings + match color { + Color::Red => {}, + Color::Green => {}, + Color::Rgb(_r, _g, _b) => {}, + _ => {}, + } + + // check exhaustive wild + match color { + Color::Red => {}, + Color::Green => {}, + Color::Rgb(..) => {}, + _ => {}, + } + match color { + Color::Red => {}, + Color::Green => {}, + Color::Rgb(_, _, _) => {}, + _ => {}, + } + + // shouldn't lint as there is one missing variant + // and one that isn't exhaustively covered + match color { + Color::Red => {}, + Color::Green => {}, + Color::Rgb(255, _, _) => {}, + _ => {}, + } +} diff --git a/tests/ui/match_wildcard_for_single_variants.stderr b/tests/ui/match_wildcard_for_single_variants.stderr new file mode 100644 index 00000000000..82790aa9e80 --- /dev/null +++ b/tests/ui/match_wildcard_for_single_variants.stderr @@ -0,0 +1,28 @@ +error: wildcard match will miss any future added variants + --> $DIR/match_wildcard_for_single_variants.rs:24:9 + | +LL | _ => {}, + | ^ help: try this: `Foo::C` + | + = note: `-D clippy::match-wildcard-for-single-variants` implied by `-D warnings` + +error: wildcard match will miss any future added variants + --> $DIR/match_wildcard_for_single_variants.rs:34:9 + | +LL | _ => {}, + | ^ help: try this: `Color::Blue` + +error: wildcard match will miss any future added variants + --> $DIR/match_wildcard_for_single_variants.rs:42:9 + | +LL | _ => {}, + | ^ help: try this: `Color::Blue` + +error: wildcard match will miss any future added variants + --> $DIR/match_wildcard_for_single_variants.rs:48:9 + | +LL | _ => {}, + | ^ help: try this: `Color::Blue` + +error: aborting due to 4 previous errors + |
