From dff0397882b8698521cd5de4e110c4c3ea183851 Mon Sep 17 00:00:00 2001 From: Matthew Smith Date: Tue, 28 Mar 2023 15:03:56 +0100 Subject: [PATCH] new lint: `synthetic_non_exhaustive` Closes: https://github.com/rust-lang/rust-clippy/issues/10550 --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/lib.rs | 2 + clippy_lints/src/synthetic_non_exhaustive.rs | 77 ++++++++++++++++++++ tests/ui/synthetic_non_exhaustive.rs | 27 +++++++ tests/ui/synthetic_non_exhaustive.stderr | 16 ++++ 6 files changed, 124 insertions(+) create mode 100644 clippy_lints/src/synthetic_non_exhaustive.rs create mode 100644 tests/ui/synthetic_non_exhaustive.rs create mode 100644 tests/ui/synthetic_non_exhaustive.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fde8c6d902f..3aedc849525c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4931,6 +4931,7 @@ Released 2018-09-13 [`suspicious_unary_op_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_unary_op_formatting [`suspicious_xor_used_as_pow`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_xor_used_as_pow [`swap_ptr_to_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#swap_ptr_to_ref +[`synthetic_non_exhaustive`]: https://rust-lang.github.io/rust-clippy/master/index.html#synthetic_non_exhaustive [`tabs_in_doc_comments`]: https://rust-lang.github.io/rust-clippy/master/index.html#tabs_in_doc_comments [`temporary_assignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_assignment [`temporary_cstring_as_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_cstring_as_ptr diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 15b557bded2e..d4c2518981df 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -573,6 +573,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::swap::ALMOST_SWAPPED_INFO, crate::swap::MANUAL_SWAP_INFO, crate::swap_ptr_to_ref::SWAP_PTR_TO_REF_INFO, + crate::synthetic_non_exhaustive::SYNTHETIC_NON_EXHAUSTIVE_INFO, crate::tabs_in_doc_comments::TABS_IN_DOC_COMMENTS_INFO, crate::temporary_assignment::TEMPORARY_ASSIGNMENT_INFO, crate::to_digit_is_some::TO_DIGIT_IS_SOME_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index c9210bf73f89..a86345bfcc8c 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -287,6 +287,7 @@ mod suspicious_trait_impl; mod suspicious_xor_used_as_pow; mod swap; mod swap_ptr_to_ref; +mod synthetic_non_exhaustive; mod tabs_in_doc_comments; mod temporary_assignment; mod to_digit_is_some; @@ -940,6 +941,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|_| Box::new(allow_attributes::AllowAttribute)); store.register_late_pass(move |_| Box::new(manual_main_separator_str::ManualMainSeparatorStr::new(msrv()))); store.register_late_pass(|_| Box::new(unnecessary_struct_initialization::UnnecessaryStruct)); + store.register_early_pass(|| Box::new(synthetic_non_exhaustive::SyntheticNonExhaustive)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/synthetic_non_exhaustive.rs b/clippy_lints/src/synthetic_non_exhaustive.rs new file mode 100644 index 000000000000..cb9c79555099 --- /dev/null +++ b/clippy_lints/src/synthetic_non_exhaustive.rs @@ -0,0 +1,77 @@ +use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::is_doc_hidden; +use rustc_ast::ast::*; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::symbol::sym; + +declare_clippy_lint! { + /// ### What it does + /// Detects `enum`s that have hidden variants and are not marked as + /// non-exhaustive. + /// + /// ### Why is this bad? + /// `enum`s with hidden variants are effectively non-exhaustive, and should + // be marked as such to give clarity to consumers. + /// + /// ### Example + /// ```rust + /// pub enum Foo { + /// A, + /// B, + /// #[doc(hidden)] + /// C, + /// } + /// ``` + /// Use instead: + /// ```rust + /// #[non_exhaustive] + /// pub enum Foo { + /// A, + /// B, + /// #[doc(hidden)] + /// C, + /// } + /// ``` + #[clippy::version = "1.70.0"] + pub SYNTHETIC_NON_EXHAUSTIVE, + pedantic, + "enum with hidden variants not marked as non-exhaustive" +} +declare_lint_pass!(SyntheticNonExhaustive => [SYNTHETIC_NON_EXHAUSTIVE]); + +impl EarlyLintPass for SyntheticNonExhaustive { + fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { + if_chain! { + if let ItemKind::Enum(ref def, _) = item.kind; + if !item.attrs.iter().any(is_non_exhaustive); + if def.variants.iter().any(is_hidden_variant); + then { + span_lint_and_help( + cx, + SYNTHETIC_NON_EXHAUSTIVE, + item.span, + "missing `#[non_exhaustive]` on an enum with hidden variants", + None, + "consider adding the `#[non_exhaustive]` attribute to the enum" + ); + } + } + } +} + +fn is_non_exhaustive(attr: &Attribute) -> bool { + if_chain! { + if let AttrKind::Normal(normal) = &attr.kind; + if let [segment] = normal.item.path.segments.as_slice(); + then { + segment.ident.name == sym::non_exhaustive + } else { + false + } + } +} + +fn is_hidden_variant(variant: &Variant) -> bool { + is_doc_hidden(&variant.attrs) +} diff --git a/tests/ui/synthetic_non_exhaustive.rs b/tests/ui/synthetic_non_exhaustive.rs new file mode 100644 index 000000000000..d574589c3eb6 --- /dev/null +++ b/tests/ui/synthetic_non_exhaustive.rs @@ -0,0 +1,27 @@ +#![allow(unused)] +#![warn(clippy::synthetic_non_exhaustive)] + +pub enum Foo1 { + A, + B, + C, +} + +pub enum Foo2 { + A, + B, + #[doc(hidden)] + C, +} + +#[non_exhaustive] +pub enum Foo3 { + A, + B, + #[doc(hidden)] + C, +} + +fn main() { + // test code goes here +} diff --git a/tests/ui/synthetic_non_exhaustive.stderr b/tests/ui/synthetic_non_exhaustive.stderr new file mode 100644 index 000000000000..20851deb26e0 --- /dev/null +++ b/tests/ui/synthetic_non_exhaustive.stderr @@ -0,0 +1,16 @@ +error: missing `#[non_exhaustive]` on an enum with hidden variants + --> $DIR/synthetic_non_exhaustive.rs:10:1 + | +LL | / pub enum Foo2 { +LL | | A, +LL | | B, +LL | | #[doc(hidden)] +LL | | C, +LL | | } + | |_^ + | + = help: consider adding the `#[non_exhaustive]` attribute to the enum + = note: `-D clippy::synthetic-non-exhaustive` implied by `-D warnings` + +error: aborting due to previous error +