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/attrs/useless_attribute.rs40
-rw-r--r--clippy_lints/src/bool_assert_comparison.rs9
-rw-r--r--clippy_lints/src/floating_point_arithmetic.rs12
-rw-r--r--clippy_lints/src/item_name_repetitions.rs28
-rw-r--r--clippy_lints/src/manual_ignore_case_cmp.rs9
-rw-r--r--clippy_lints/src/manual_option_as_slice.rs12
-rw-r--r--clippy_lints/src/methods/case_sensitive_file_extension_comparisons.rs5
-rw-r--r--clippy_lints/src/methods/manual_c_str_literals.rs16
-rw-r--r--clippy_lints/src/methods/mod.rs98
-rw-r--r--clippy_lints/src/methods/needless_collect.rs10
-rw-r--r--clippy_lints/src/missing_doc.rs12
-rw-r--r--clippy_lints/src/mutable_debug_assertion.rs11
-rw-r--r--clippy_lints/src/operators/eq_op.rs15
-rw-r--r--clippy_lints/src/panic_in_result_fn.rs11
-rw-r--r--clippy_lints/src/panic_unimplemented.rs8
-rw-r--r--clippy_lints/src/returns.rs8
-rw-r--r--clippy_lints/src/strings.rs2
-rw-r--r--clippy_lints/src/unit_types/unit_cmp.rs17
-rw-r--r--clippy_lints/src/unused_io_amount.rs2
-rw-r--r--clippy_lints/src/write.rs6
-rw-r--r--clippy_lints_internal/src/symbols.rs119
-rw-r--r--clippy_utils/src/sym.rs35
-rw-r--r--tests/ui-internal/interning_literals.stderr15
-rw-r--r--tests/ui-internal/interning_literals_unfixable.stderr9
-rw-r--r--tests/ui-internal/symbol_as_str.fixed7
-rw-r--r--tests/ui-internal/symbol_as_str.rs7
-rw-r--r--tests/ui-internal/symbol_as_str.stderr35
-rw-r--r--tests/ui-internal/symbol_as_str_unfixable.stderr9
-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
38 files changed, 517 insertions, 198 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/attrs/useless_attribute.rs b/clippy_lints/src/attrs/useless_attribute.rs
index d75b73280e6..4059f9603c3 100644
--- a/clippy_lints/src/attrs/useless_attribute.rs
+++ b/clippy_lints/src/attrs/useless_attribute.rs
@@ -26,16 +26,16 @@ pub(super) fn check(cx: &EarlyContext<'_>, item: &Item, attrs: &[Attribute]) {
 
                         if namespace.is_none()
                             && matches!(
-                                name.as_str(),
-                                "ambiguous_glob_reexports"
-                                    | "dead_code"
-                                    | "deprecated"
-                                    | "hidden_glob_reexports"
-                                    | "unreachable_pub"
-                                    | "unused"
-                                    | "unused_braces"
-                                    | "unused_import_braces"
-                                    | "unused_imports"
+                                name,
+                                sym::ambiguous_glob_reexports
+                                    | sym::dead_code
+                                    | sym::deprecated
+                                    | sym::hidden_glob_reexports
+                                    | sym::unreachable_pub
+                                    | sym::unused
+                                    | sym::unused_braces
+                                    | sym::unused_import_braces
+                                    | sym::unused_imports
                             )
                         {
                             return;
@@ -43,16 +43,16 @@ pub(super) fn check(cx: &EarlyContext<'_>, item: &Item, attrs: &[Attribute]) {
 
                         if namespace == Some(sym::clippy)
                             && matches!(
-                                name.as_str(),
-                                "wildcard_imports"
-                                    | "enum_glob_use"
-                                    | "redundant_pub_crate"
-                                    | "macro_use_imports"
-                                    | "unsafe_removed_from_name"
-                                    | "module_name_repetitions"
-                                    | "single_component_path_imports"
-                                    | "disallowed_types"
-                                    | "unused_trait_names"
+                                name,
+                                sym::wildcard_imports
+                                    | sym::enum_glob_use
+                                    | sym::redundant_pub_crate
+                                    | sym::macro_use_imports
+                                    | sym::unsafe_removed_from_name
+                                    | sym::module_name_repetitions
+                                    | sym::single_component_path_imports
+                                    | sym::disallowed_types
+                                    | sym::unused_trait_names
                             )
                         {
                             return;
diff --git a/clippy_lints/src/bool_assert_comparison.rs b/clippy_lints/src/bool_assert_comparison.rs
index 4a876b85416..ae36bb76117 100644
--- a/clippy_lints/src/bool_assert_comparison.rs
+++ b/clippy_lints/src/bool_assert_comparison.rs
@@ -1,6 +1,7 @@
 use clippy_utils::diagnostics::span_lint_and_then;
 use clippy_utils::macros::{find_assert_eq_args, root_macro_call_first_node};
 use clippy_utils::sugg::Sugg;
+use clippy_utils::sym;
 use clippy_utils::ty::{implements_trait, is_copy};
 use rustc_ast::ast::LitKind;
 use rustc_errors::Applicability;
@@ -73,10 +74,9 @@ impl<'tcx> LateLintPass<'tcx> for BoolAssertComparison {
         let Some(macro_call) = root_macro_call_first_node(cx, expr) else {
             return;
         };
-        let macro_name = cx.tcx.item_name(macro_call.def_id);
-        let eq_macro = match macro_name.as_str() {
-            "assert_eq" | "debug_assert_eq" => true,
-            "assert_ne" | "debug_assert_ne" => false,
+        let eq_macro = match cx.tcx.get_diagnostic_name(macro_call.def_id) {
+            Some(sym::assert_eq_macro | sym::debug_assert_eq_macro) => true,
+            Some(sym::assert_ne_macro | sym::debug_assert_ne_macro) => false,
             _ => return,
         };
         let Some((a, b, _)) = find_assert_eq_args(cx, expr, macro_call.expn) else {
@@ -115,6 +115,7 @@ impl<'tcx> LateLintPass<'tcx> for BoolAssertComparison {
             return;
         }
 
+        let macro_name = cx.tcx.item_name(macro_call.def_id);
         let macro_name = macro_name.as_str();
         let non_eq_mac = &macro_name[..macro_name.len() - 3];
         span_lint_and_then(
diff --git a/clippy_lints/src/floating_point_arithmetic.rs b/clippy_lints/src/floating_point_arithmetic.rs
index 553a00ed868..e653a57196d 100644
--- a/clippy_lints/src/floating_point_arithmetic.rs
+++ b/clippy_lints/src/floating_point_arithmetic.rs
@@ -759,12 +759,12 @@ impl<'tcx> LateLintPass<'tcx> for FloatingPointArithmetic {
             let recv_ty = cx.typeck_results().expr_ty(receiver);
 
             if recv_ty.is_floating_point() && !is_no_std_crate(cx) && is_inherent_method_call(cx, expr) {
-                match path.ident.name.as_str() {
-                    "ln" => check_ln1p(cx, expr, receiver),
-                    "log" => check_log_base(cx, expr, receiver, args),
-                    "powf" => check_powf(cx, expr, receiver, args),
-                    "powi" => check_powi(cx, expr, receiver, args),
-                    "sqrt" => check_hypot(cx, expr, receiver),
+                match path.ident.name {
+                    sym::ln => check_ln1p(cx, expr, receiver),
+                    sym::log => check_log_base(cx, expr, receiver, args),
+                    sym::powf => check_powf(cx, expr, receiver, args),
+                    sym::powi => check_powi(cx, expr, receiver, args),
+                    sym::sqrt => check_hypot(cx, expr, receiver),
                     _ => {},
                 }
             }
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/manual_ignore_case_cmp.rs b/clippy_lints/src/manual_ignore_case_cmp.rs
index d92069edb6d..57c03fbb2ed 100644
--- a/clippy_lints/src/manual_ignore_case_cmp.rs
+++ b/clippy_lints/src/manual_ignore_case_cmp.rs
@@ -1,6 +1,7 @@
 use crate::manual_ignore_case_cmp::MatchType::{Literal, ToAscii};
 use clippy_utils::diagnostics::span_lint_and_then;
 use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::sym;
 use clippy_utils::ty::{get_type_diagnostic_name, is_type_diagnostic_item, is_type_lang_item};
 use rustc_ast::LitKind;
 use rustc_errors::Applicability;
@@ -10,7 +11,7 @@ use rustc_lint::{LateContext, LateLintPass};
 use rustc_middle::ty;
 use rustc_middle::ty::{Ty, UintTy};
 use rustc_session::declare_lint_pass;
-use rustc_span::{Span, sym};
+use rustc_span::Span;
 
 declare_clippy_lint! {
     /// ### What it does
@@ -47,9 +48,9 @@ enum MatchType<'a, 'b> {
 
 fn get_ascii_type<'a, 'b>(cx: &LateContext<'a>, kind: rustc_hir::ExprKind<'b>) -> Option<(Span, MatchType<'a, 'b>)> {
     if let MethodCall(path, expr, _, _) = kind {
-        let is_lower = match path.ident.name.as_str() {
-            "to_ascii_lowercase" => true,
-            "to_ascii_uppercase" => false,
+        let is_lower = match path.ident.name {
+            sym::to_ascii_lowercase => true,
+            sym::to_ascii_uppercase => false,
             _ => return None,
         };
         let ty_raw = cx.typeck_results().expr_ty(expr);
diff --git a/clippy_lints/src/manual_option_as_slice.rs b/clippy_lints/src/manual_option_as_slice.rs
index 04e00f84103..b55c11f2d5b 100644
--- a/clippy_lints/src/manual_option_as_slice.rs
+++ b/clippy_lints/src/manual_option_as_slice.rs
@@ -80,26 +80,26 @@ impl LateLintPass<'_> for ManualOptionAsSlice {
                     check_map(cx, callee, span, self.msrv);
                 }
             },
-            ExprKind::MethodCall(seg, callee, [or], _) => match seg.ident.name.as_str() {
-                "unwrap_or" => {
+            ExprKind::MethodCall(seg, callee, [or], _) => match seg.ident.name {
+                sym::unwrap_or => {
                     if is_empty_slice(cx, or) {
                         check_map(cx, callee, span, self.msrv);
                     }
                 },
-                "unwrap_or_else" => {
+                sym::unwrap_or_else => {
                     if returns_empty_slice(cx, or) {
                         check_map(cx, callee, span, self.msrv);
                     }
                 },
                 _ => {},
             },
-            ExprKind::MethodCall(seg, callee, [or_else, map], _) => match seg.ident.name.as_str() {
-                "map_or" => {
+            ExprKind::MethodCall(seg, callee, [or_else, map], _) => match seg.ident.name {
+                sym::map_or => {
                     if is_empty_slice(cx, or_else) && is_slice_from_ref(cx, map) {
                         check_as_ref(cx, callee, span, self.msrv);
                     }
                 },
-                "map_or_else" => {
+                sym::map_or_else => {
                     if returns_empty_slice(cx, or_else) && is_slice_from_ref(cx, map) {
                         check_as_ref(cx, callee, span, self.msrv);
                     }
diff --git a/clippy_lints/src/methods/case_sensitive_file_extension_comparisons.rs b/clippy_lints/src/methods/case_sensitive_file_extension_comparisons.rs
index d07870d4951..292fa08b598 100644
--- a/clippy_lints/src/methods/case_sensitive_file_extension_comparisons.rs
+++ b/clippy_lints/src/methods/case_sensitive_file_extension_comparisons.rs
@@ -1,6 +1,7 @@
 use clippy_utils::diagnostics::span_lint_and_then;
 use clippy_utils::msrvs::{self, Msrv};
 use clippy_utils::source::{SpanRangeExt, indent_of, reindent_multiline};
+use clippy_utils::sym;
 use clippy_utils::ty::is_type_lang_item;
 use rustc_ast::ast::LitKind;
 use rustc_errors::Applicability;
@@ -21,8 +22,8 @@ pub(super) fn check<'tcx>(
 ) {
     if let ExprKind::MethodCall(path_segment, ..) = recv.kind
         && matches!(
-            path_segment.ident.name.as_str(),
-            "to_lowercase" | "to_uppercase" | "to_ascii_lowercase" | "to_ascii_uppercase"
+            path_segment.ident.name,
+            sym::to_lowercase | sym::to_uppercase | sym::to_ascii_lowercase | sym::to_ascii_uppercase
         )
     {
         return;
diff --git a/clippy_lints/src/methods/manual_c_str_literals.rs b/clippy_lints/src/methods/manual_c_str_literals.rs
index 0274e31b4c3..3fa83cd39d1 100644
--- a/clippy_lints/src/methods/manual_c_str_literals.rs
+++ b/clippy_lints/src/methods/manual_c_str_literals.rs
@@ -1,13 +1,13 @@
 use clippy_utils::diagnostics::span_lint_and_sugg;
-use clippy_utils::get_parent_expr;
 use clippy_utils::msrvs::{self, Msrv};
 use clippy_utils::source::snippet;
+use clippy_utils::{get_parent_expr, sym};
 use rustc_ast::{LitKind, StrStyle};
 use rustc_errors::Applicability;
 use rustc_hir::{Expr, ExprKind, Node, QPath, TyKind};
 use rustc_lint::LateContext;
 use rustc_span::edition::Edition::Edition2021;
-use rustc_span::{Span, Symbol, sym};
+use rustc_span::{Span, Symbol};
 
 use super::MANUAL_C_STR_LITERALS;
 
@@ -71,15 +71,15 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, func: &Expr<'_>, args
         && cx.tcx.sess.edition() >= Edition2021
         && msrv.meets(cx, msrvs::C_STR_LITERALS)
     {
-        match fn_name.as_str() {
-            name @ ("from_bytes_with_nul" | "from_bytes_with_nul_unchecked")
+        match fn_name {
+            sym::from_bytes_with_nul | sym::from_bytes_with_nul_unchecked
                 if !arg.span.from_expansion()
                     && let ExprKind::Lit(lit) = arg.kind
                     && let LitKind::ByteStr(_, StrStyle::Cooked) | LitKind::Str(_, StrStyle::Cooked) = lit.node =>
             {
-                check_from_bytes(cx, expr, arg, name);
+                check_from_bytes(cx, expr, arg, fn_name);
             },
-            "from_ptr" => check_from_ptr(cx, expr, arg),
+            sym::from_ptr => check_from_ptr(cx, expr, arg),
             _ => {},
         }
     }
@@ -106,13 +106,13 @@ fn check_from_ptr(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>) {
     }
 }
 /// Checks `CStr::from_bytes_with_nul(b"foo\0")`
-fn check_from_bytes(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, method: &str) {
+fn check_from_bytes(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, method: Symbol) {
     let (span, applicability) = if let Some(parent) = get_parent_expr(cx, expr)
         && let ExprKind::MethodCall(method, ..) = parent.kind
         && [sym::unwrap, sym::expect].contains(&method.ident.name)
     {
         (parent.span, Applicability::MachineApplicable)
-    } else if method == "from_bytes_with_nul_unchecked" {
+    } else if method == sym::from_bytes_with_nul_unchecked {
         // `*_unchecked` returns `&CStr` directly, nothing needs to be changed
         (expr.span, Applicability::MachineApplicable)
     } else {
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/methods/needless_collect.rs b/clippy_lints/src/methods/needless_collect.rs
index cd22583b8a2..4c1ed6a1d83 100644
--- a/clippy_lints/src/methods/needless_collect.rs
+++ b/clippy_lints/src/methods/needless_collect.rs
@@ -357,20 +357,20 @@ impl<'tcx> Visitor<'tcx> for IterFunctionVisitor<'_, 'tcx> {
                     if let Some(hir_id) = self.current_statement_hir_id {
                         self.hir_id_uses_map.insert(hir_id, self.uses.len());
                     }
-                    match method_name.ident.name.as_str() {
-                        "into_iter" => self.uses.push(Some(IterFunction {
+                    match method_name.ident.name {
+                        sym::into_iter => self.uses.push(Some(IterFunction {
                             func: IterFunctionKind::IntoIter(expr.hir_id),
                             span: expr.span,
                         })),
-                        "len" => self.uses.push(Some(IterFunction {
+                        sym::len => self.uses.push(Some(IterFunction {
                             func: IterFunctionKind::Len,
                             span: expr.span,
                         })),
-                        "is_empty" => self.uses.push(Some(IterFunction {
+                        sym::is_empty => self.uses.push(Some(IterFunction {
                             func: IterFunctionKind::IsEmpty,
                             span: expr.span,
                         })),
-                        "contains" => self.uses.push(Some(IterFunction {
+                        sym::contains => self.uses.push(Some(IterFunction {
                             func: IterFunctionKind::Contains(args[0].span),
                             span: expr.span,
                         })),
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/clippy_lints/src/mutable_debug_assertion.rs b/clippy_lints/src/mutable_debug_assertion.rs
index d5bed54f0be..9b327955608 100644
--- a/clippy_lints/src/mutable_debug_assertion.rs
+++ b/clippy_lints/src/mutable_debug_assertion.rs
@@ -1,5 +1,6 @@
 use clippy_utils::diagnostics::span_lint;
 use clippy_utils::macros::{find_assert_eq_args, root_macro_call_first_node};
+use clippy_utils::sym;
 use rustc_hir::intravisit::{Visitor, walk_expr};
 use rustc_hir::{BorrowKind, Expr, ExprKind, MatchSource, Mutability};
 use rustc_lint::{LateContext, LateLintPass};
@@ -42,10 +43,9 @@ impl<'tcx> LateLintPass<'tcx> for DebugAssertWithMutCall {
         let Some(macro_call) = root_macro_call_first_node(cx, e) else {
             return;
         };
-        let macro_name = cx.tcx.item_name(macro_call.def_id);
         if !matches!(
-            macro_name.as_str(),
-            "debug_assert" | "debug_assert_eq" | "debug_assert_ne"
+            cx.tcx.get_diagnostic_name(macro_call.def_id),
+            Some(sym::debug_assert_macro | sym::debug_assert_eq_macro | sym::debug_assert_ne_macro)
         ) {
             return;
         }
@@ -60,7 +60,10 @@ impl<'tcx> LateLintPass<'tcx> for DebugAssertWithMutCall {
                     cx,
                     DEBUG_ASSERT_WITH_MUT_CALL,
                     span,
-                    format!("do not call a function with mutable arguments inside of `{macro_name}!`"),
+                    format!(
+                        "do not call a function with mutable arguments inside of `{}!`",
+                        cx.tcx.item_name(macro_call.def_id)
+                    ),
                 );
             }
         }
diff --git a/clippy_lints/src/operators/eq_op.rs b/clippy_lints/src/operators/eq_op.rs
index 1421893274f..d79101a687d 100644
--- a/clippy_lints/src/operators/eq_op.rs
+++ b/clippy_lints/src/operators/eq_op.rs
@@ -1,20 +1,18 @@
 use clippy_utils::ast_utils::is_useless_with_eq_exprs;
 use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
 use clippy_utils::macros::{find_assert_eq_args, first_node_macro_backtrace};
-use clippy_utils::{eq_expr_value, is_in_test_function};
+use clippy_utils::{eq_expr_value, is_in_test_function, sym};
 use rustc_hir::{BinOpKind, Expr};
 use rustc_lint::LateContext;
 
 use super::EQ_OP;
 
 pub(crate) fn check_assert<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
-    if let Some((macro_call, macro_name)) = first_node_macro_backtrace(cx, e).find_map(|macro_call| {
-        let name = cx.tcx.item_name(macro_call.def_id);
+    if let Some(macro_call) = first_node_macro_backtrace(cx, e).find(|macro_call| {
         matches!(
-            name.as_str(),
-            "assert_eq" | "assert_ne" | "debug_assert_eq" | "debug_assert_ne"
+            cx.tcx.get_diagnostic_name(macro_call.def_id),
+            Some(sym::assert_eq_macro | sym::assert_ne_macro | sym::debug_assert_eq_macro | sym::debug_assert_ne_macro)
         )
-        .then(|| (macro_call, name))
     }) && let Some((lhs, rhs, _)) = find_assert_eq_args(cx, e, macro_call.expn)
         && eq_expr_value(cx, lhs, rhs)
         && macro_call.is_local()
@@ -24,7 +22,10 @@ pub(crate) fn check_assert<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
             cx,
             EQ_OP,
             lhs.span.to(rhs.span),
-            format!("identical args used in this `{macro_name}!` macro call"),
+            format!(
+                "identical args used in this `{}!` macro call",
+                cx.tcx.item_name(macro_call.def_id)
+            ),
         );
     }
 }
diff --git a/clippy_lints/src/panic_in_result_fn.rs b/clippy_lints/src/panic_in_result_fn.rs
index eebc62e2a5a..ee1d59490ce 100644
--- a/clippy_lints/src/panic_in_result_fn.rs
+++ b/clippy_lints/src/panic_in_result_fn.rs
@@ -1,5 +1,5 @@
 use clippy_utils::diagnostics::span_lint_and_then;
-use clippy_utils::macros::root_macro_call_first_node;
+use clippy_utils::macros::{is_panic, root_macro_call_first_node};
 use clippy_utils::ty::is_type_diagnostic_item;
 use clippy_utils::visitors::{Descend, for_each_expr};
 use clippy_utils::{is_inside_always_const_context, return_ty};
@@ -69,10 +69,11 @@ fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, body: &'tcx hir
             return ControlFlow::Continue(Descend::Yes);
         };
         if !is_inside_always_const_context(cx.tcx, e.hir_id)
-            && matches!(
-                cx.tcx.item_name(macro_call.def_id).as_str(),
-                "panic" | "assert" | "assert_eq" | "assert_ne"
-            )
+            && (is_panic(cx, macro_call.def_id)
+                || matches!(
+                    cx.tcx.get_diagnostic_name(macro_call.def_id),
+                    Some(sym::assert_macro | sym::assert_eq_macro | sym::assert_ne_macro)
+                ))
         {
             panics.push(macro_call.span);
             ControlFlow::Continue(Descend::No)
diff --git a/clippy_lints/src/panic_unimplemented.rs b/clippy_lints/src/panic_unimplemented.rs
index 39ae9967e01..8962f36db1e 100644
--- a/clippy_lints/src/panic_unimplemented.rs
+++ b/clippy_lints/src/panic_unimplemented.rs
@@ -113,8 +113,8 @@ impl<'tcx> LateLintPass<'tcx> for PanicUnimplemented {
                 );
                 return;
             }
-            match cx.tcx.item_name(macro_call.def_id).as_str() {
-                "todo" => {
+            match cx.tcx.get_diagnostic_name(macro_call.def_id) {
+                Some(sym::todo_macro) => {
                     span_lint(
                         cx,
                         TODO,
@@ -122,7 +122,7 @@ impl<'tcx> LateLintPass<'tcx> for PanicUnimplemented {
                         "`todo` should not be present in production code",
                     );
                 },
-                "unimplemented" => {
+                Some(sym::unimplemented_macro) => {
                     span_lint(
                         cx,
                         UNIMPLEMENTED,
@@ -130,7 +130,7 @@ impl<'tcx> LateLintPass<'tcx> for PanicUnimplemented {
                         "`unimplemented` should not be present in production code",
                     );
                 },
-                "unreachable" => {
+                Some(sym::unreachable_macro) => {
                     span_lint(cx, UNREACHABLE, macro_call.span, "usage of the `unreachable!` macro");
                 },
                 _ => {},
diff --git a/clippy_lints/src/returns.rs b/clippy_lints/src/returns.rs
index d8e8ead2912..122d97fdf81 100644
--- a/clippy_lints/src/returns.rs
+++ b/clippy_lints/src/returns.rs
@@ -5,7 +5,7 @@ use clippy_utils::visitors::for_each_expr;
 use clippy_utils::{
     binary_expr_needs_parentheses, fn_def_id, is_from_proc_macro, is_inside_let_else, is_res_lang_ctor,
     leaks_droppable_temporary_with_limited_lifetime, path_res, path_to_local_id, span_contains_cfg,
-    span_find_starting_semi,
+    span_find_starting_semi, sym,
 };
 use core::ops::ControlFlow;
 use rustc_ast::MetaItemInner;
@@ -22,7 +22,7 @@ use rustc_middle::ty::{self, GenericArgKind, Ty};
 use rustc_session::declare_lint_pass;
 use rustc_span::def_id::LocalDefId;
 use rustc_span::edition::Edition;
-use rustc_span::{BytePos, Pos, Span, sym};
+use rustc_span::{BytePos, Pos, Span};
 use std::borrow::Cow;
 use std::fmt::Display;
 
@@ -411,8 +411,8 @@ fn check_final_expr<'tcx>(
                         && let [tool, lint_name] = meta_item.path.segments.as_slice()
                         && tool.ident.name == sym::clippy
                         && matches!(
-                            lint_name.ident.name.as_str(),
-                            "needless_return" | "style" | "all" | "warnings"
+                            lint_name.ident.name,
+                            sym::needless_return | sym::style | sym::all | sym::warnings
                         )
                     {
                         // This is an expectation of the `needless_return` lint
diff --git a/clippy_lints/src/strings.rs b/clippy_lints/src/strings.rs
index af4d0d541f1..73a9fe71e00 100644
--- a/clippy_lints/src/strings.rs
+++ b/clippy_lints/src/strings.rs
@@ -334,7 +334,7 @@ impl<'tcx> LateLintPass<'tcx> for StringLitAsBytes {
         if let ExprKind::MethodCall(path, recv, [], _) = &e.kind
             && path.ident.name == sym::into_bytes
             && let ExprKind::MethodCall(path, recv, [], _) = &recv.kind
-            && matches!(path.ident.name.as_str(), "to_owned" | "to_string")
+            && matches!(path.ident.name, sym::to_owned | sym::to_string)
             && let ExprKind::Lit(lit) = &recv.kind
             && let LitKind::Str(lit_content, _) = &lit.node
             && lit_content.as_str().is_ascii()
diff --git a/clippy_lints/src/unit_types/unit_cmp.rs b/clippy_lints/src/unit_types/unit_cmp.rs
index 6dcc1195a70..48b532968cb 100644
--- a/clippy_lints/src/unit_types/unit_cmp.rs
+++ b/clippy_lints/src/unit_types/unit_cmp.rs
@@ -1,5 +1,6 @@
 use clippy_utils::diagnostics::span_lint;
 use clippy_utils::macros::{find_assert_eq_args, root_macro_call_first_node};
+use clippy_utils::sym;
 use rustc_hir::{BinOpKind, Expr, ExprKind};
 use rustc_lint::LateContext;
 
@@ -7,11 +8,12 @@ use super::UNIT_CMP;
 
 pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
     if expr.span.from_expansion() {
-        if let Some(macro_call) = root_macro_call_first_node(cx, expr) {
-            let macro_name = cx.tcx.item_name(macro_call.def_id);
-            let result = match macro_name.as_str() {
-                "assert_eq" | "debug_assert_eq" => "succeed",
-                "assert_ne" | "debug_assert_ne" => "fail",
+        if let Some(macro_call) = root_macro_call_first_node(cx, expr)
+            && let Some(diag_name) = cx.tcx.get_diagnostic_name(macro_call.def_id)
+        {
+            let result = match diag_name {
+                sym::assert_eq_macro | sym::debug_assert_eq_macro => "succeed",
+                sym::assert_ne_macro | sym::debug_assert_ne_macro => "fail",
                 _ => return,
             };
             let Some((left, _, _)) = find_assert_eq_args(cx, expr, macro_call.expn) else {
@@ -24,7 +26,10 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
                 cx,
                 UNIT_CMP,
                 macro_call.span,
-                format!("`{macro_name}` of unit values detected. This will always {result}"),
+                format!(
+                    "`{}` of unit values detected. This will always {result}",
+                    cx.tcx.item_name(macro_call.def_id)
+                ),
             );
         }
         return;
diff --git a/clippy_lints/src/unused_io_amount.rs b/clippy_lints/src/unused_io_amount.rs
index e3f28908ff8..5e1cb9e54f5 100644
--- a/clippy_lints/src/unused_io_amount.rs
+++ b/clippy_lints/src/unused_io_amount.rs
@@ -226,7 +226,7 @@ fn is_unreachable_or_panic(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
     if is_panic(cx, macro_call.def_id) {
         return !cx.tcx.hir_is_inside_const_context(expr.hir_id);
     }
-    matches!(cx.tcx.item_name(macro_call.def_id).as_str(), "unreachable")
+    cx.tcx.is_diagnostic_item(sym::unreachable_macro, macro_call.def_id)
 }
 
 fn unpack_call_chain<'a>(mut expr: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> {
diff --git a/clippy_lints/src/write.rs b/clippy_lints/src/write.rs
index f24c127c452..a8758b65c91 100644
--- a/clippy_lints/src/write.rs
+++ b/clippy_lints/src/write.rs
@@ -498,9 +498,9 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) {
                     None => return,
                 },
                 LitKind::Char => (
-                    match lit.symbol.as_str() {
-                        "\"" => "\\\"",
-                        "\\'" => "'",
+                    match lit.symbol {
+                        sym::DOUBLE_QUOTE => "\\\"",
+                        sym::BACKSLASH_SINGLE_QUOTE => "'",
                         _ => match value_string.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')) {
                             Some(stripped) => stripped,
                             None => return,
diff --git a/clippy_lints_internal/src/symbols.rs b/clippy_lints_internal/src/symbols.rs
index bf166988a0c..5aee545fb0f 100644
--- a/clippy_lints_internal/src/symbols.rs
+++ b/clippy_lints_internal/src/symbols.rs
@@ -4,7 +4,7 @@ use rustc_ast::LitKind;
 use rustc_data_structures::fx::FxHashMap;
 use rustc_errors::Applicability;
 use rustc_hir::def::{DefKind, Res};
-use rustc_hir::{Expr, ExprKind};
+use rustc_hir::{Expr, ExprKind, Lit, Node, Pat, PatExprKind, PatKind};
 use rustc_lint::{LateContext, LateLintPass};
 use rustc_lint_defs::declare_tool_lint;
 use rustc_middle::mir::ConstValue;
@@ -65,6 +65,39 @@ pub struct Symbols {
 
 impl_lint_pass!(Symbols => [INTERNING_LITERALS, SYMBOL_AS_STR]);
 
+impl Symbols {
+    fn lit_suggestion(&self, lit: &Lit) -> Option<(Span, String)> {
+        if let LitKind::Str(name, _) = lit.node {
+            let sugg = if let Some((prefix, name)) = self.symbol_map.get(&name.as_u32()) {
+                format!("{prefix}::{name}")
+            } else {
+                format!("sym::{}", name.as_str().replace(|ch: char| !ch.is_alphanumeric(), "_"))
+            };
+            Some((lit.span, sugg))
+        } else {
+            None
+        }
+    }
+
+    fn expr_suggestion(&self, expr: &Expr<'_>) -> Option<(Span, String)> {
+        if let ExprKind::Lit(lit) = expr.kind {
+            self.lit_suggestion(lit)
+        } else {
+            None
+        }
+    }
+
+    fn pat_suggestions(&self, pat: &Pat<'_>, suggestions: &mut Vec<(Span, String)>) {
+        pat.walk_always(|pat| {
+            if let PatKind::Expr(pat_expr) = pat.kind
+                && let PatExprKind::Lit { lit, .. } = pat_expr.kind
+            {
+                suggestions.extend(self.lit_suggestion(lit));
+            }
+        });
+    }
+}
+
 impl<'tcx> LateLintPass<'tcx> for Symbols {
     fn check_crate(&mut self, cx: &LateContext<'_>) {
         let modules = [
@@ -75,7 +108,7 @@ impl<'tcx> LateLintPass<'tcx> for Symbols {
         for (prefix, module) in modules {
             for def_id in module.get(cx) {
                 // When linting `clippy_utils` itself we can't use `module_children` as it's a local def id. It will
-                // still lint but the suggestion will say to add it to `sym.rs` even if it's already there
+                // still lint but the suggestion may suggest the incorrect name for symbols such as `sym::CRLF`
                 if def_id.is_local() {
                     continue;
                 }
@@ -98,8 +131,7 @@ impl<'tcx> LateLintPass<'tcx> for Symbols {
         if let ExprKind::Call(func, [arg]) = &expr.kind
             && let ty::FnDef(def_id, _) = cx.typeck_results().expr_ty(func).kind()
             && cx.tcx.is_diagnostic_item(sym::SymbolIntern, *def_id)
-            && let ExprKind::Lit(lit) = arg.kind
-            && let LitKind::Str(name, _) = lit.node
+            && let Some((_, sugg)) = self.expr_suggestion(arg)
         {
             span_lint_and_then(
                 cx,
@@ -107,48 +139,55 @@ impl<'tcx> LateLintPass<'tcx> for Symbols {
                 expr.span,
                 "interning a string literal",
                 |diag| {
-                    let (message, path) = suggestion(&mut self.symbol_map, name);
-                    diag.span_suggestion_verbose(expr.span, message, path, Applicability::MaybeIncorrect);
+                    diag.span_suggestion_verbose(
+                        expr.span,
+                        "use a preinterned symbol instead",
+                        sugg,
+                        Applicability::MaybeIncorrect,
+                    );
+                    diag.help("add the symbol to `clippy_utils/src/sym.rs` if needed");
                 },
             );
         }
 
-        if let ExprKind::Binary(_, lhs, rhs) = expr.kind {
-            check_binary(cx, lhs, rhs, &mut self.symbol_map);
-            check_binary(cx, rhs, lhs, &mut self.symbol_map);
-        }
-    }
-}
+        if let Some(as_str) = as_str_span(cx, expr)
+            && let Node::Expr(parent) = cx.tcx.parent_hir_node(expr.hir_id)
+        {
+            let mut suggestions = Vec::new();
 
-fn check_binary(
-    cx: &LateContext<'_>,
-    lhs: &Expr<'_>,
-    rhs: &Expr<'_>,
-    symbols: &mut FxHashMap<u32, (&'static str, Symbol)>,
-) {
-    if let Some(removal_span) = as_str_span(cx, lhs)
-        && let ExprKind::Lit(lit) = rhs.kind
-        && let LitKind::Str(name, _) = lit.node
-    {
-        span_lint_and_then(cx, SYMBOL_AS_STR, lhs.span, "converting a Symbol to a string", |diag| {
-            let (message, path) = suggestion(symbols, name);
-            diag.multipart_suggestion_verbose(
-                message,
-                vec![(removal_span, String::new()), (rhs.span, path)],
-                Applicability::MachineApplicable,
-            );
-        });
-    }
-}
+            match parent.kind {
+                ExprKind::Binary(_, lhs, rhs) => {
+                    suggestions.extend(self.expr_suggestion(lhs));
+                    suggestions.extend(self.expr_suggestion(rhs));
+                },
+                ExprKind::Match(_, arms, _) => {
+                    for arm in arms {
+                        self.pat_suggestions(arm.pat, &mut suggestions);
+                    }
+                },
+                _ => {},
+            }
 
-fn suggestion(symbols: &mut FxHashMap<u32, (&'static str, Symbol)>, name: Symbol) -> (&'static str, String) {
-    if let Some((prefix, name)) = symbols.get(&name.as_u32()) {
-        ("use the preinterned symbol", format!("{prefix}::{name}"))
-    } else {
-        (
-            "add the symbol to `clippy_utils/src/sym.rs` and use it",
-            format!("sym::{}", name.as_str().replace(|ch: char| !ch.is_alphanumeric(), "_")),
-        )
+            if suggestions.is_empty() {
+                return;
+            }
+
+            span_lint_and_then(
+                cx,
+                SYMBOL_AS_STR,
+                expr.span,
+                "converting a Symbol to a string",
+                |diag| {
+                    suggestions.push((as_str, String::new()));
+                    diag.multipart_suggestion(
+                        "use preinterned symbols instead",
+                        suggestions,
+                        Applicability::MaybeIncorrect,
+                    );
+                    diag.help("add the symbols to `clippy_utils/src/sym.rs` if needed");
+                },
+            );
+        }
     }
 }
 
diff --git a/clippy_utils/src/sym.rs b/clippy_utils/src/sym.rs
index 94b73f37269..9428262b99a 100644
--- a/clippy_utils/src/sym.rs
+++ b/clippy_utils/src/sym.rs
@@ -32,12 +32,14 @@ macro_rules! generate {
 generate! {
     abs,
     align_of,
+    ambiguous_glob_reexports,
     as_bytes,
     as_deref_mut,
     as_deref,
     as_mut,
     AsyncReadExt,
     AsyncWriteExt,
+    BACKSLASH_SINGLE_QUOTE: r"\'",
     Binary,
     build_hasher,
     bytes,
@@ -59,8 +61,11 @@ generate! {
     de,
     Deserialize,
     diagnostics,
+    disallowed_types,
+    DOUBLE_QUOTE: "\"",
     EarlyLintPass,
     ends_with,
+    enum_glob_use,
     error,
     ErrorKind,
     exp,
@@ -69,12 +74,16 @@ generate! {
     finish,
     flat_map,
     for_each,
+    from_bytes_with_nul_unchecked,
+    from_bytes_with_nul,
+    from_ptr,
     from_raw,
     from_ref,
     from_str_radix,
     fs,
     futures_util,
     get,
+    hidden_glob_reexports,
     hygiene,
     insert,
     int_roundings,
@@ -96,20 +105,27 @@ generate! {
     Lazy,
     LF: "\n",
     Lint,
+    ln,
     lock_api,
+    log,
     LowerExp,
     LowerHex,
+    macro_use_imports,
+    map_or_else,
+    map_or,
     max,
     MAX,
     mem,
     min,
     MIN,
     mode,
+    module_name_repetitions,
     msrv,
     msrvs,
     MsrvStack,
     mut_ptr,
     mutex,
+    needless_return,
     next_tuple,
     Octal,
     once_cell,
@@ -119,7 +135,10 @@ generate! {
     parse,
     PathLookup,
     paths,
+    powf,
+    powi,
     push,
+    redundant_pub_crate,
     regex,
     Regex,
     RegexBuilder,
@@ -137,29 +156,45 @@ generate! {
     set_mode,
     set_readonly,
     signum,
+    single_component_path_imports,
     span_lint_and_then,
     split_whitespace,
     split,
+    sqrt,
     Start,
     Step,
+    style,
     symbol,
     Symbol,
     SyntaxContext,
     take,
     TBD,
     then_some,
+    to_ascii_lowercase,
+    to_ascii_uppercase,
     to_digit,
+    to_lowercase,
     to_owned,
+    to_uppercase,
     tokio,
+    unreachable_pub,
+    unsafe_removed_from_name,
+    unused_braces,
     unused_extern_crates,
+    unused_import_braces,
+    unused_trait_names,
+    unused,
     unwrap_err,
     unwrap_or_default,
+    unwrap_or_else,
     UpperExp,
     UpperHex,
     V4,
     V6,
     Visitor,
+    warnings,
     Weak,
+    wildcard_imports,
     with_capacity,
     wrapping_offset,
 }
diff --git a/tests/ui-internal/interning_literals.stderr b/tests/ui-internal/interning_literals.stderr
index 628b97eff84..9ff4194e542 100644
--- a/tests/ui-internal/interning_literals.stderr
+++ b/tests/ui-internal/interning_literals.stderr
@@ -4,9 +4,10 @@ error: interning a string literal
 LL |     let _ = Symbol::intern("f32");
    |             ^^^^^^^^^^^^^^^^^^^^^
    |
+   = help: add the symbol to `clippy_utils/src/sym.rs` if needed
    = note: `-D clippy::interning-literals` implied by `-D warnings`
    = help: to override `-D warnings` add `#[allow(clippy::interning_literals)]`
-help: use the preinterned symbol
+help: use a preinterned symbol instead
    |
 LL -     let _ = Symbol::intern("f32");
 LL +     let _ = sym::f32;
@@ -18,7 +19,8 @@ error: interning a string literal
 LL |     let _ = Symbol::intern("proc-macro");
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
-help: use the preinterned symbol
+   = help: add the symbol to `clippy_utils/src/sym.rs` if needed
+help: use a preinterned symbol instead
    |
 LL -     let _ = Symbol::intern("proc-macro");
 LL +     let _ = sym::proc_dash_macro;
@@ -30,7 +32,8 @@ error: interning a string literal
 LL |     let _ = Symbol::intern("self");
    |             ^^^^^^^^^^^^^^^^^^^^^^
    |
-help: use the preinterned symbol
+   = help: add the symbol to `clippy_utils/src/sym.rs` if needed
+help: use a preinterned symbol instead
    |
 LL -     let _ = Symbol::intern("self");
 LL +     let _ = kw::SelfLower;
@@ -42,7 +45,8 @@ error: interning a string literal
 LL |     let _ = Symbol::intern("msrv");
    |             ^^^^^^^^^^^^^^^^^^^^^^
    |
-help: use the preinterned symbol
+   = help: add the symbol to `clippy_utils/src/sym.rs` if needed
+help: use a preinterned symbol instead
    |
 LL -     let _ = Symbol::intern("msrv");
 LL +     let _ = sym::msrv;
@@ -54,7 +58,8 @@ error: interning a string literal
 LL |     let _ = Symbol::intern("Cargo.toml");
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
-help: use the preinterned symbol
+   = help: add the symbol to `clippy_utils/src/sym.rs` if needed
+help: use a preinterned symbol instead
    |
 LL -     let _ = Symbol::intern("Cargo.toml");
 LL +     let _ = sym::Cargo_toml;
diff --git a/tests/ui-internal/interning_literals_unfixable.stderr b/tests/ui-internal/interning_literals_unfixable.stderr
index 8294453a8f9..879d9e633c2 100644
--- a/tests/ui-internal/interning_literals_unfixable.stderr
+++ b/tests/ui-internal/interning_literals_unfixable.stderr
@@ -4,9 +4,10 @@ error: interning a string literal
 LL |     let _ = Symbol::intern("xyz123");
    |             ^^^^^^^^^^^^^^^^^^^^^^^^
    |
+   = help: add the symbol to `clippy_utils/src/sym.rs` if needed
    = note: `-D clippy::interning-literals` implied by `-D warnings`
    = help: to override `-D warnings` add `#[allow(clippy::interning_literals)]`
-help: add the symbol to `clippy_utils/src/sym.rs` and use it
+help: use a preinterned symbol instead
    |
 LL -     let _ = Symbol::intern("xyz123");
 LL +     let _ = sym::xyz123;
@@ -18,7 +19,8 @@ error: interning a string literal
 LL |     let _ = Symbol::intern("with-dash");
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
-help: add the symbol to `clippy_utils/src/sym.rs` and use it
+   = help: add the symbol to `clippy_utils/src/sym.rs` if needed
+help: use a preinterned symbol instead
    |
 LL -     let _ = Symbol::intern("with-dash");
 LL +     let _ = sym::with_dash;
@@ -30,7 +32,8 @@ error: interning a string literal
 LL |     let _ = Symbol::intern("with.dot");
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
-help: add the symbol to `clippy_utils/src/sym.rs` and use it
+   = help: add the symbol to `clippy_utils/src/sym.rs` if needed
+help: use a preinterned symbol instead
    |
 LL -     let _ = Symbol::intern("with.dot");
 LL +     let _ = sym::with_dot;
diff --git a/tests/ui-internal/symbol_as_str.fixed b/tests/ui-internal/symbol_as_str.fixed
index 3e26732836c..6a71b16c604 100644
--- a/tests/ui-internal/symbol_as_str.fixed
+++ b/tests/ui-internal/symbol_as_str.fixed
@@ -18,4 +18,11 @@ fn f(s: Symbol) {
     //~^ symbol_as_str
     sym::get == s;
     //~^ symbol_as_str
+
+    let _ = match s {
+        //~^ symbol_as_str
+        sym::unwrap_err => 1,
+        sym::unwrap_or_default | sym::unwrap_or_else => 2,
+        _ => 3,
+    };
 }
diff --git a/tests/ui-internal/symbol_as_str.rs b/tests/ui-internal/symbol_as_str.rs
index 334c32d1898..43136504bf1 100644
--- a/tests/ui-internal/symbol_as_str.rs
+++ b/tests/ui-internal/symbol_as_str.rs
@@ -18,4 +18,11 @@ fn f(s: Symbol) {
     //~^ symbol_as_str
     "get" == s.as_str();
     //~^ symbol_as_str
+
+    let _ = match s.as_str() {
+        //~^ symbol_as_str
+        "unwrap_err" => 1,
+        "unwrap_or_default" | "unwrap_or_else" => 2,
+        _ => 3,
+    };
 }
diff --git a/tests/ui-internal/symbol_as_str.stderr b/tests/ui-internal/symbol_as_str.stderr
index 39f81f3833c..3eeead4aa8c 100644
--- a/tests/ui-internal/symbol_as_str.stderr
+++ b/tests/ui-internal/symbol_as_str.stderr
@@ -4,9 +4,10 @@ error: converting a Symbol to a string
 LL |     s.as_str() == "f32";
    |     ^^^^^^^^^^
    |
+   = help: add the symbols to `clippy_utils/src/sym.rs` if needed
    = note: `-D clippy::symbol-as-str` implied by `-D warnings`
    = help: to override `-D warnings` add `#[allow(clippy::symbol_as_str)]`
-help: use the preinterned symbol
+help: use preinterned symbols instead
    |
 LL -     s.as_str() == "f32";
 LL +     s == sym::f32;
@@ -18,7 +19,8 @@ error: converting a Symbol to a string
 LL |     s.as_str() == "proc-macro";
    |     ^^^^^^^^^^
    |
-help: use the preinterned symbol
+   = help: add the symbols to `clippy_utils/src/sym.rs` if needed
+help: use preinterned symbols instead
    |
 LL -     s.as_str() == "proc-macro";
 LL +     s == sym::proc_dash_macro;
@@ -30,7 +32,8 @@ error: converting a Symbol to a string
 LL |     s.as_str() == "self";
    |     ^^^^^^^^^^
    |
-help: use the preinterned symbol
+   = help: add the symbols to `clippy_utils/src/sym.rs` if needed
+help: use preinterned symbols instead
    |
 LL -     s.as_str() == "self";
 LL +     s == kw::SelfLower;
@@ -42,7 +45,8 @@ error: converting a Symbol to a string
 LL |     s.as_str() == "msrv";
    |     ^^^^^^^^^^
    |
-help: use the preinterned symbol
+   = help: add the symbols to `clippy_utils/src/sym.rs` if needed
+help: use preinterned symbols instead
    |
 LL -     s.as_str() == "msrv";
 LL +     s == sym::msrv;
@@ -54,7 +58,8 @@ error: converting a Symbol to a string
 LL |     s.as_str() == "Cargo.toml";
    |     ^^^^^^^^^^
    |
-help: use the preinterned symbol
+   = help: add the symbols to `clippy_utils/src/sym.rs` if needed
+help: use preinterned symbols instead
    |
 LL -     s.as_str() == "Cargo.toml";
 LL +     s == sym::Cargo_toml;
@@ -66,11 +71,27 @@ error: converting a Symbol to a string
 LL |     "get" == s.as_str();
    |              ^^^^^^^^^^
    |
-help: use the preinterned symbol
+   = help: add the symbols to `clippy_utils/src/sym.rs` if needed
+help: use preinterned symbols instead
    |
 LL -     "get" == s.as_str();
 LL +     sym::get == s;
    |
 
-error: aborting due to 6 previous errors
+error: converting a Symbol to a string
+  --> tests/ui-internal/symbol_as_str.rs:22:19
+   |
+LL |     let _ = match s.as_str() {
+   |                   ^^^^^^^^^^
+   |
+   = help: add the symbols to `clippy_utils/src/sym.rs` if needed
+help: use preinterned symbols instead
+   |
+LL ~     let _ = match s {
+LL |
+LL ~         sym::unwrap_err => 1,
+LL ~         sym::unwrap_or_default | sym::unwrap_or_else => 2,
+   |
+
+error: aborting due to 7 previous errors
 
diff --git a/tests/ui-internal/symbol_as_str_unfixable.stderr b/tests/ui-internal/symbol_as_str_unfixable.stderr
index 5349983ca51..65664ebb451 100644
--- a/tests/ui-internal/symbol_as_str_unfixable.stderr
+++ b/tests/ui-internal/symbol_as_str_unfixable.stderr
@@ -4,9 +4,10 @@ error: converting a Symbol to a string
 LL |     s.as_str() == "xyz123";
    |     ^^^^^^^^^^
    |
+   = help: add the symbols to `clippy_utils/src/sym.rs` if needed
    = note: `-D clippy::symbol-as-str` implied by `-D warnings`
    = help: to override `-D warnings` add `#[allow(clippy::symbol_as_str)]`
-help: add the symbol to `clippy_utils/src/sym.rs` and use it
+help: use preinterned symbols instead
    |
 LL -     s.as_str() == "xyz123";
 LL +     s == sym::xyz123;
@@ -18,7 +19,8 @@ error: converting a Symbol to a string
 LL |     s.as_str() == "with-dash";
    |     ^^^^^^^^^^
    |
-help: add the symbol to `clippy_utils/src/sym.rs` and use it
+   = help: add the symbols to `clippy_utils/src/sym.rs` if needed
+help: use preinterned symbols instead
    |
 LL -     s.as_str() == "with-dash";
 LL +     s == sym::with_dash;
@@ -30,7 +32,8 @@ error: converting a Symbol to a string
 LL |     s.as_str() == "with.dot";
    |     ^^^^^^^^^^
    |
-help: add the symbol to `clippy_utils/src/sym.rs` and use it
+   = help: add the symbols to `clippy_utils/src/sym.rs` if needed
+help: use preinterned symbols instead
    |
 LL -     s.as_str() == "with.dot";
 LL +     s == sym::with_dot;
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