use clippy_config::Conf; use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then}; use clippy_utils::is_doc_hidden; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet_indent; use itertools::Itertools; use rustc_ast::attr; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res}; use rustc_hir::{Expr, ExprKind, Item, ItemKind, QPath, TyKind, VariantData}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; use rustc_span::def_id::LocalDefId; use rustc_span::{Span, sym}; declare_clippy_lint! { /// ### What it does /// Checks for manual implementations of the non-exhaustive pattern. /// /// ### Why is this bad? /// Using the #[non_exhaustive] attribute expresses better the intent /// and allows possible optimizations when applied to enums. /// /// ### Example /// ```no_run /// struct S { /// pub a: i32, /// pub b: i32, /// _c: (), /// } /// /// enum E { /// A, /// B, /// #[doc(hidden)] /// _C, /// } /// /// struct T(pub i32, pub i32, ()); /// ``` /// Use instead: /// ```no_run /// #[non_exhaustive] /// struct S { /// pub a: i32, /// pub b: i32, /// } /// /// #[non_exhaustive] /// enum E { /// A, /// B, /// } /// /// #[non_exhaustive] /// struct T(pub i32, pub i32); /// ``` #[clippy::version = "1.45.0"] pub MANUAL_NON_EXHAUSTIVE, style, "manual implementations of the non-exhaustive pattern can be simplified using #[non_exhaustive]" } pub struct ManualNonExhaustive { msrv: Msrv, constructed_enum_variants: FxHashSet, potential_enums: Vec<(LocalDefId, LocalDefId, Span, Span)>, } impl ManualNonExhaustive { pub fn new(conf: &'static Conf) -> Self { Self { msrv: conf.msrv.clone(), constructed_enum_variants: FxHashSet::default(), potential_enums: Vec::new(), } } } impl_lint_pass!(ManualNonExhaustive => [MANUAL_NON_EXHAUSTIVE]); impl<'tcx> LateLintPass<'tcx> for ManualNonExhaustive { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { if !self.msrv.meets(msrvs::NON_EXHAUSTIVE) || !cx.effective_visibilities.is_exported(item.owner_id.def_id) { return; } match item.kind { ItemKind::Enum(def, _) if def.variants.len() > 1 => { let iter = def.variants.iter().filter_map(|v| { (matches!(v.data, VariantData::Unit(_, _)) && is_doc_hidden(cx.tcx.hir().attrs(v.hir_id))) .then_some((v.def_id, v.span)) }); if let Ok((id, span)) = iter.exactly_one() && !attr::contains_name(cx.tcx.hir().attrs(item.hir_id()), sym::non_exhaustive) { self.potential_enums.push((item.owner_id.def_id, id, item.span, span)); } }, ItemKind::Struct(variant_data, _) => { let fields = variant_data.fields(); let private_fields = fields .iter() .filter(|field| !cx.effective_visibilities.is_exported(field.def_id)); if fields.len() > 1 && let Ok(field) = private_fields.exactly_one() && let TyKind::Tup([]) = field.ty.kind { span_lint_and_then( cx, MANUAL_NON_EXHAUSTIVE, item.span, "this seems like a manual implementation of the non-exhaustive pattern", |diag| { if let Some(non_exhaustive) = attr::find_by_name(cx.tcx.hir().attrs(item.hir_id()), sym::non_exhaustive) { diag.span_note(non_exhaustive.span, "the struct is already non-exhaustive"); } else { let indent = snippet_indent(cx, item.span).unwrap_or_default(); diag.span_suggestion_verbose( item.span.shrink_to_lo(), "use the `#[non_exhaustive]` attribute instead", format!("#[non_exhaustive]\n{indent}"), Applicability::MaybeIncorrect, ); } diag.span_help(field.span, "remove this field"); }, ); } }, _ => {}, } } fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { if let ExprKind::Path(QPath::Resolved(None, p)) = &e.kind && let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Const), ctor_id) = p.res && let Some(local_ctor) = ctor_id.as_local() { let variant_id = cx.tcx.local_parent(local_ctor); self.constructed_enum_variants.insert(variant_id); } } fn check_crate_post(&mut self, cx: &LateContext<'tcx>) { for &(enum_id, _, enum_span, variant_span) in self .potential_enums .iter() .filter(|(_, variant_id, _, _)| !self.constructed_enum_variants.contains(variant_id)) { let hir_id = cx.tcx.local_def_id_to_hir_id(enum_id); span_lint_hir_and_then( cx, MANUAL_NON_EXHAUSTIVE, hir_id, enum_span, "this seems like a manual implementation of the non-exhaustive pattern", |diag| { let indent = snippet_indent(cx, enum_span).unwrap_or_default(); diag.span_suggestion_verbose( enum_span.shrink_to_lo(), "use the `#[non_exhaustive]` attribute instead", format!("#[non_exhaustive]\n{indent}"), Applicability::MaybeIncorrect, ); diag.span_help(variant_span, "remove this variant"); }, ); } } extract_msrv_attr!(LateContext); }