about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md2
-rw-r--r--clippy_lints/src/exhaustive_items.rs102
-rw-r--r--clippy_lints/src/lib.rs6
-rw-r--r--tests/ui/exhaustive_items.fixed82
-rw-r--r--tests/ui/exhaustive_items.rs79
-rw-r--r--tests/ui/exhaustive_items.stderr61
6 files changed, 332 insertions, 0 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7f2de888d35..dadb6832d1f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1938,6 +1938,8 @@ Released 2018-09-13
 [`erasing_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#erasing_op
 [`eval_order_dependence`]: https://rust-lang.github.io/rust-clippy/master/index.html#eval_order_dependence
 [`excessive_precision`]: https://rust-lang.github.io/rust-clippy/master/index.html#excessive_precision
+[`exhaustive_enums`]: https://rust-lang.github.io/rust-clippy/master/index.html#exhaustive_enums
+[`exhaustive_structs`]: https://rust-lang.github.io/rust-clippy/master/index.html#exhaustive_structs
 [`exit`]: https://rust-lang.github.io/rust-clippy/master/index.html#exit
 [`expect_fun_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#expect_fun_call
 [`expect_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#expect_used
diff --git a/clippy_lints/src/exhaustive_items.rs b/clippy_lints/src/exhaustive_items.rs
new file mode 100644
index 00000000000..32b1299efce
--- /dev/null
+++ b/clippy_lints/src/exhaustive_items.rs
@@ -0,0 +1,102 @@
+use crate::utils::{indent_of, span_lint_and_then};
+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.
+    ///
+    /// **Known problems:** None.
+    ///
+    /// **Example:**
+    ///
+    /// ```rust
+    /// enum Foo {
+    ///     Bar,
+    ///     Baz
+    /// }
+    /// ```
+    /// Use instead:
+    /// ```rust
+    /// #[non_exhaustive]
+    /// enum Foo {
+    ///     Bar,
+    ///     Baz
+    /// }
+    /// ```
+    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.
+    ///
+    /// **Known problems:** None.
+    ///
+    /// **Example:**
+    ///
+    /// ```rust
+    /// struct Foo {
+    ///     bar: u8,
+    ///     baz: String,
+    /// }
+    /// ```
+    /// Use instead:
+    /// ```rust
+    /// #[non_exhaustive]
+    /// struct Foo {
+    ///     bar: u8,
+    ///     baz: String,
+    /// }
+    /// ```
+    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.hir_id);
+            if !item.attrs.iter().any(|a| a.has_name(sym::non_exhaustive));
+            then {
+                let (lint, msg) = if let ItemKind::Enum(..) = item.kind {
+                    (EXHAUSTIVE_ENUMS, "exported enums should not be exhaustive")
+                } else {
+                    (EXHAUSTIVE_STRUCTS, "exported structs 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);
+                    }
+                );
+
+            }
+        }
+    }
+}
diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs
index 7097812e088..4c889b8a8bb 100644
--- a/clippy_lints/src/lib.rs
+++ b/clippy_lints/src/lib.rs
@@ -200,6 +200,7 @@ mod escape;
 mod eta_reduction;
 mod eval_order_dependence;
 mod excessive_bools;
+mod exhaustive_items;
 mod exit;
 mod explicit_write;
 mod fallible_impl_from;
@@ -615,6 +616,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
         &eval_order_dependence::EVAL_ORDER_DEPENDENCE,
         &excessive_bools::FN_PARAMS_EXCESSIVE_BOOLS,
         &excessive_bools::STRUCT_EXCESSIVE_BOOLS,
+        &exhaustive_items::EXHAUSTIVE_ENUMS,
+        &exhaustive_items::EXHAUSTIVE_STRUCTS,
         &exit::EXIT,
         &explicit_write::EXPLICIT_WRITE,
         &fallible_impl_from::FALLIBLE_IMPL_FROM,
@@ -1101,6 +1104,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
     store.register_late_pass(|| box eval_order_dependence::EvalOrderDependence);
     store.register_late_pass(|| box missing_doc::MissingDoc::new());
     store.register_late_pass(|| box missing_inline::MissingInline);
+    store.register_late_pass(move || box exhaustive_items::ExhaustiveItems);
     store.register_late_pass(|| box if_let_some_result::OkIfLet);
     store.register_late_pass(|| box partialeq_ne_impl::PartialEqNeImpl);
     store.register_late_pass(|| box unused_io_amount::UnusedIoAmount);
@@ -1251,6 +1255,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
         LintId::of(&create_dir::CREATE_DIR),
         LintId::of(&dbg_macro::DBG_MACRO),
         LintId::of(&else_if_without_else::ELSE_IF_WITHOUT_ELSE),
+        LintId::of(&exhaustive_items::EXHAUSTIVE_ENUMS),
+        LintId::of(&exhaustive_items::EXHAUSTIVE_STRUCTS),
         LintId::of(&exit::EXIT),
         LintId::of(&float_literal::LOSSY_FLOAT_LITERAL),
         LintId::of(&implicit_return::IMPLICIT_RETURN),
diff --git a/tests/ui/exhaustive_items.fixed b/tests/ui/exhaustive_items.fixed
new file mode 100644
index 00000000000..8174a0175ab
--- /dev/null
+++ b/tests/ui/exhaustive_items.fixed
@@ -0,0 +1,82 @@
+// run-rustfix
+
+#![deny(clippy::exhaustive_enums, clippy::exhaustive_structs)]
+#![allow(unused)]
+
+fn main() {
+    // nop
+}
+
+pub mod enums {
+    #[non_exhaustive]
+    pub enum Exhaustive {
+        Foo,
+        Bar,
+        Baz,
+        Quux(String),
+    }
+
+    /// Some docs
+    #[repr(C)]
+    #[non_exhaustive]
+    pub enum ExhaustiveWithAttrs {
+        Foo,
+        Bar,
+        Baz,
+        Quux(String),
+    }
+
+    // no warning, already non_exhaustive
+    #[non_exhaustive]
+    pub enum NonExhaustive {
+        Foo,
+        Bar,
+        Baz,
+        Quux(String),
+    }
+
+    // no warning, private
+    enum ExhaustivePrivate {
+        Foo,
+        Bar,
+        Baz,
+        Quux(String),
+    }
+
+    // no warning, private
+    #[non_exhaustive]
+    enum NonExhaustivePrivate {
+        Foo,
+        Bar,
+        Baz,
+        Quux(String),
+    }
+}
+
+pub mod structs {
+    #[non_exhaustive]
+    pub struct Exhaustive {
+        foo: u8,
+        bar: String,
+    }
+
+    // no warning, already non_exhaustive
+    #[non_exhaustive]
+    pub struct NonExhaustive {
+        foo: u8,
+        bar: String,
+    }
+
+    // no warning, private
+    struct ExhaustivePrivate {
+        foo: u8,
+        bar: String,
+    }
+
+    // no warning, private
+    #[non_exhaustive]
+    struct NonExhaustivePrivate {
+        foo: u8,
+        bar: String,
+    }
+}
diff --git a/tests/ui/exhaustive_items.rs b/tests/ui/exhaustive_items.rs
new file mode 100644
index 00000000000..b476f09f8a0
--- /dev/null
+++ b/tests/ui/exhaustive_items.rs
@@ -0,0 +1,79 @@
+// run-rustfix
+
+#![deny(clippy::exhaustive_enums, clippy::exhaustive_structs)]
+#![allow(unused)]
+
+fn main() {
+    // nop
+}
+
+pub mod enums {
+    pub enum Exhaustive {
+        Foo,
+        Bar,
+        Baz,
+        Quux(String),
+    }
+
+    /// Some docs
+    #[repr(C)]
+    pub enum ExhaustiveWithAttrs {
+        Foo,
+        Bar,
+        Baz,
+        Quux(String),
+    }
+
+    // no warning, already non_exhaustive
+    #[non_exhaustive]
+    pub enum NonExhaustive {
+        Foo,
+        Bar,
+        Baz,
+        Quux(String),
+    }
+
+    // no warning, private
+    enum ExhaustivePrivate {
+        Foo,
+        Bar,
+        Baz,
+        Quux(String),
+    }
+
+    // no warning, private
+    #[non_exhaustive]
+    enum NonExhaustivePrivate {
+        Foo,
+        Bar,
+        Baz,
+        Quux(String),
+    }
+}
+
+pub mod structs {
+    pub struct Exhaustive {
+        foo: u8,
+        bar: String,
+    }
+
+    // no warning, already non_exhaustive
+    #[non_exhaustive]
+    pub struct NonExhaustive {
+        foo: u8,
+        bar: String,
+    }
+
+    // no warning, private
+    struct ExhaustivePrivate {
+        foo: u8,
+        bar: String,
+    }
+
+    // no warning, private
+    #[non_exhaustive]
+    struct NonExhaustivePrivate {
+        foo: u8,
+        bar: String,
+    }
+}
diff --git a/tests/ui/exhaustive_items.stderr b/tests/ui/exhaustive_items.stderr
new file mode 100644
index 00000000000..7369fe75a4f
--- /dev/null
+++ b/tests/ui/exhaustive_items.stderr
@@ -0,0 +1,61 @@
+error: exported enums should not be exhaustive
+  --> $DIR/exhaustive_items.rs:11:5
+   |
+LL | /     pub enum Exhaustive {
+LL | |         Foo,
+LL | |         Bar,
+LL | |         Baz,
+LL | |         Quux(String),
+LL | |     }
+   | |_____^
+   |
+note: the lint level is defined here
+  --> $DIR/exhaustive_items.rs:3:9
+   |
+LL | #![deny(clippy::exhaustive_enums, clippy::exhaustive_structs)]
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^
+help: try adding #[non_exhaustive]
+   |
+LL |     #[non_exhaustive]
+LL |     pub enum Exhaustive {
+   |
+
+error: exported enums should not be exhaustive
+  --> $DIR/exhaustive_items.rs:20:5
+   |
+LL | /     pub enum ExhaustiveWithAttrs {
+LL | |         Foo,
+LL | |         Bar,
+LL | |         Baz,
+LL | |         Quux(String),
+LL | |     }
+   | |_____^
+   |
+help: try adding #[non_exhaustive]
+   |
+LL |     #[non_exhaustive]
+LL |     pub enum ExhaustiveWithAttrs {
+   |
+
+error: exported structs should not be exhaustive
+  --> $DIR/exhaustive_items.rs:55:5
+   |
+LL | /     pub struct Exhaustive {
+LL | |         foo: u8,
+LL | |         bar: String,
+LL | |     }
+   | |_____^
+   |
+note: the lint level is defined here
+  --> $DIR/exhaustive_items.rs:3:35
+   |
+LL | #![deny(clippy::exhaustive_enums, clippy::exhaustive_structs)]
+   |                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^
+help: try adding #[non_exhaustive]
+   |
+LL |     #[non_exhaustive]
+LL |     pub struct Exhaustive {
+   |
+
+error: aborting due to 3 previous errors
+