use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::indent_of; use rustc_errors::Applicability; use rustc_hir::{Item, ItemKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; use rustc_span::sym; declare_clippy_lint! { /// ### What it does /// Warns on any exported `enum`s that are not tagged `#[non_exhaustive]` /// /// ### Why is this bad? /// Exhaustive enums are typically fine, but a project which does /// not wish to make a stability commitment around exported enums may wish to /// disable them by default. /// /// ### Example /// ```no_run /// enum Foo { /// Bar, /// Baz /// } /// ``` /// Use instead: /// ```no_run /// #[non_exhaustive] /// enum Foo { /// Bar, /// Baz /// } /// ``` #[clippy::version = "1.51.0"] pub EXHAUSTIVE_ENUMS, restriction, "detects exported enums that have not been marked #[non_exhaustive]" } declare_clippy_lint! { /// ### What it does /// Warns on any exported `struct`s that are not tagged `#[non_exhaustive]` /// /// ### Why is this bad? /// Exhaustive structs are typically fine, but a project which does /// not wish to make a stability commitment around exported structs may wish to /// disable them by default. /// /// ### Example /// ```no_run /// struct Foo { /// bar: u8, /// baz: String, /// } /// ``` /// Use instead: /// ```no_run /// #[non_exhaustive] /// struct Foo { /// bar: u8, /// baz: String, /// } /// ``` #[clippy::version = "1.51.0"] pub EXHAUSTIVE_STRUCTS, restriction, "detects exported structs that have not been marked #[non_exhaustive]" } declare_lint_pass!(ExhaustiveItems => [EXHAUSTIVE_ENUMS, EXHAUSTIVE_STRUCTS]); impl LateLintPass<'_> for ExhaustiveItems { fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { if let ItemKind::Enum(..) | ItemKind::Struct(..) = item.kind && cx.effective_visibilities.is_exported(item.owner_id.def_id) && let attrs = cx.tcx.hir().attrs(item.hir_id()) && !attrs.iter().any(|a| a.has_name(sym::non_exhaustive)) { let (lint, msg) = if let ItemKind::Struct(ref v, ..) = item.kind { if v.fields().iter().any(|f| !cx.tcx.visibility(f.def_id).is_public()) { // skip structs with private fields return; } (EXHAUSTIVE_STRUCTS, "exported structs should not be exhaustive") } else { (EXHAUSTIVE_ENUMS, "exported enums should not be exhaustive") }; let suggestion_span = item.span.shrink_to_lo(); let indent = " ".repeat(indent_of(cx, item.span).unwrap_or(0)); span_lint_and_then(cx, lint, item.span, msg, |diag| { let sugg = format!("#[non_exhaustive]\n{indent}"); diag.span_suggestion( suggestion_span, "try adding #[non_exhaustive]", sugg, Applicability::MaybeIncorrect, ); }); } } }