about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--book/src/lint_configuration.md10
-rw-r--r--clippy_config/src/conf.rs3
-rw-r--r--clippy_lints/src/item_name_repetitions.rs28
-rw-r--r--clippy_lints/src/methods/mod.rs98
-rw-r--r--clippy_lints/src/missing_doc.rs12
-rw-r--r--tests/ui-toml/missing_docs_allow_unused/clippy.toml1
-rw-r--r--tests/ui-toml/missing_docs_allow_unused/missing_docs_allow_unused.rs26
-rw-r--r--tests/ui-toml/missing_docs_allow_unused/missing_docs_allow_unused.stderr38
-rw-r--r--tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr3
-rw-r--r--tests/ui/enum_variants.rs15
-rw-r--r--tests/ui/unwrap_expect_used.rs17
-rw-r--r--tests/ui/unwrap_expect_used.stderr34
13 files changed, 236 insertions, 50 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2b62c9a59aa..9d698c7c472 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6435,6 +6435,7 @@ Released 2018-09-13
 [`max-suggested-slice-pattern-length`]: https://doc.rust-lang.org/clippy/lint_configuration.html#max-suggested-slice-pattern-length
 [`max-trait-bounds`]: https://doc.rust-lang.org/clippy/lint_configuration.html#max-trait-bounds
 [`min-ident-chars-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#min-ident-chars-threshold
+[`missing-docs-allow-unused`]: https://doc.rust-lang.org/clippy/lint_configuration.html#missing-docs-allow-unused
 [`missing-docs-in-crate-items`]: https://doc.rust-lang.org/clippy/lint_configuration.html#missing-docs-in-crate-items
 [`module-item-order-groupings`]: https://doc.rust-lang.org/clippy/lint_configuration.html#module-item-order-groupings
 [`module-items-ordered-within-groupings`]: https://doc.rust-lang.org/clippy/lint_configuration.html#module-items-ordered-within-groupings
diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md
index 2314d1beac7..58c79c119cc 100644
--- a/book/src/lint_configuration.md
+++ b/book/src/lint_configuration.md
@@ -734,6 +734,16 @@ Minimum chars an ident can have, anything below or equal to this will be linted.
 * [`min_ident_chars`](https://rust-lang.github.io/rust-clippy/master/index.html#min_ident_chars)
 
 
+## `missing-docs-allow-unused`
+Whether to allow fields starting with an underscore to skip documentation requirements
+
+**Default Value:** `false`
+
+---
+**Affected lints:**
+* [`missing_docs_in_private_items`](https://rust-lang.github.io/rust-clippy/master/index.html#missing_docs_in_private_items)
+
+
 ## `missing-docs-in-crate-items`
 Whether to **only** check for missing documentation in items visible within the current
 crate. For example, `pub(crate)` items.
diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs
index 511cb84527d..aef0516b75b 100644
--- a/clippy_config/src/conf.rs
+++ b/clippy_config/src/conf.rs
@@ -675,6 +675,9 @@ define_Conf! {
     /// Minimum chars an ident can have, anything below or equal to this will be linted.
     #[lints(min_ident_chars)]
     min_ident_chars_threshold: u64 = 1,
+    /// Whether to allow fields starting with an underscore to skip documentation requirements
+    #[lints(missing_docs_in_private_items)]
+    missing_docs_allow_unused: bool = false,
     /// Whether to **only** check for missing documentation in items visible within the current
     /// crate. For example, `pub(crate)` items.
     #[lints(missing_docs_in_private_items)]
diff --git a/clippy_lints/src/item_name_repetitions.rs b/clippy_lints/src/item_name_repetitions.rs
index b1271a264b5..30f61af29e5 100644
--- a/clippy_lints/src/item_name_repetitions.rs
+++ b/clippy_lints/src/item_name_repetitions.rs
@@ -5,7 +5,7 @@ use clippy_utils::macros::span_is_local;
 use clippy_utils::source::is_present_in_source;
 use clippy_utils::str_utils::{camel_case_split, count_match_end, count_match_start, to_camel_case, to_snake_case};
 use rustc_data_structures::fx::FxHashSet;
-use rustc_hir::{EnumDef, FieldDef, Item, ItemKind, OwnerId, Variant, VariantData};
+use rustc_hir::{EnumDef, FieldDef, Item, ItemKind, OwnerId, QPath, TyKind, Variant, VariantData};
 use rustc_lint::{LateContext, LateLintPass};
 use rustc_session::impl_lint_pass;
 use rustc_span::symbol::Symbol;
@@ -405,6 +405,7 @@ fn check_enum_start(cx: &LateContext<'_>, item_name: &str, variant: &Variant<'_>
     if count_match_start(item_name, name).char_count == item_name_chars
         && name.chars().nth(item_name_chars).is_some_and(|c| !c.is_lowercase())
         && name.chars().nth(item_name_chars + 1).is_some_and(|c| !c.is_numeric())
+        && !check_enum_tuple_path_match(name, variant.data)
     {
         span_lint_hir(
             cx,
@@ -420,7 +421,9 @@ fn check_enum_end(cx: &LateContext<'_>, item_name: &str, variant: &Variant<'_>)
     let name = variant.ident.name.as_str();
     let item_name_chars = item_name.chars().count();
 
-    if count_match_end(item_name, name).char_count == item_name_chars {
+    if count_match_end(item_name, name).char_count == item_name_chars
+        && !check_enum_tuple_path_match(name, variant.data)
+    {
         span_lint_hir(
             cx,
             ENUM_VARIANT_NAMES,
@@ -431,6 +434,27 @@ fn check_enum_end(cx: &LateContext<'_>, item_name: &str, variant: &Variant<'_>)
     }
 }
 
+/// Checks if an enum tuple variant contains a single field
+/// whose qualified path contains the variant's name.
+fn check_enum_tuple_path_match(variant_name: &str, variant_data: VariantData<'_>) -> bool {
+    // Only check single-field tuple variants
+    let VariantData::Tuple(fields, ..) = variant_data else {
+        return false;
+    };
+    if fields.len() != 1 {
+        return false;
+    }
+    // Check if field type is a path and contains the variant name
+    match fields[0].ty.kind {
+        TyKind::Path(QPath::Resolved(_, path)) => path
+            .segments
+            .iter()
+            .any(|segment| segment.ident.name.as_str() == variant_name),
+        TyKind::Path(QPath::TypeRelative(_, segment)) => segment.ident.name.as_str() == variant_name,
+        _ => false,
+    }
+}
+
 impl LateLintPass<'_> for ItemNameRepetitions {
     fn check_item_post(&mut self, _cx: &LateContext<'_>, item: &Item<'_>) {
         let Some(_ident) = item.kind.ident() else { return };
diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs
index 10f4637d08f..4690119ba3d 100644
--- a/clippy_lints/src/methods/mod.rs
+++ b/clippy_lints/src/methods/mod.rs
@@ -4709,6 +4709,8 @@ impl_lint_pass!(Methods => [
 ]);
 
 /// Extracts a method call name, args, and `Span` of the method name.
+/// This ensures that neither the receiver nor any of the arguments
+/// come from expansion.
 pub fn method_call<'tcx>(
     recv: &'tcx Expr<'tcx>,
 ) -> Option<(&'tcx str, &'tcx Expr<'tcx>, &'tcx [Expr<'tcx>], Span, Span)> {
@@ -4907,6 +4909,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
 impl Methods {
     #[allow(clippy::too_many_lines)]
     fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+        // Handle method calls whose receiver and arguments may not come from expansion
         if let Some((name, recv, args, span, call_span)) = method_call(expr) {
             match (name, args) {
                 ("add" | "offset" | "sub" | "wrapping_offset" | "wrapping_add" | "wrapping_sub", [_arg]) => {
@@ -5049,29 +5052,12 @@ impl Methods {
                         Some(("err", recv, [], err_span, _)) => {
                             err_expect::check(cx, expr, recv, span, err_span, self.msrv);
                         },
-                        _ => unwrap_expect_used::check(
-                            cx,
-                            expr,
-                            recv,
-                            false,
-                            self.allow_expect_in_consts,
-                            self.allow_expect_in_tests,
-                            unwrap_expect_used::Variant::Expect,
-                        ),
+                        _ => {},
                     }
                     unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
                 },
-                ("expect_err", [_]) => {
+                ("expect_err", [_]) | ("unwrap_err" | "unwrap_unchecked" | "unwrap_err_unchecked", []) => {
                     unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
-                    unwrap_expect_used::check(
-                        cx,
-                        expr,
-                        recv,
-                        true,
-                        self.allow_expect_in_consts,
-                        self.allow_expect_in_tests,
-                        unwrap_expect_used::Variant::Expect,
-                    );
                 },
                 ("extend", [arg]) => {
                     string_extend_chars::check(cx, expr, recv, arg);
@@ -5437,27 +5423,6 @@ impl Methods {
                         _ => {},
                     }
                     unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
-                    unwrap_expect_used::check(
-                        cx,
-                        expr,
-                        recv,
-                        false,
-                        self.allow_unwrap_in_consts,
-                        self.allow_unwrap_in_tests,
-                        unwrap_expect_used::Variant::Unwrap,
-                    );
-                },
-                ("unwrap_err", []) => {
-                    unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
-                    unwrap_expect_used::check(
-                        cx,
-                        expr,
-                        recv,
-                        true,
-                        self.allow_unwrap_in_consts,
-                        self.allow_unwrap_in_tests,
-                        unwrap_expect_used::Variant::Unwrap,
-                    );
                 },
                 ("unwrap_or", [u_arg]) => {
                     match method_call(recv) {
@@ -5486,9 +5451,6 @@ impl Methods {
                     }
                     unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
                 },
-                ("unwrap_unchecked" | "unwrap_err_unchecked", []) => {
-                    unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
-                },
                 ("unwrap_or_else", [u_arg]) => {
                     match method_call(recv) {
                         Some(("map", recv, [map_arg], _, _))
@@ -5526,6 +5488,56 @@ impl Methods {
                 _ => {},
             }
         }
+        // Handle method calls whose receiver and arguments may come from expansion
+        if let ExprKind::MethodCall(path, recv, args, _call_span) = expr.kind {
+            match (path.ident.name.as_str(), args) {
+                ("expect", [_]) if !matches!(method_call(recv), Some(("ok" | "err", _, [], _, _))) => {
+                    unwrap_expect_used::check(
+                        cx,
+                        expr,
+                        recv,
+                        false,
+                        self.allow_expect_in_consts,
+                        self.allow_expect_in_tests,
+                        unwrap_expect_used::Variant::Expect,
+                    );
+                },
+                ("expect_err", [_]) => {
+                    unwrap_expect_used::check(
+                        cx,
+                        expr,
+                        recv,
+                        true,
+                        self.allow_expect_in_consts,
+                        self.allow_expect_in_tests,
+                        unwrap_expect_used::Variant::Expect,
+                    );
+                },
+                ("unwrap", []) => {
+                    unwrap_expect_used::check(
+                        cx,
+                        expr,
+                        recv,
+                        false,
+                        self.allow_unwrap_in_consts,
+                        self.allow_unwrap_in_tests,
+                        unwrap_expect_used::Variant::Unwrap,
+                    );
+                },
+                ("unwrap_err", []) => {
+                    unwrap_expect_used::check(
+                        cx,
+                        expr,
+                        recv,
+                        true,
+                        self.allow_unwrap_in_consts,
+                        self.allow_unwrap_in_tests,
+                        unwrap_expect_used::Variant::Unwrap,
+                    );
+                },
+                _ => {},
+            }
+        }
     }
 }
 
diff --git a/clippy_lints/src/missing_doc.rs b/clippy_lints/src/missing_doc.rs
index b234b190153..7772051eb5c 100644
--- a/clippy_lints/src/missing_doc.rs
+++ b/clippy_lints/src/missing_doc.rs
@@ -48,6 +48,8 @@ pub struct MissingDoc {
     /// Whether to **only** check for missing documentation in items visible within the current
     /// crate. For example, `pub(crate)` items.
     crate_items_only: bool,
+    /// Whether to allow fields starting with an underscore to skip documentation requirements
+    allow_unused: bool,
     /// Stack of whether #[doc(hidden)] is set
     /// at each level which has lint attributes.
     doc_hidden_stack: Vec<bool>,
@@ -59,6 +61,7 @@ impl MissingDoc {
     pub fn new(conf: &'static Conf) -> Self {
         Self {
             crate_items_only: conf.missing_docs_in_crate_items,
+            allow_unused: conf.missing_docs_allow_unused,
             doc_hidden_stack: vec![false],
             prev_span: None,
         }
@@ -260,11 +263,12 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc {
     }
 
     fn check_field_def(&mut self, cx: &LateContext<'tcx>, sf: &'tcx hir::FieldDef<'_>) {
-        if !sf.is_positional() {
+        if !(sf.is_positional()
+            || is_from_proc_macro(cx, sf)
+            || self.allow_unused && sf.ident.as_str().starts_with('_'))
+        {
             let attrs = cx.tcx.hir_attrs(sf.hir_id);
-            if !is_from_proc_macro(cx, sf) {
-                self.check_missing_docs_attrs(cx, sf.def_id, attrs, sf.span, "a", "struct field");
-            }
+            self.check_missing_docs_attrs(cx, sf.def_id, attrs, sf.span, "a", "struct field");
         }
         self.prev_span = Some(sf.span);
     }
diff --git a/tests/ui-toml/missing_docs_allow_unused/clippy.toml b/tests/ui-toml/missing_docs_allow_unused/clippy.toml
new file mode 100644
index 00000000000..2fe64b2755b
--- /dev/null
+++ b/tests/ui-toml/missing_docs_allow_unused/clippy.toml
@@ -0,0 +1 @@
+missing-docs-allow-unused = true
diff --git a/tests/ui-toml/missing_docs_allow_unused/missing_docs_allow_unused.rs b/tests/ui-toml/missing_docs_allow_unused/missing_docs_allow_unused.rs
new file mode 100644
index 00000000000..155f680c7b1
--- /dev/null
+++ b/tests/ui-toml/missing_docs_allow_unused/missing_docs_allow_unused.rs
@@ -0,0 +1,26 @@
+//! Test file for missing_docs_in_private_items lint with allow_unused configuration
+#![warn(clippy::missing_docs_in_private_items)]
+#![allow(dead_code)]
+
+/// A struct with some documented and undocumented fields
+struct Test {
+    /// This field is documented
+    field1: i32,
+    _unused: i32, // This should not trigger a warning because it starts with an underscore
+    field3: i32,  //~ missing_docs_in_private_items
+}
+
+struct Test2 {
+    //~^ missing_docs_in_private_items
+    _field1: i32, // This should not trigger a warning
+    _field2: i32, // This should not trigger a warning
+}
+
+struct Test3 {
+    //~^ missing_docs_in_private_items
+    /// This field is documented although this is not mandatory
+    _unused: i32, // This should not trigger a warning because it starts with an underscore
+    field2: i32, //~ missing_docs_in_private_items
+}
+
+fn main() {}
diff --git a/tests/ui-toml/missing_docs_allow_unused/missing_docs_allow_unused.stderr b/tests/ui-toml/missing_docs_allow_unused/missing_docs_allow_unused.stderr
new file mode 100644
index 00000000000..8f511883e90
--- /dev/null
+++ b/tests/ui-toml/missing_docs_allow_unused/missing_docs_allow_unused.stderr
@@ -0,0 +1,38 @@
+error: missing documentation for a struct field
+  --> tests/ui-toml/missing_docs_allow_unused/missing_docs_allow_unused.rs:10:5
+   |
+LL |     field3: i32,
+   |     ^^^^^^^^^^^
+   |
+   = note: `-D clippy::missing-docs-in-private-items` implied by `-D warnings`
+   = help: to override `-D warnings` add `#[allow(clippy::missing_docs_in_private_items)]`
+
+error: missing documentation for a struct
+  --> tests/ui-toml/missing_docs_allow_unused/missing_docs_allow_unused.rs:13:1
+   |
+LL | / struct Test2 {
+LL | |
+LL | |     _field1: i32, // This should not trigger a warning
+LL | |     _field2: i32, // This should not trigger a warning
+LL | | }
+   | |_^
+
+error: missing documentation for a struct
+  --> tests/ui-toml/missing_docs_allow_unused/missing_docs_allow_unused.rs:19:1
+   |
+LL | / struct Test3 {
+LL | |
+LL | |     /// This field is documented although this is not mandatory
+LL | |     _unused: i32, // This should not trigger a warning because it starts with an underscore
+LL | |     field2: i32,
+LL | | }
+   | |_^
+
+error: missing documentation for a struct field
+  --> tests/ui-toml/missing_docs_allow_unused/missing_docs_allow_unused.rs:23:5
+   |
+LL |     field2: i32,
+   |     ^^^^^^^^^^^
+
+error: aborting due to 4 previous errors
+
diff --git a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr
index f2eaa66a4ae..0a36cd3cf26 100644
--- a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr
+++ b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr
@@ -57,6 +57,7 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect
            max-suggested-slice-pattern-length
            max-trait-bounds
            min-ident-chars-threshold
+           missing-docs-allow-unused
            missing-docs-in-crate-items
            module-item-order-groupings
            module-items-ordered-within-groupings
@@ -149,6 +150,7 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect
            max-suggested-slice-pattern-length
            max-trait-bounds
            min-ident-chars-threshold
+           missing-docs-allow-unused
            missing-docs-in-crate-items
            module-item-order-groupings
            module-items-ordered-within-groupings
@@ -241,6 +243,7 @@ error: error reading Clippy's configuration file: unknown field `allow_mixed_uni
            max-suggested-slice-pattern-length
            max-trait-bounds
            min-ident-chars-threshold
+           missing-docs-allow-unused
            missing-docs-in-crate-items
            module-item-order-groupings
            module-items-ordered-within-groupings
diff --git a/tests/ui/enum_variants.rs b/tests/ui/enum_variants.rs
index f7bbf83654f..18c80c7aba4 100644
--- a/tests/ui/enum_variants.rs
+++ b/tests/ui/enum_variants.rs
@@ -220,4 +220,19 @@ mod issue11494 {
     }
 }
 
+mod encapsulated {
+    mod types {
+        pub struct FooError;
+        pub struct BarError;
+        pub struct BazError;
+    }
+
+    enum Error {
+        FooError(types::FooError),
+        BarError(types::BarError),
+        BazError(types::BazError),
+        Other,
+    }
+}
+
 fn main() {}
diff --git a/tests/ui/unwrap_expect_used.rs b/tests/ui/unwrap_expect_used.rs
index d0bb571273b..b429f3a8a0b 100644
--- a/tests/ui/unwrap_expect_used.rs
+++ b/tests/ui/unwrap_expect_used.rs
@@ -66,3 +66,20 @@ fn main() {
         SOME.expect("Still not three?");
     }
 }
+
+mod with_expansion {
+    macro_rules! open {
+        ($file:expr) => {
+            std::fs::File::open($file)
+        };
+    }
+
+    fn test(file: &str) {
+        use std::io::Read;
+        let mut s = String::new();
+        let _ = open!(file).unwrap(); //~ unwrap_used
+        let _ = open!(file).expect("can open"); //~ expect_used
+        let _ = open!(file).unwrap_err(); //~ unwrap_used
+        let _ = open!(file).expect_err("can open"); //~ expect_used
+    }
+}
diff --git a/tests/ui/unwrap_expect_used.stderr b/tests/ui/unwrap_expect_used.stderr
index 79eac3f58cc..6fd1b84d812 100644
--- a/tests/ui/unwrap_expect_used.stderr
+++ b/tests/ui/unwrap_expect_used.stderr
@@ -50,5 +50,37 @@ LL |     a.expect_err("Hello error!");
    |
    = note: if this value is an `Ok`, it will panic
 
-error: aborting due to 6 previous errors
+error: used `unwrap()` on a `Result` value
+  --> tests/ui/unwrap_expect_used.rs:80:17
+   |
+LL |         let _ = open!(file).unwrap();
+   |                 ^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: if this value is an `Err`, it will panic
+
+error: used `expect()` on a `Result` value
+  --> tests/ui/unwrap_expect_used.rs:81:17
+   |
+LL |         let _ = open!(file).expect("can open");
+   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: if this value is an `Err`, it will panic
+
+error: used `unwrap_err()` on a `Result` value
+  --> tests/ui/unwrap_expect_used.rs:82:17
+   |
+LL |         let _ = open!(file).unwrap_err();
+   |                 ^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: if this value is an `Ok`, it will panic
+
+error: used `expect_err()` on a `Result` value
+  --> tests/ui/unwrap_expect_used.rs:83:17
+   |
+LL |         let _ = open!(file).expect_err("can open");
+   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: if this value is an `Ok`, it will panic
+
+error: aborting due to 10 previous errors