diff --git a/CHANGELOG.md b/CHANGELOG.md index c0679d280c266..71066aadfcbdc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1028,6 +1028,7 @@ All notable changes to this project will be documented in this file. [`while_let_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#while_let_loop [`while_let_on_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#while_let_on_iterator [`wildcard_dependencies`]: https://rust-lang.github.io/rust-clippy/master/index.html#wildcard_dependencies +[`wildcard_enum_match_arm`]: https://rust-lang.github.io/rust-clippy/master/index.html#wildcard_enum_match_arm [`write_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#write_literal [`write_with_newline`]: https://rust-lang.github.io/rust-clippy/master/index.html#write_with_newline [`writeln_empty_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#writeln_empty_string diff --git a/README.md b/README.md index dad18ef756901..c1f457a956ebc 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code. -[There are 293 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html) +[There are 294 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html) We have a bunch of lint categories to allow you to choose how much Clippy is supposed to ~~annoy~~ help you: diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 3483aae0ca3bc..52cc2a88da4aa 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -499,6 +499,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry<'_>, conf: &Conf) { indexing_slicing::INDEXING_SLICING, inherent_impl::MULTIPLE_INHERENT_IMPL, literal_representation::DECIMAL_LITERAL_REPRESENTATION, + matches::WILDCARD_ENUM_MATCH_ARM, mem_forget::MEM_FORGET, methods::CLONE_ON_REF_PTR, methods::OPTION_UNWRAP_USED, diff --git a/clippy_lints/src/matches.rs b/clippy_lints/src/matches.rs index b290980fc3615..6ef0731669102 100644 --- a/clippy_lints/src/matches.rs +++ b/clippy_lints/src/matches.rs @@ -187,6 +187,25 @@ declare_clippy_lint! { "a match on an Option value instead of using `as_ref()` or `as_mut`" } +/// **What it does:** Checks for wildcard enum matches using `_`. +/// +/// **Why is this bad?** New enum variants added by library updates can be missed. +/// +/// **Known problems:** Nested wildcards a la `Foo(_)` are currently not detected. +/// +/// **Example:** +/// ```rust +/// match x { +/// A => {}, +/// _ => {}, +/// } +/// ``` +declare_clippy_lint! { + pub WILDCARD_ENUM_MATCH_ARM, + restriction, + "a wildcard enum match arm using `_`" +} + #[allow(missing_copy_implementations)] pub struct MatchPass; @@ -199,7 +218,8 @@ impl LintPass for MatchPass { SINGLE_MATCH_ELSE, MATCH_OVERLAPPING_ARM, MATCH_WILD_ERR_ARM, - MATCH_AS_REF + MATCH_AS_REF, + WILDCARD_ENUM_MATCH_ARM ) } @@ -218,6 +238,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for MatchPass { check_match_bool(cx, ex, arms, expr); check_overlapping_arms(cx, ex, arms); check_wild_err_arm(cx, ex, arms); + check_wild_enum_match(cx, ex, arms); check_match_as_ref(cx, ex, arms, expr); } if let ExprKind::Match(ref ex, ref arms, _) = expr.node { @@ -442,6 +463,23 @@ fn check_wild_err_arm(cx: &LateContext<'_, '_>, ex: &Expr, arms: &[Arm]) { } } +fn check_wild_enum_match(cx: &LateContext<'_, '_>, ex: &Expr, arms: &[Arm]) { + if cx.tables.expr_ty(ex).is_enum() { + for arm in arms { + if is_wild(&arm.pats[0]) { + span_note_and_lint( + cx, + WILDCARD_ENUM_MATCH_ARM, + arm.pats[0].span, + "wildcard match will miss any future added variants.", + arm.pats[0].span, + "to resolve, match each variant explicitly", + ); + } + } + } +} + // If the block contains only a `panic!` macro (as expression or statement) fn is_panic_block(block: &Block) -> bool { match (&block.expr, block.stmts.len(), block.stmts.first()) { diff --git a/tests/ui/wildcard_enum_match_arm.rs b/tests/ui/wildcard_enum_match_arm.rs new file mode 100644 index 0000000000000..58daabf426864 --- /dev/null +++ b/tests/ui/wildcard_enum_match_arm.rs @@ -0,0 +1,42 @@ +#![deny(clippy::wildcard_enum_match_arm)] + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum Color { + Red, + Green, + Blue, + Rgb(u8, u8, u8), + Cyan, +} + +impl Color { + fn is_monochrome(self) -> bool { + match self { + Color::Red | Color::Green | Color::Blue => true, + Color::Rgb(r, g, b) => r | g == 0 || r | b == 0 || g | b == 0, + Color::Cyan => false, + } + } +} + +fn main() { + let color = Color::Rgb(0, 0, 127); + match color { + Color::Red => println!("Red"), + _ => eprintln!("Not red"), + }; + match color { + Color::Red => {}, + Color::Green => {}, + Color::Blue => {}, + Color::Cyan => {}, + c if c.is_monochrome() => {}, + Color::Rgb(_, _, _) => {}, + }; + let x: u8 = unimplemented!(); + match x { + 0 => {}, + 140 => {}, + _ => {}, + }; +} diff --git a/tests/ui/wildcard_enum_match_arm.stderr b/tests/ui/wildcard_enum_match_arm.stderr new file mode 100644 index 0000000000000..6319a3f3d46c3 --- /dev/null +++ b/tests/ui/wildcard_enum_match_arm.stderr @@ -0,0 +1,15 @@ +error: wildcard match will miss any future added variants. + --> $DIR/wildcard_enum_match_arm.rs:26:9 + | +LL | _ => eprintln!("Not red"), + | ^ + | +note: lint level defined here + --> $DIR/wildcard_enum_match_arm.rs:1:9 + | +LL | #![deny(clippy::wildcard_enum_match_arm)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: to resolve, match each variant explicitly + +error: aborting due to previous error +