summaryrefslogtreecommitdiffstats
path: root/src/tools/clippy/clippy_lints/src/exhaustive_items.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/tools/clippy/clippy_lints/src/exhaustive_items.rs111
1 files changed, 111 insertions, 0 deletions
diff --git a/src/tools/clippy/clippy_lints/src/exhaustive_items.rs b/src/tools/clippy/clippy_lints/src/exhaustive_items.rs
new file mode 100644
index 000000000..173d41b4b
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/exhaustive_items.rs
@@ -0,0 +1,111 @@
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::indent_of;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{Item, ItemKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+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
+ /// ```rust
+ /// enum Foo {
+ /// Bar,
+ /// Baz
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// #[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 `structs`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
+ /// ```rust
+ /// struct Foo {
+ /// bar: u8,
+ /// baz: String,
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// #[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_chain! {
+ if let ItemKind::Enum(..) | ItemKind::Struct(..) = item.kind;
+ if cx.access_levels.is_exported(item.def_id);
+ let attrs = cx.tcx.hir().attrs(item.hir_id());
+ if !attrs.iter().any(|a| a.has_name(sym::non_exhaustive));
+ then {
+ let (lint, msg) = if let ItemKind::Struct(ref v, ..) = item.kind {
+ if v.fields().iter().any(|f| {
+ let def_id = cx.tcx.hir().local_def_id(f.hir_id);
+ !cx.tcx.visibility(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);
+ }
+ );
+
+ }
+ }
+ }
+}