about summary refs log tree commit diff
path: root/clippy_lints/src
diff options
context:
space:
mode:
Diffstat (limited to 'clippy_lints/src')
-rw-r--r--clippy_lints/src/arc_with_non_send_sync.rs2
-rw-r--r--clippy_lints/src/assigning_clones.rs78
-rw-r--r--clippy_lints/src/attrs/allow_attributes.rs4
-rw-r--r--clippy_lints/src/attrs/allow_attributes_without_reason.rs2
-rw-r--r--clippy_lints/src/attrs/mod.rs16
-rw-r--r--clippy_lints/src/casts/ref_as_ptr.rs4
-rw-r--r--clippy_lints/src/cognitive_complexity.rs22
-rw-r--r--clippy_lints/src/copies.rs16
-rw-r--r--clippy_lints/src/declared_lints.rs3
-rw-r--r--clippy_lints/src/dereference.rs48
-rw-r--r--clippy_lints/src/field_scoped_visibility_modifiers.rs75
-rw-r--r--clippy_lints/src/from_over_into.rs10
-rw-r--r--clippy_lints/src/implicit_hasher.rs40
-rw-r--r--clippy_lints/src/implicit_return.rs6
-rw-r--r--clippy_lints/src/lib.rs5
-rw-r--r--clippy_lints/src/loops/mod.rs6
-rw-r--r--clippy_lints/src/manual_unwrap_or_default.rs9
-rw-r--r--clippy_lints/src/matches/manual_unwrap_or.rs111
-rw-r--r--clippy_lints/src/matches/mod.rs10
-rw-r--r--clippy_lints/src/matches/single_match.rs4
-rw-r--r--clippy_lints/src/methods/manual_inspect.rs238
-rw-r--r--clippy_lints/src/methods/mod.rs54
-rw-r--r--clippy_lints/src/methods/unnecessary_min_or_max.rs90
-rw-r--r--clippy_lints/src/methods/unnecessary_sort_by.rs3
-rw-r--r--clippy_lints/src/missing_const_for_fn.rs24
-rw-r--r--clippy_lints/src/missing_doc.rs7
-rw-r--r--clippy_lints/src/multiple_bound_locations.rs8
-rw-r--r--clippy_lints/src/mutable_debug_assertion.rs4
-rw-r--r--clippy_lints/src/needless_borrows_for_generic_args.rs8
-rw-r--r--clippy_lints/src/needless_else.rs16
-rw-r--r--clippy_lints/src/needless_for_each.rs6
-rw-r--r--clippy_lints/src/needless_if.rs24
-rw-r--r--clippy_lints/src/non_octal_unix_permissions.rs12
-rw-r--r--clippy_lints/src/octal_escapes.rs158
-rw-r--r--clippy_lints/src/ranges.rs19
-rw-r--r--clippy_lints/src/string_patterns.rs25
-rw-r--r--clippy_lints/src/upper_case_acronyms.rs102
-rw-r--r--clippy_lints/src/utils/internal_lints/invalid_paths.rs1
38 files changed, 929 insertions, 341 deletions
diff --git a/clippy_lints/src/arc_with_non_send_sync.rs b/clippy_lints/src/arc_with_non_send_sync.rs
index 38933897389..d57ab539fff 100644
--- a/clippy_lints/src/arc_with_non_send_sync.rs
+++ b/clippy_lints/src/arc_with_non_send_sync.rs
@@ -17,7 +17,7 @@ declare_clippy_lint! {
     /// `Arc<T>` is a thread-safe `Rc<T>` and guarantees that updates to the reference counter
     /// use atomic operations. To send an `Arc<T>` across thread boundaries and
     /// share ownership between multiple threads, `T` must be [both `Send` and `Sync`](https://doc.rust-lang.org/std/sync/struct.Arc.html#thread-safety),
-    /// so either `T` should be made `Send + Sync` or an `Rc` should be used instead of an `Arc`
+    /// so either `T` should be made `Send + Sync` or an `Rc` should be used instead of an `Arc`.
     ///
     /// ### Example
     /// ```no_run
diff --git a/clippy_lints/src/assigning_clones.rs b/clippy_lints/src/assigning_clones.rs
index e94a6f3e3fc..05ea74b0d53 100644
--- a/clippy_lints/src/assigning_clones.rs
+++ b/clippy_lints/src/assigning_clones.rs
@@ -1,16 +1,18 @@
 use clippy_config::msrvs::{self, Msrv};
 use clippy_utils::diagnostics::span_lint_and_then;
 use clippy_utils::macros::HirNode;
+use clippy_utils::mir::{enclosing_mir, PossibleBorrowerMap};
 use clippy_utils::sugg::Sugg;
 use clippy_utils::{is_trait_method, local_is_initialized, path_to_local};
 use rustc_errors::Applicability;
 use rustc_hir::{self as hir, Expr, ExprKind};
 use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::mir;
 use rustc_middle::ty::{self, Instance, Mutability};
 use rustc_session::impl_lint_pass;
 use rustc_span::def_id::DefId;
 use rustc_span::symbol::sym;
-use rustc_span::{ExpnKind, SyntaxContext};
+use rustc_span::{ExpnKind, Span, SyntaxContext};
 
 declare_clippy_lint! {
     /// ### What it does
@@ -144,6 +146,7 @@ fn extract_call<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<
     };
 
     Some(CallCandidate {
+        span: expr.span,
         target,
         kind,
         method_def_id: resolved_method.def_id(),
@@ -215,6 +218,10 @@ fn is_ok_to_suggest<'tcx>(cx: &LateContext<'tcx>, lhs: &Expr<'tcx>, call: &CallC
         return false;
     };
 
+    if clone_source_borrows_from_dest(cx, lhs, call.span) {
+        return false;
+    }
+
     // Now take a look if the impl block defines an implementation for the method that we're interested
     // in. If not, then we're using a default implementation, which is not interesting, so we will
     // not suggest the lint.
@@ -222,6 +229,74 @@ fn is_ok_to_suggest<'tcx>(cx: &LateContext<'tcx>, lhs: &Expr<'tcx>, call: &CallC
     implemented_fns.contains_key(&provided_fn.def_id)
 }
 
+/// Checks if the data being cloned borrows from the place that is being assigned to:
+///
+/// ```
+/// let mut s = String::new();
+/// let s2 = &s;
+/// s = s2.to_owned();
+/// ```
+///
+/// This cannot be written `s2.clone_into(&mut s)` because it has conflicting borrows.
+fn clone_source_borrows_from_dest(cx: &LateContext<'_>, lhs: &Expr<'_>, call_span: Span) -> bool {
+    /// If this basic block only exists to drop a local as part of an assignment, returns its
+    /// successor. Otherwise returns the basic block that was passed in.
+    fn skip_drop_block(mir: &mir::Body<'_>, bb: mir::BasicBlock) -> mir::BasicBlock {
+        if let mir::TerminatorKind::Drop { target, .. } = mir.basic_blocks[bb].terminator().kind {
+            target
+        } else {
+            bb
+        }
+    }
+
+    let Some(mir) = enclosing_mir(cx.tcx, lhs.hir_id) else {
+        return false;
+    };
+    let PossibleBorrowerMap { map: borrow_map, .. } = PossibleBorrowerMap::new(cx, mir);
+
+    // The operation `dest = src.to_owned()` in MIR is split up across 3 blocks *if* the type has `Drop`
+    // code. For types that don't, the second basic block is simply skipped.
+    // For the doc example above that would be roughly:
+    //
+    // bb0:
+    //  s2 = &s
+    //  s_temp = ToOwned::to_owned(move s2) -> bb1
+    //
+    // bb1:
+    //  drop(s) -> bb2  // drop the old string
+    //
+    // bb2:
+    //  s = s_temp
+    for bb in mir.basic_blocks.iter() {
+        let terminator = bb.terminator();
+
+        // Look for the to_owned/clone call.
+        if terminator.source_info.span != call_span {
+            continue;
+        }
+
+        if let mir::TerminatorKind::Call { ref args, target: Some(assign_bb), .. } = terminator.kind
+            && let [source] = &**args
+            && let mir::Operand::Move(source) = &source.node
+            && let assign_bb = skip_drop_block(mir, assign_bb)
+            // Skip any storage statements as they are just noise
+            && let Some(assignment) = mir.basic_blocks[assign_bb].statements
+                .iter()
+                .find(|stmt| {
+                    !matches!(stmt.kind, mir::StatementKind::StorageDead(_) | mir::StatementKind::StorageLive(_))
+                })
+            && let mir::StatementKind::Assign(box (borrowed, _)) = &assignment.kind
+            && let Some(borrowers) = borrow_map.get(&borrowed.local)
+            && borrowers.contains(source.local)
+        {
+            return true;
+        }
+
+        return false;
+    }
+    false
+}
+
 fn suggest<'tcx>(
     cx: &LateContext<'tcx>,
     ctxt: SyntaxContext,
@@ -255,6 +330,7 @@ enum TargetTrait {
 
 #[derive(Debug)]
 struct CallCandidate<'tcx> {
+    span: Span,
     target: TargetTrait,
     kind: CallKind<'tcx>,
     // DefId of the called method from an impl block that implements the target trait
diff --git a/clippy_lints/src/attrs/allow_attributes.rs b/clippy_lints/src/attrs/allow_attributes.rs
index c5b6980b0b9..df9994086cd 100644
--- a/clippy_lints/src/attrs/allow_attributes.rs
+++ b/clippy_lints/src/attrs/allow_attributes.rs
@@ -1,17 +1,17 @@
+use super::ALLOW_ATTRIBUTES;
 use clippy_utils::diagnostics::span_lint_and_sugg;
 use clippy_utils::is_from_proc_macro;
 use rustc_ast::{AttrStyle, Attribute};
 use rustc_errors::Applicability;
 use rustc_lint::{LateContext, LintContext};
 use rustc_middle::lint::in_external_macro;
-use super::ALLOW_ATTRIBUTES;
 
 // Separate each crate's features.
 pub fn check<'cx>(cx: &LateContext<'cx>, attr: &'cx Attribute) {
     if !in_external_macro(cx.sess(), attr.span)
         && let AttrStyle::Outer = attr.style
         && let Some(ident) = attr.ident()
-        && !is_from_proc_macro(cx, &attr)
+        && !is_from_proc_macro(cx, attr)
     {
         span_lint_and_sugg(
             cx,
diff --git a/clippy_lints/src/attrs/allow_attributes_without_reason.rs b/clippy_lints/src/attrs/allow_attributes_without_reason.rs
index 8bf985a36c7..4b42616a636 100644
--- a/clippy_lints/src/attrs/allow_attributes_without_reason.rs
+++ b/clippy_lints/src/attrs/allow_attributes_without_reason.rs
@@ -17,7 +17,7 @@ pub(super) fn check<'cx>(cx: &LateContext<'cx>, name: Symbol, items: &[NestedMet
     }
 
     // Check if the attribute is in an external macro and therefore out of the developer's control
-    if in_external_macro(cx.sess(), attr.span) || is_from_proc_macro(cx, &attr) {
+    if in_external_macro(cx.sess(), attr.span) || is_from_proc_macro(cx, attr) {
         return;
     }
 
diff --git a/clippy_lints/src/attrs/mod.rs b/clippy_lints/src/attrs/mod.rs
index da19f17998a..8ec60314cc9 100644
--- a/clippy_lints/src/attrs/mod.rs
+++ b/clippy_lints/src/attrs/mod.rs
@@ -1,7 +1,7 @@
 //! checks for attributes
 
-mod allow_attributes_without_reason;
 mod allow_attributes;
+mod allow_attributes_without_reason;
 mod blanket_clippy_restriction_lints;
 mod deprecated_cfg_attr;
 mod deprecated_semver;
@@ -505,6 +505,7 @@ pub struct Attributes {
 }
 
 impl_lint_pass!(Attributes => [
+    ALLOW_ATTRIBUTES,
     ALLOW_ATTRIBUTES_WITHOUT_REASON,
     INLINE_ALWAYS,
     DEPRECATED_SEMVER,
@@ -534,15 +535,12 @@ impl<'tcx> LateLintPass<'tcx> for Attributes {
                 if is_lint_level(ident.name, attr.id) {
                     blanket_clippy_restriction_lints::check(cx, ident.name, items);
                 }
-                if matches!(ident.name, sym::allow) {
-                    if self.msrv.meets(msrvs::LINT_REASONS_STABILIZATION) {
-                        allow_attributes::check(cx, attr);
-                    }
+                if matches!(ident.name, sym::allow) && self.msrv.meets(msrvs::LINT_REASONS_STABILIZATION) {
+                    allow_attributes::check(cx, attr);
                 }
-                if matches!(ident.name, sym::allow | sym::expect) {
-                    if self.msrv.meets(msrvs::LINT_REASONS_STABILIZATION) {
-                        allow_attributes_without_reason::check(cx, ident.name, items, attr);
-                    }
+                if matches!(ident.name, sym::allow | sym::expect) && self.msrv.meets(msrvs::LINT_REASONS_STABILIZATION)
+                {
+                    allow_attributes_without_reason::check(cx, ident.name, items, attr);
                 }
                 if items.is_empty() || !attr.has_name(sym::deprecated) {
                     return;
diff --git a/clippy_lints/src/casts/ref_as_ptr.rs b/clippy_lints/src/casts/ref_as_ptr.rs
index f42bafce4dd..5f48b8bd206 100644
--- a/clippy_lints/src/casts/ref_as_ptr.rs
+++ b/clippy_lints/src/casts/ref_as_ptr.rs
@@ -22,9 +22,9 @@ pub(super) fn check<'tcx>(
 
     if matches!(cast_from.kind(), ty::Ref(..))
         && let ty::RawPtr(_, to_mutbl) = cast_to.kind()
-        && let Some(use_cx) = expr_use_ctxt(cx, expr)
+        && let use_cx = expr_use_ctxt(cx, expr)
         // TODO: only block the lint if `cast_expr` is a temporary
-        && !matches!(use_cx.node, ExprUseNode::LetStmt(_) | ExprUseNode::ConstStatic(_))
+        && !matches!(use_cx.use_node(cx), ExprUseNode::LetStmt(_) | ExprUseNode::ConstStatic(_))
     {
         let core_or_std = if is_no_std_crate(cx) { "core" } else { "std" };
         let fn_name = match to_mutbl {
diff --git a/clippy_lints/src/cognitive_complexity.rs b/clippy_lints/src/cognitive_complexity.rs
index e41abf42234..60815f4f2af 100644
--- a/clippy_lints/src/cognitive_complexity.rs
+++ b/clippy_lints/src/cognitive_complexity.rs
@@ -1,7 +1,7 @@
 //! calculate cognitive complexity and warn about overly complex functions
 
 use clippy_utils::diagnostics::span_lint_and_help;
-use clippy_utils::source::snippet_opt;
+use clippy_utils::source::{IntoSpan, SpanRangeExt};
 use clippy_utils::ty::is_type_diagnostic_item;
 use clippy_utils::visitors::for_each_expr_without_closures;
 use clippy_utils::{get_async_fn_body, is_async_fn, LimitStack};
@@ -12,7 +12,7 @@ use rustc_hir::{Body, Expr, ExprKind, FnDecl};
 use rustc_lint::{LateContext, LateLintPass, LintContext};
 use rustc_session::impl_lint_pass;
 use rustc_span::def_id::LocalDefId;
-use rustc_span::{sym, BytePos, Span};
+use rustc_span::{sym, Span};
 
 declare_clippy_lint! {
     /// ### What it does
@@ -50,7 +50,6 @@ impl CognitiveComplexity {
 impl_lint_pass!(CognitiveComplexity => [COGNITIVE_COMPLEXITY]);
 
 impl CognitiveComplexity {
-    #[expect(clippy::cast_possible_truncation)]
     fn check<'tcx>(
         &mut self,
         cx: &LateContext<'tcx>,
@@ -100,17 +99,12 @@ impl CognitiveComplexity {
                 FnKind::ItemFn(ident, _, _) | FnKind::Method(ident, _) => ident.span,
                 FnKind::Closure => {
                     let header_span = body_span.with_hi(decl.output.span().lo());
-                    let pos = snippet_opt(cx, header_span).and_then(|snip| {
-                        let low_offset = snip.find('|')?;
-                        let high_offset = 1 + snip.get(low_offset + 1..)?.find('|')?;
-                        let low = header_span.lo() + BytePos(low_offset as u32);
-                        let high = low + BytePos(high_offset as u32 + 1);
-
-                        Some((low, high))
-                    });
-
-                    if let Some((low, high)) = pos {
-                        Span::new(low, high, header_span.ctxt(), header_span.parent())
+                    #[expect(clippy::range_plus_one)]
+                    if let Some(range) = header_span.map_range(cx, |src, range| {
+                        let mut idxs = src.get(range.clone())?.match_indices('|');
+                        Some(range.start + idxs.next()?.0..range.start + idxs.next()?.0 + 1)
+                    }) {
+                        range.with_ctxt(header_span.ctxt())
                     } else {
                         return;
                     }
diff --git a/clippy_lints/src/copies.rs b/clippy_lints/src/copies.rs
index 480df675d75..d896452be92 100644
--- a/clippy_lints/src/copies.rs
+++ b/clippy_lints/src/copies.rs
@@ -1,5 +1,5 @@
 use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_then};
-use clippy_utils::source::{first_line_of_span, indent_of, reindent_multiline, snippet, snippet_opt};
+use clippy_utils::source::{first_line_of_span, indent_of, reindent_multiline, snippet, IntoSpan, SpanRangeExt};
 use clippy_utils::ty::{needs_ordered_drop, InteriorMut};
 use clippy_utils::visitors::for_each_expr_without_closures;
 use clippy_utils::{
@@ -14,7 +14,7 @@ use rustc_lint::{LateContext, LateLintPass};
 use rustc_session::impl_lint_pass;
 use rustc_span::hygiene::walk_chain;
 use rustc_span::source_map::SourceMap;
-use rustc_span::{BytePos, Span, Symbol};
+use rustc_span::{Span, Symbol};
 use std::borrow::Cow;
 
 declare_clippy_lint! {
@@ -266,12 +266,12 @@ fn lint_branches_sharing_code<'tcx>(
 
         let span = span.with_hi(last_block.span.hi());
         // Improve formatting if the inner block has indention (i.e. normal Rust formatting)
-        let test_span = Span::new(span.lo() - BytePos(4), span.lo(), span.ctxt(), span.parent());
-        let span = if snippet_opt(cx, test_span).map_or(false, |snip| snip == "    ") {
-            span.with_lo(test_span.lo())
-        } else {
-            span
-        };
+        let span = span
+            .map_range(cx, |src, range| {
+                (range.start > 4 && src.get(range.start - 4..range.start)? == "    ")
+                    .then_some(range.start - 4..range.end)
+            })
+            .map_or(span, |range| range.with_ctxt(span.ctxt()));
         (span, suggestion.to_string())
     });
 
diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs
index 76a0e450e59..638de5e818c 100644
--- a/clippy_lints/src/declared_lints.rs
+++ b/clippy_lints/src/declared_lints.rs
@@ -178,6 +178,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
     crate::explicit_write::EXPLICIT_WRITE_INFO,
     crate::extra_unused_type_parameters::EXTRA_UNUSED_TYPE_PARAMETERS_INFO,
     crate::fallible_impl_from::FALLIBLE_IMPL_FROM_INFO,
+    crate::field_scoped_visibility_modifiers::FIELD_SCOPED_VISIBILITY_MODIFIERS_INFO,
     crate::float_literal::EXCESSIVE_PRECISION_INFO,
     crate::float_literal::LOSSY_FLOAT_LITERAL_INFO,
     crate::floating_point_arithmetic::IMPRECISE_FLOPS_INFO,
@@ -402,6 +403,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
     crate::methods::MANUAL_C_STR_LITERALS_INFO,
     crate::methods::MANUAL_FILTER_MAP_INFO,
     crate::methods::MANUAL_FIND_MAP_INFO,
+    crate::methods::MANUAL_INSPECT_INFO,
     crate::methods::MANUAL_IS_VARIANT_AND_INFO,
     crate::methods::MANUAL_NEXT_BACK_INFO,
     crate::methods::MANUAL_OK_OR_INFO,
@@ -469,6 +471,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
     crate::methods::UNNECESSARY_JOIN_INFO,
     crate::methods::UNNECESSARY_LAZY_EVALUATIONS_INFO,
     crate::methods::UNNECESSARY_LITERAL_UNWRAP_INFO,
+    crate::methods::UNNECESSARY_MIN_OR_MAX_INFO,
     crate::methods::UNNECESSARY_RESULT_MAP_OR_ELSE_INFO,
     crate::methods::UNNECESSARY_SORT_BY_INFO,
     crate::methods::UNNECESSARY_TO_OWNED_INFO,
diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs
index f451758c335..a115f8d0631 100644
--- a/clippy_lints/src/dereference.rs
+++ b/clippy_lints/src/dereference.rs
@@ -6,7 +6,7 @@ use clippy_utils::{
     expr_use_ctxt, get_parent_expr, is_block_like, is_lint_allowed, path_to_local, DefinedTy, ExprUseNode,
 };
 use core::mem;
-use rustc_ast::util::parser::{PREC_UNAMBIGUOUS, PREC_PREFIX};
+use rustc_ast::util::parser::{PREC_PREFIX, PREC_UNAMBIGUOUS};
 use rustc_data_structures::fx::FxIndexMap;
 use rustc_errors::Applicability;
 use rustc_hir::intravisit::{walk_ty, Visitor};
@@ -260,18 +260,13 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
             (None, kind) => {
                 let expr_ty = typeck.expr_ty(expr);
                 let use_cx = expr_use_ctxt(cx, expr);
-                let adjusted_ty = match &use_cx {
-                    Some(use_cx) => match use_cx.adjustments {
-                        [.., a] => a.target,
-                        _ => expr_ty,
-                    },
-                    _ => typeck.expr_ty_adjusted(expr),
-                };
+                let adjusted_ty = use_cx.adjustments.last().map_or(expr_ty, |a| a.target);
 
-                match (use_cx, kind) {
-                    (Some(use_cx), RefOp::Deref) => {
+                match kind {
+                    RefOp::Deref if use_cx.same_ctxt => {
+                        let use_node = use_cx.use_node(cx);
                         let sub_ty = typeck.expr_ty(sub_expr);
-                        if let ExprUseNode::FieldAccess(name) = use_cx.node
+                        if let ExprUseNode::FieldAccess(name) = use_node
                             && !use_cx.moved_before_use
                             && !ty_contains_field(sub_ty, name.name)
                         {
@@ -288,9 +283,9 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
                         } else if sub_ty.is_ref()
                             // Linting method receivers would require verifying that name lookup
                             // would resolve the same way. This is complicated by trait methods.
-                            && !use_cx.node.is_recv()
-                            && let Some(ty) = use_cx.node.defined_ty(cx)
-                            && TyCoercionStability::for_defined_ty(cx, ty, use_cx.node.is_return()).is_deref_stable()
+                            && !use_node.is_recv()
+                            && let Some(ty) = use_node.defined_ty(cx)
+                            && TyCoercionStability::for_defined_ty(cx, ty, use_node.is_return()).is_deref_stable()
                         {
                             self.state = Some((
                                 State::ExplicitDeref { mutability: None },
@@ -301,7 +296,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
                             ));
                         }
                     },
-                    (_, RefOp::Method { mutbl, is_ufcs })
+                    RefOp::Method { mutbl, is_ufcs }
                         if !is_lint_allowed(cx, EXPLICIT_DEREF_METHODS, expr.hir_id)
                             // Allow explicit deref in method chains. e.g. `foo.deref().bar()`
                             && (is_ufcs || !in_postfix_position(cx, expr)) =>
@@ -319,7 +314,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
                             },
                         ));
                     },
-                    (Some(use_cx), RefOp::AddrOf(mutability)) => {
+                    RefOp::AddrOf(mutability) if use_cx.same_ctxt => {
                         // Find the number of times the borrow is auto-derefed.
                         let mut iter = use_cx.adjustments.iter();
                         let mut deref_count = 0usize;
@@ -338,10 +333,11 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
                             };
                         };
 
-                        let stability = use_cx.node.defined_ty(cx).map_or(TyCoercionStability::None, |ty| {
-                            TyCoercionStability::for_defined_ty(cx, ty, use_cx.node.is_return())
+                        let use_node = use_cx.use_node(cx);
+                        let stability = use_node.defined_ty(cx).map_or(TyCoercionStability::None, |ty| {
+                            TyCoercionStability::for_defined_ty(cx, ty, use_node.is_return())
                         });
-                        let can_auto_borrow = match use_cx.node {
+                        let can_auto_borrow = match use_node {
                             ExprUseNode::FieldAccess(_)
                                 if !use_cx.moved_before_use && matches!(sub_expr.kind, ExprKind::Field(..)) =>
                             {
@@ -353,7 +349,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
                                 // deref through `ManuallyDrop<_>` will not compile.
                                 !adjust_derefs_manually_drop(use_cx.adjustments, expr_ty)
                             },
-                            ExprUseNode::Callee | ExprUseNode::FieldAccess(_) => true,
+                            ExprUseNode::Callee | ExprUseNode::FieldAccess(_) if !use_cx.moved_before_use => true,
                             ExprUseNode::MethodArg(hir_id, _, 0) if !use_cx.moved_before_use => {
                                 // Check for calls to trait methods where the trait is implemented
                                 // on a reference.
@@ -363,9 +359,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
                                 //   priority.
                                 if let Some(fn_id) = typeck.type_dependent_def_id(hir_id)
                                     && let Some(trait_id) = cx.tcx.trait_of_item(fn_id)
-                                    && let arg_ty = cx
-                                        .tcx
-                                        .erase_regions(use_cx.adjustments.last().map_or(expr_ty, |a| a.target))
+                                    && let arg_ty = cx.tcx.erase_regions(adjusted_ty)
                                     && let ty::Ref(_, sub_ty, _) = *arg_ty.kind()
                                     && let args =
                                         typeck.node_args_opt(hir_id).map(|args| &args[1..]).unwrap_or_default()
@@ -443,7 +437,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
                                     count: deref_count - required_refs,
                                     msg,
                                     stability,
-                                    for_field_access: if let ExprUseNode::FieldAccess(name) = use_cx.node
+                                    for_field_access: if let ExprUseNode::FieldAccess(name) = use_node
                                         && !use_cx.moved_before_use
                                     {
                                         Some(name.name)
@@ -453,7 +447,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
                                 }),
                                 StateData {
                                     first_expr: expr,
-                                    adjusted_ty: use_cx.adjustments.last().map_or(expr_ty, |a| a.target),
+                                    adjusted_ty,
                                 },
                             ));
                         } else if stability.is_deref_stable()
@@ -465,12 +459,12 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
                                 State::Borrow { mutability },
                                 StateData {
                                     first_expr: expr,
-                                    adjusted_ty: use_cx.adjustments.last().map_or(expr_ty, |a| a.target),
+                                    adjusted_ty,
                                 },
                             ));
                         }
                     },
-                    (None, _) | (_, RefOp::Method { .. }) => (),
+                    _ => {},
                 }
             },
             (
diff --git a/clippy_lints/src/field_scoped_visibility_modifiers.rs b/clippy_lints/src/field_scoped_visibility_modifiers.rs
new file mode 100644
index 00000000000..bb74e345703
--- /dev/null
+++ b/clippy_lints/src/field_scoped_visibility_modifiers.rs
@@ -0,0 +1,75 @@
+use clippy_utils::diagnostics::span_lint_and_help;
+use rustc_ast::ast::{Item, ItemKind, VisibilityKind};
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_session::declare_lint_pass;
+
+declare_clippy_lint! {
+    /// ### What it does
+    /// Checks for usage of scoped visibility modifiers, like `pub(crate)`, on fields. These
+    /// make a field visible within a scope between public and private.
+    ///
+    /// ### Why restrict this?
+    /// Scoped visibility modifiers cause a field to be accessible within some scope between
+    /// public and private, potentially within an entire crate. This allows for fields to be
+    /// non-private while upholding internal invariants, but can be a code smell. Scoped visibility
+    /// requires checking a greater area, potentially an entire crate, to verify that an invariant
+    /// is upheld, and global analysis requires a lot of effort.
+    ///
+    /// ### Example
+    /// ```no_run
+    /// pub mod public_module {
+    ///     struct MyStruct {
+    ///         pub(crate) first_field: bool,
+    ///         pub(super) second_field: bool
+    ///     }
+    /// }
+    /// ```
+    /// Use instead:
+    /// ```no_run
+    /// pub mod public_module {
+    ///     struct MyStruct {
+    ///         first_field: bool,
+    ///         second_field: bool
+    ///     }
+    ///     impl MyStruct {
+    ///         pub(crate) fn get_first_field(&self) -> bool {
+    ///             self.first_field
+    ///         }
+    ///         pub(super) fn get_second_field(&self) -> bool {
+    ///             self.second_field
+    ///         }
+    ///     }
+    /// }
+    /// ```
+    #[clippy::version = "1.78.0"]
+    pub FIELD_SCOPED_VISIBILITY_MODIFIERS,
+    restriction,
+    "checks for usage of a scoped visibility modifier, like `pub(crate)`, on fields"
+}
+
+declare_lint_pass!(FieldScopedVisibilityModifiers => [FIELD_SCOPED_VISIBILITY_MODIFIERS]);
+
+impl EarlyLintPass for FieldScopedVisibilityModifiers {
+    fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
+        let ItemKind::Struct(ref st, _) = item.kind else {
+            return;
+        };
+        for field in st.fields() {
+            let VisibilityKind::Restricted { path, .. } = &field.vis.kind else {
+                continue;
+            };
+            if !path.segments.is_empty() && path.segments[0].ident.name == rustc_span::symbol::kw::SelfLower {
+                // pub(self) is equivalent to not using pub at all, so we ignore it
+                continue;
+            }
+            span_lint_and_help(
+                cx,
+                FIELD_SCOPED_VISIBILITY_MODIFIERS,
+                field.vis.span,
+                "scoped visibility modifier on a field",
+                None,
+                "consider making the field private and adding a scoped visibility method for it",
+            );
+        }
+    }
+}
diff --git a/clippy_lints/src/from_over_into.rs b/clippy_lints/src/from_over_into.rs
index 1933a00891b..93527bcdf5c 100644
--- a/clippy_lints/src/from_over_into.rs
+++ b/clippy_lints/src/from_over_into.rs
@@ -181,6 +181,9 @@ fn convert_to_from(
     let from = snippet_opt(cx, self_ty.span)?;
     let into = snippet_opt(cx, target_ty.span)?;
 
+    let return_type = matches!(sig.decl.output, FnRetTy::Return(_))
+        .then_some(String::from("Self"))
+        .unwrap_or_default();
     let mut suggestions = vec![
         // impl Into<T> for U  ->  impl From<T> for U
         //      ~~~~                    ~~~~
@@ -197,13 +200,10 @@ fn convert_to_from(
         // fn into([mut] self) -> T  ->  fn into([mut] v: T) -> T
         //               ~~~~                          ~~~~
         (self_ident.span, format!("val: {from}")),
-    ];
-
-    if let FnRetTy::Return(_) = sig.decl.output {
         // fn into(self) -> T  ->  fn into(self) -> Self
         //                  ~                       ~~~~
-        suggestions.push((sig.decl.output.span(), String::from("Self")));
-    }
+        (sig.decl.output.span(), return_type),
+    ];
 
     let mut finder = SelfFinder {
         cx,
diff --git a/clippy_lints/src/implicit_hasher.rs b/clippy_lints/src/implicit_hasher.rs
index ca830af3b2f..344a04e6e7e 100644
--- a/clippy_lints/src/implicit_hasher.rs
+++ b/clippy_lints/src/implicit_hasher.rs
@@ -14,7 +14,7 @@ use rustc_span::symbol::sym;
 use rustc_span::Span;
 
 use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
-use clippy_utils::source::{snippet, snippet_opt};
+use clippy_utils::source::{snippet, IntoSpan, SpanRangeExt};
 use clippy_utils::ty::is_type_diagnostic_item;
 
 declare_clippy_lint! {
@@ -59,10 +59,8 @@ declare_clippy_lint! {
 declare_lint_pass!(ImplicitHasher => [IMPLICIT_HASHER]);
 
 impl<'tcx> LateLintPass<'tcx> for ImplicitHasher {
-    #[expect(clippy::cast_possible_truncation, clippy::too_many_lines)]
+    #[expect(clippy::too_many_lines)]
     fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
-        use rustc_span::BytePos;
-
         fn suggestion(
             cx: &LateContext<'_>,
             diag: &mut Diag<'_, ()>,
@@ -123,10 +121,11 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitHasher {
                     }
 
                     let generics_suggestion_span = impl_.generics.span.substitute_dummy({
-                        let pos = snippet_opt(cx, item.span.until(target.span()))
-                            .and_then(|snip| Some(item.span.lo() + BytePos(snip.find("impl")? as u32 + 4)));
-                        if let Some(pos) = pos {
-                            Span::new(pos, pos, item.span.ctxt(), item.span.parent())
+                        let range = (item.span.lo()..target.span().lo()).map_range(cx, |src, range| {
+                            Some(src.get(range.clone())?.find("impl")? + 4..range.end)
+                        });
+                        if let Some(range) = range {
+                            range.with_ctxt(item.span.ctxt())
                         } else {
                             return;
                         }
@@ -163,21 +162,16 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitHasher {
                             continue;
                         }
                         let generics_suggestion_span = generics.span.substitute_dummy({
-                            let pos = snippet_opt(
-                                cx,
-                                Span::new(
-                                    item.span.lo(),
-                                    body.params[0].pat.span.lo(),
-                                    item.span.ctxt(),
-                                    item.span.parent(),
-                                ),
-                            )
-                            .and_then(|snip| {
-                                let i = snip.find("fn")?;
-                                Some(item.span.lo() + BytePos((i + snip[i..].find('(')?) as u32))
-                            })
-                            .expect("failed to create span for type parameters");
-                            Span::new(pos, pos, item.span.ctxt(), item.span.parent())
+                            let range = (item.span.lo()..body.params[0].pat.span.lo()).map_range(cx, |src, range| {
+                                let (pre, post) = src.get(range.clone())?.split_once("fn")?;
+                                let pos = post.find('(')? + pre.len() + 2;
+                                Some(pos..pos)
+                            });
+                            if let Some(range) = range {
+                                range.with_ctxt(item.span.ctxt())
+                            } else {
+                                return;
+                            }
                         });
 
                         let mut ctr_vis = ImplicitHasherConstructorVisitor::new(cx, target);
diff --git a/clippy_lints/src/implicit_return.rs b/clippy_lints/src/implicit_return.rs
index 2f543781c44..a102b434cfa 100644
--- a/clippy_lints/src/implicit_return.rs
+++ b/clippy_lints/src/implicit_return.rs
@@ -1,7 +1,7 @@
 use clippy_utils::diagnostics::span_lint_hir_and_then;
 use clippy_utils::source::{snippet_with_applicability, snippet_with_context, walk_span_to_context};
 use clippy_utils::visitors::for_each_expr_without_closures;
-use clippy_utils::{get_async_fn_body, is_async_fn};
+use clippy_utils::{get_async_fn_body, is_async_fn, is_from_proc_macro};
 use core::ops::ControlFlow;
 use rustc_errors::Applicability;
 use rustc_hir::intravisit::FnKind;
@@ -245,6 +245,10 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitReturn {
         } else {
             body.value
         };
+
+        if is_from_proc_macro(cx, expr) {
+            return;
+        }
         lint_implicit_returns(cx, expr, expr.span.ctxt(), None);
     }
 }
diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs
index 63461c14d77..ed0ff51fbf6 100644
--- a/clippy_lints/src/lib.rs
+++ b/clippy_lints/src/lib.rs
@@ -6,7 +6,6 @@
 #![feature(if_let_guard)]
 #![feature(iter_intersperse)]
 #![feature(let_chains)]
-#![cfg_attr(bootstrap, feature(lint_reasons))]
 #![feature(never_type)]
 #![feature(rustc_private)]
 #![feature(stmt_expr_attributes)]
@@ -137,6 +136,7 @@ mod exit;
 mod explicit_write;
 mod extra_unused_type_parameters;
 mod fallible_impl_from;
+mod field_scoped_visibility_modifiers;
 mod float_literal;
 mod floating_point_arithmetic;
 mod format;
@@ -1168,7 +1168,8 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
             ..Default::default()
         })
     });
-    store.register_late_pass(|_| Box::new(string_patterns::StringPatterns));
+    store.register_late_pass(move |_| Box::new(string_patterns::StringPatterns::new(msrv())));
+    store.register_early_pass(|| Box::new(field_scoped_visibility_modifiers::FieldScopedVisibilityModifiers));
     // add lints here, do not remove this comment, it's used in `new_lint`
 }
 
diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs
index 08682942153..64ea591993d 100644
--- a/clippy_lints/src/loops/mod.rs
+++ b/clippy_lints/src/loops/mod.rs
@@ -356,10 +356,10 @@ declare_clippy_lint! {
 
 declare_clippy_lint! {
     /// ### What it does
-    /// Checks for loops which have a range bound that is a mutable variable
+    /// Checks for loops with a range bound that is a mutable variable.
     ///
     /// ### Why is this bad?
-    /// One might think that modifying the mutable variable changes the loop bounds
+    /// One might think that modifying the mutable variable changes the loop bounds. It doesn't.
     ///
     /// ### Known problems
     /// False positive when mutation is followed by a `break`, but the `break` is not immediately
@@ -381,7 +381,7 @@ declare_clippy_lint! {
     /// let mut foo = 42;
     /// for i in 0..foo {
     ///     foo -= 1;
-    ///     println!("{}", i); // prints numbers from 0 to 42, not 0 to 21
+    ///     println!("{i}"); // prints numbers from 0 to 41, not 0 to 21
     /// }
     /// ```
     #[clippy::version = "pre 1.29.0"]
diff --git a/clippy_lints/src/manual_unwrap_or_default.rs b/clippy_lints/src/manual_unwrap_or_default.rs
index 17399fb2cc2..58b2ebebbf0 100644
--- a/clippy_lints/src/manual_unwrap_or_default.rs
+++ b/clippy_lints/src/manual_unwrap_or_default.rs
@@ -53,6 +53,7 @@ declare_lint_pass!(ManualUnwrapOrDefault => [MANUAL_UNWRAP_OR_DEFAULT]);
 
 fn get_some<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>) -> Option<HirId> {
     if let PatKind::TupleStruct(QPath::Resolved(_, path), &[pat], _) = pat.kind
+        && let PatKind::Binding(_, pat_id, _, _) = pat.kind
         && let Some(def_id) = path.res.opt_def_id()
         // Since it comes from a pattern binding, we need to get the parent to actually match
         // against it.
@@ -60,13 +61,7 @@ fn get_some<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>) -> Option<HirId> {
         && (cx.tcx.lang_items().get(LangItem::OptionSome) == Some(def_id)
         || cx.tcx.lang_items().get(LangItem::ResultOk) == Some(def_id))
     {
-        let mut bindings = Vec::new();
-        pat.each_binding(|_, id, _, _| bindings.push(id));
-        if let &[id] = bindings.as_slice() {
-            Some(id)
-        } else {
-            None
-        }
+        Some(pat_id)
     } else {
         None
     }
diff --git a/clippy_lints/src/matches/manual_unwrap_or.rs b/clippy_lints/src/matches/manual_unwrap_or.rs
index 9edd6c95404..0940fc3219b 100644
--- a/clippy_lints/src/matches/manual_unwrap_or.rs
+++ b/clippy_lints/src/matches/manual_unwrap_or.rs
@@ -3,46 +3,78 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
 use clippy_utils::source::{indent_of, reindent_multiline, snippet_opt};
 use clippy_utils::ty::is_type_diagnostic_item;
 use clippy_utils::usage::contains_return_break_continue_macro;
-use clippy_utils::{is_res_lang_ctor, path_to_local_id, sugg};
+use clippy_utils::{is_res_lang_ctor, path_to_local_id, peel_blocks, sugg};
 use rustc_errors::Applicability;
 use rustc_hir::def::{DefKind, Res};
 use rustc_hir::LangItem::{OptionNone, ResultErr};
-use rustc_hir::{Arm, Expr, PatKind};
+use rustc_hir::{Arm, Expr, Pat, PatKind};
 use rustc_lint::LateContext;
+use rustc_middle::ty::Ty;
 use rustc_span::sym;
 
 use super::MANUAL_UNWRAP_OR;
 
-pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, scrutinee: &'tcx Expr<'_>, arms: &'tcx [Arm<'_>]) {
+pub(super) fn check_match<'tcx>(
+    cx: &LateContext<'tcx>,
+    expr: &'tcx Expr<'tcx>,
+    scrutinee: &'tcx Expr<'_>,
+    arms: &'tcx [Arm<'_>],
+) {
     let ty = cx.typeck_results().expr_ty(scrutinee);
-    if let Some(ty_name) = if is_type_diagnostic_item(cx, ty, sym::Option) {
+    if let Some((or_arm, unwrap_arm)) = applicable_or_arm(cx, arms) {
+        check_and_lint(cx, expr, unwrap_arm.pat, scrutinee, unwrap_arm.body, or_arm.body, ty);
+    }
+}
+
+pub(super) fn check_if_let<'tcx>(
+    cx: &LateContext<'tcx>,
+    expr: &'tcx Expr<'_>,
+    let_pat: &'tcx Pat<'_>,
+    let_expr: &'tcx Expr<'_>,
+    then_expr: &'tcx Expr<'_>,
+    else_expr: &'tcx Expr<'_>,
+) {
+    let ty = cx.typeck_results().expr_ty(let_expr);
+    check_and_lint(cx, expr, let_pat, let_expr, then_expr, peel_blocks(else_expr), ty);
+}
+
+fn check_and_lint<'tcx>(
+    cx: &LateContext<'tcx>,
+    expr: &'tcx Expr<'_>,
+    let_pat: &'tcx Pat<'_>,
+    let_expr: &'tcx Expr<'_>,
+    then_expr: &'tcx Expr<'_>,
+    else_expr: &'tcx Expr<'_>,
+    ty: Ty<'tcx>,
+) {
+    if let PatKind::TupleStruct(ref qpath, [unwrap_pat], _) = let_pat.kind
+        && let Res::Def(DefKind::Ctor(..), ctor_id) = cx.qpath_res(qpath, let_pat.hir_id)
+        && let Some(variant_id) = cx.tcx.opt_parent(ctor_id)
+        && (cx.tcx.lang_items().option_some_variant() == Some(variant_id)
+            || cx.tcx.lang_items().result_ok_variant() == Some(variant_id))
+        && let PatKind::Binding(_, binding_hir_id, ..) = unwrap_pat.kind
+        && path_to_local_id(peel_blocks(then_expr), binding_hir_id)
+        && cx.typeck_results().expr_adjustments(then_expr).is_empty()
+        && let Some(ty_name) = find_type_name(cx, ty)
+        && let Some(or_body_snippet) = snippet_opt(cx, else_expr.span)
+        && let Some(indent) = indent_of(cx, expr.span)
+        && constant_simple(cx, cx.typeck_results(), else_expr).is_some()
+    {
+        lint(cx, expr, let_expr, ty_name, or_body_snippet, indent);
+    }
+}
+
+fn find_type_name<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<&'static str> {
+    if is_type_diagnostic_item(cx, ty, sym::Option) {
         Some("Option")
     } else if is_type_diagnostic_item(cx, ty, sym::Result) {
         Some("Result")
     } else {
         None
-    } && let Some(or_arm) = applicable_or_arm(cx, arms)
-        && let Some(or_body_snippet) = snippet_opt(cx, or_arm.body.span)
-        && let Some(indent) = indent_of(cx, expr.span)
-        && constant_simple(cx, cx.typeck_results(), or_arm.body).is_some()
-    {
-        let reindented_or_body = reindent_multiline(or_body_snippet.into(), true, Some(indent));
-
-        let mut app = Applicability::MachineApplicable;
-        let suggestion = sugg::Sugg::hir_with_context(cx, scrutinee, expr.span.ctxt(), "..", &mut app).maybe_par();
-        span_lint_and_sugg(
-            cx,
-            MANUAL_UNWRAP_OR,
-            expr.span,
-            format!("this pattern reimplements `{ty_name}::unwrap_or`"),
-            "replace with",
-            format!("{suggestion}.unwrap_or({reindented_or_body})",),
-            app,
-        );
     }
 }
 
-fn applicable_or_arm<'a>(cx: &LateContext<'_>, arms: &'a [Arm<'a>]) -> Option<&'a Arm<'a>> {
+fn applicable_or_arm<'a>(cx: &LateContext<'_>, arms: &'a [Arm<'a>]) -> Option<(&'a Arm<'a>, &'a Arm<'a>)> {
     if arms.len() == 2
         && arms.iter().all(|arm| arm.guard.is_none())
         && let Some((idx, or_arm)) = arms.iter().enumerate().find(|(_, arm)| match arm.pat.kind {
@@ -54,18 +86,33 @@ fn applicable_or_arm<'a>(cx: &LateContext<'_>, arms: &'a [Arm<'a>]) -> Option<&'
             _ => false,
         })
         && let unwrap_arm = &arms[1 - idx]
-        && let PatKind::TupleStruct(ref qpath, [unwrap_pat], _) = unwrap_arm.pat.kind
-        && let Res::Def(DefKind::Ctor(..), ctor_id) = cx.qpath_res(qpath, unwrap_arm.pat.hir_id)
-        && let Some(variant_id) = cx.tcx.opt_parent(ctor_id)
-        && (cx.tcx.lang_items().option_some_variant() == Some(variant_id)
-            || cx.tcx.lang_items().result_ok_variant() == Some(variant_id))
-        && let PatKind::Binding(_, binding_hir_id, ..) = unwrap_pat.kind
-        && path_to_local_id(unwrap_arm.body, binding_hir_id)
-        && cx.typeck_results().expr_adjustments(unwrap_arm.body).is_empty()
         && !contains_return_break_continue_macro(or_arm.body)
     {
-        Some(or_arm)
+        Some((or_arm, unwrap_arm))
     } else {
         None
     }
 }
+
+fn lint<'tcx>(
+    cx: &LateContext<'tcx>,
+    expr: &Expr<'tcx>,
+    scrutinee: &'tcx Expr<'_>,
+    ty_name: &str,
+    or_body_snippet: String,
+    indent: usize,
+) {
+    let reindented_or_body = reindent_multiline(or_body_snippet.into(), true, Some(indent));
+
+    let mut app = Applicability::MachineApplicable;
+    let suggestion = sugg::Sugg::hir_with_context(cx, scrutinee, expr.span.ctxt(), "..", &mut app).maybe_par();
+    span_lint_and_sugg(
+        cx,
+        MANUAL_UNWRAP_OR,
+        expr.span,
+        format!("this pattern reimplements `{ty_name}::unwrap_or`"),
+        "replace with",
+        format!("{suggestion}.unwrap_or({reindented_or_body})",),
+        app,
+    );
+}
diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs
index 691ecd57535..bf7156cc53e 100644
--- a/clippy_lints/src/matches/mod.rs
+++ b/clippy_lints/src/matches/mod.rs
@@ -1069,7 +1069,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
                     redundant_guards::check(cx, arms, &self.msrv);
 
                     if !in_constant(cx, expr.hir_id) {
-                        manual_unwrap_or::check(cx, expr, ex, arms);
+                        manual_unwrap_or::check_match(cx, expr, ex, arms);
                         manual_map::check_match(cx, expr, ex, arms);
                         manual_filter::check_match(cx, ex, arms, expr);
                     }
@@ -1097,6 +1097,14 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
                         );
                     }
                     if !in_constant(cx, expr.hir_id) {
+                        manual_unwrap_or::check_if_let(
+                            cx,
+                            expr,
+                            if_let.let_pat,
+                            if_let.let_expr,
+                            if_let.if_then,
+                            else_expr,
+                        );
                         manual_map::check_if_let(cx, expr, if_let.let_pat, if_let.let_expr, if_let.if_then, else_expr);
                         manual_filter::check_if_let(
                             cx,
diff --git a/clippy_lints/src/matches/single_match.rs b/clippy_lints/src/matches/single_match.rs
index 69791414f72..99fdbcff890 100644
--- a/clippy_lints/src/matches/single_match.rs
+++ b/clippy_lints/src/matches/single_match.rs
@@ -1,5 +1,5 @@
 use clippy_utils::diagnostics::span_lint_and_sugg;
-use clippy_utils::source::{expr_block, get_source_text, snippet};
+use clippy_utils::source::{expr_block, snippet, SpanRangeExt};
 use clippy_utils::ty::{implements_trait, is_type_diagnostic_item, peel_mid_ty_refs};
 use clippy_utils::{is_lint_allowed, is_unit_expr, is_wild, peel_blocks, peel_hir_pat_refs, peel_n_hir_expr_refs};
 use core::cmp::max;
@@ -17,7 +17,7 @@ use super::{MATCH_BOOL, SINGLE_MATCH, SINGLE_MATCH_ELSE};
 /// span, e.g. a string literal `"//"`, but we know that this isn't the case for empty
 /// match arms.
 fn empty_arm_has_comment(cx: &LateContext<'_>, span: Span) -> bool {
-    if let Some(ff) = get_source_text(cx, span)
+    if let Some(ff) = span.get_source_text(cx)
         && let Some(text) = ff.as_str()
     {
         text.as_bytes().windows(2).any(|w| w == b"//" || w == b"/*")
diff --git a/clippy_lints/src/methods/manual_inspect.rs b/clippy_lints/src/methods/manual_inspect.rs
new file mode 100644
index 00000000000..e3ce64c246a
--- /dev/null
+++ b/clippy_lints/src/methods/manual_inspect.rs
@@ -0,0 +1,238 @@
+use clippy_config::msrvs::{self, Msrv};
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::{IntoSpan, SpanRangeExt};
+use clippy_utils::ty::get_field_by_name;
+use clippy_utils::visitors::{for_each_expr, for_each_expr_without_closures};
+use clippy_utils::{expr_use_ctxt, is_diag_item_method, is_diag_trait_item, path_to_local_id, ExprUseNode};
+use core::ops::ControlFlow;
+use rustc_errors::Applicability;
+use rustc_hir::{BindingMode, BorrowKind, ByRef, ClosureKind, Expr, ExprKind, Mutability, Node, PatKind};
+use rustc_lint::LateContext;
+use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability};
+use rustc_span::{sym, Span, Symbol, DUMMY_SP};
+
+use super::MANUAL_INSPECT;
+
+#[expect(clippy::too_many_lines)]
+pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, name: &str, name_span: Span, msrv: &Msrv) {
+    if let ExprKind::Closure(c) = arg.kind
+        && matches!(c.kind, ClosureKind::Closure)
+        && let typeck = cx.typeck_results()
+        && let Some(fn_id) = typeck.type_dependent_def_id(expr.hir_id)
+        && (is_diag_trait_item(cx, fn_id, sym::Iterator)
+            || (msrv.meets(msrvs::OPTION_RESULT_INSPECT)
+                && (is_diag_item_method(cx, fn_id, sym::Option) || is_diag_item_method(cx, fn_id, sym::Result))))
+        && let body = cx.tcx.hir().body(c.body)
+        && let [param] = body.params
+        && let PatKind::Binding(BindingMode(ByRef::No, Mutability::Not), arg_id, _, None) = param.pat.kind
+        && let arg_ty = typeck.node_type(arg_id)
+        && let ExprKind::Block(block, _) = body.value.kind
+        && let Some(final_expr) = block.expr
+        && !block.stmts.is_empty()
+        && path_to_local_id(final_expr, arg_id)
+        && typeck.expr_adjustments(final_expr).is_empty()
+    {
+        let mut requires_copy = false;
+        let mut requires_deref = false;
+
+        // The number of unprocessed return expressions.
+        let mut ret_count = 0u32;
+
+        // The uses for which processing is delayed until after the visitor.
+        let mut delayed = vec![];
+
+        let ctxt = arg.span.ctxt();
+        let can_lint = for_each_expr_without_closures(block.stmts, |e| {
+            if let ExprKind::Closure(c) = e.kind {
+                // Nested closures don't need to treat returns specially.
+                let _: Option<!> = for_each_expr(cx, cx.tcx.hir().body(c.body).value, |e| {
+                    if path_to_local_id(e, arg_id) {
+                        let (kind, same_ctxt) = check_use(cx, e);
+                        match (kind, same_ctxt && e.span.ctxt() == ctxt) {
+                            (_, false) | (UseKind::Deref | UseKind::Return(..), true) => {
+                                requires_copy = true;
+                                requires_deref = true;
+                            },
+                            (UseKind::AutoBorrowed, true) => {},
+                            (UseKind::WillAutoDeref, true) => {
+                                requires_copy = true;
+                            },
+                            (kind, true) => delayed.push(kind),
+                        }
+                    }
+                    ControlFlow::Continue(())
+                });
+            } else if matches!(e.kind, ExprKind::Ret(_)) {
+                ret_count += 1;
+            } else if path_to_local_id(e, arg_id) {
+                let (kind, same_ctxt) = check_use(cx, e);
+                match (kind, same_ctxt && e.span.ctxt() == ctxt) {
+                    (UseKind::Return(..), false) => {
+                        return ControlFlow::Break(());
+                    },
+                    (_, false) | (UseKind::Deref, true) => {
+                        requires_copy = true;
+                        requires_deref = true;
+                    },
+                    (UseKind::AutoBorrowed, true) => {},
+                    (UseKind::WillAutoDeref, true) => {
+                        requires_copy = true;
+                    },
+                    (kind @ UseKind::Return(_), true) => {
+                        ret_count -= 1;
+                        delayed.push(kind);
+                    },
+                    (kind, true) => delayed.push(kind),
+                }
+            }
+            ControlFlow::Continue(())
+        })
+        .is_none();
+
+        if ret_count != 0 {
+            // A return expression that didn't return the original value was found.
+            return;
+        }
+
+        let mut edits = Vec::with_capacity(delayed.len() + 3);
+        let mut addr_of_edits = Vec::with_capacity(delayed.len());
+        for x in delayed {
+            match x {
+                UseKind::Return(s) => edits.push((s.with_leading_whitespace(cx).with_ctxt(s.ctxt()), String::new())),
+                UseKind::Borrowed(s) => {
+                    #[expect(clippy::range_plus_one)]
+                    let range = s.map_range(cx, |src, range| {
+                        let src = src.get(range.clone())?;
+                        let trimmed = src.trim_start_matches([' ', '\t', '\n', '\r', '(']);
+                        trimmed.starts_with('&').then(|| {
+                            let pos = range.start + src.len() - trimmed.len();
+                            pos..pos + 1
+                        })
+                    });
+                    if let Some(range) = range {
+                        addr_of_edits.push((range.with_ctxt(s.ctxt()), String::new()));
+                    } else {
+                        requires_copy = true;
+                        requires_deref = true;
+                    }
+                },
+                UseKind::FieldAccess(name, e) => {
+                    let Some(mut ty) = get_field_by_name(cx.tcx, arg_ty.peel_refs(), name) else {
+                        requires_copy = true;
+                        continue;
+                    };
+                    let mut prev_expr = e;
+
+                    for (_, parent) in cx.tcx.hir().parent_iter(e.hir_id) {
+                        if let Node::Expr(e) = parent {
+                            match e.kind {
+                                ExprKind::Field(_, name)
+                                    if let Some(fty) = get_field_by_name(cx.tcx, ty.peel_refs(), name.name) =>
+                                {
+                                    ty = fty;
+                                    prev_expr = e;
+                                    continue;
+                                },
+                                ExprKind::AddrOf(BorrowKind::Ref, ..) => break,
+                                _ if matches!(
+                                    typeck.expr_adjustments(prev_expr).first(),
+                                    Some(Adjustment {
+                                        kind: Adjust::Borrow(AutoBorrow::Ref(_, AutoBorrowMutability::Not))
+                                            | Adjust::Deref(_),
+                                        ..
+                                    })
+                                ) =>
+                                {
+                                    break;
+                                },
+                                _ => {},
+                            }
+                        }
+                        requires_copy |= !ty.is_copy_modulo_regions(cx.tcx, cx.param_env);
+                        break;
+                    }
+                },
+                // Already processed uses.
+                UseKind::AutoBorrowed | UseKind::WillAutoDeref | UseKind::Deref => {},
+            }
+        }
+
+        if can_lint
+            && (!requires_copy || arg_ty.is_copy_modulo_regions(cx.tcx, cx.param_env))
+            // This case could be handled, but a fair bit of care would need to be taken.
+            && (!requires_deref || arg_ty.is_freeze(cx.tcx, cx.param_env))
+        {
+            if requires_deref {
+                edits.push((param.span.shrink_to_lo(), "&".into()));
+            } else {
+                edits.extend(addr_of_edits);
+            }
+            edits.push((
+                name_span,
+                String::from(match name {
+                    "map" => "inspect",
+                    "map_err" => "inspect_err",
+                    _ => return,
+                }),
+            ));
+            edits.push((
+                final_expr
+                    .span
+                    .with_leading_whitespace(cx)
+                    .with_ctxt(final_expr.span.ctxt()),
+                String::new(),
+            ));
+            let app = if edits.iter().any(|(s, _)| s.from_expansion()) {
+                Applicability::MaybeIncorrect
+            } else {
+                Applicability::MachineApplicable
+            };
+            span_lint_and_then(cx, MANUAL_INSPECT, name_span, "", |diag| {
+                diag.multipart_suggestion("try", edits, app);
+            });
+        }
+    }
+}
+
+enum UseKind<'tcx> {
+    AutoBorrowed,
+    WillAutoDeref,
+    Deref,
+    Return(Span),
+    Borrowed(Span),
+    FieldAccess(Symbol, &'tcx Expr<'tcx>),
+}
+
+/// Checks how the value is used, and whether it was used in the same `SyntaxContext`.
+fn check_use<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> (UseKind<'tcx>, bool) {
+    let use_cx = expr_use_ctxt(cx, e);
+    if use_cx
+        .adjustments
+        .first()
+        .is_some_and(|a| matches!(a.kind, Adjust::Deref(_)))
+    {
+        return (UseKind::AutoBorrowed, use_cx.same_ctxt);
+    }
+    let res = match use_cx.use_node(cx) {
+        ExprUseNode::Return(_) => {
+            if let ExprKind::Ret(Some(e)) = use_cx.node.expect_expr().kind {
+                UseKind::Return(e.span)
+            } else {
+                return (UseKind::Return(DUMMY_SP), false);
+            }
+        },
+        ExprUseNode::FieldAccess(name) => UseKind::FieldAccess(name.name, use_cx.node.expect_expr()),
+        ExprUseNode::Callee | ExprUseNode::MethodArg(_, _, 0)
+            if use_cx
+                .adjustments
+                .first()
+                .is_some_and(|a| matches!(a.kind, Adjust::Borrow(AutoBorrow::Ref(_, AutoBorrowMutability::Not)))) =>
+        {
+            UseKind::AutoBorrowed
+        },
+        ExprUseNode::Callee | ExprUseNode::MethodArg(_, _, 0) => UseKind::WillAutoDeref,
+        ExprUseNode::AddrOf(BorrowKind::Ref, _) => UseKind::Borrowed(use_cx.node.expect_expr().span),
+        _ => UseKind::Deref,
+    };
+    (res, use_cx.same_ctxt)
+}
diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs
index 6200716afbe..1408f454820 100644
--- a/clippy_lints/src/methods/mod.rs
+++ b/clippy_lints/src/methods/mod.rs
@@ -53,6 +53,7 @@ mod iter_with_drain;
 mod iterator_step_by_zero;
 mod join_absolute_paths;
 mod manual_c_str_literals;
+mod manual_inspect;
 mod manual_is_variant_and;
 mod manual_next_back;
 mod manual_ok_or;
@@ -116,6 +117,7 @@ mod unnecessary_iter_cloned;
 mod unnecessary_join;
 mod unnecessary_lazy_eval;
 mod unnecessary_literal_unwrap;
+mod unnecessary_min_or_max;
 mod unnecessary_result_map_or_else;
 mod unnecessary_sort_by;
 mod unnecessary_to_owned;
@@ -3946,6 +3948,31 @@ declare_clippy_lint! {
 
 declare_clippy_lint! {
     /// ### What it does
+    /// Checks for unnecessary calls to `min()` or `max()` in the following cases
+    /// - Either both side is constant
+    /// - One side is clearly larger than the other, like i32::MIN and an i32 variable
+    ///
+    /// ### Why is this bad?
+    ///
+    /// In the aformentioned cases it is not necessary to call `min()` or `max()`
+    /// to compare values, it may even cause confusion.
+    ///
+    /// ### Example
+    /// ```no_run
+    /// let _ = 0.min(7_u32);
+    /// ```
+    /// Use instead:
+    /// ```no_run
+    /// let _ = 0;
+    /// ```
+    #[clippy::version = "1.78.0"]
+    pub UNNECESSARY_MIN_OR_MAX,
+    complexity,
+    "using 'min()/max()' when there is no need for it"
+}
+
+declare_clippy_lint! {
+    /// ### What it does
     /// Checks for usage of `.map_or_else()` "map closure" for `Result` type.
     ///
     /// ### Why is this bad?
@@ -4079,6 +4106,27 @@ declare_clippy_lint! {
     "is_ascii() called on a char iterator"
 }
 
+declare_clippy_lint! {
+    /// ### What it does
+    /// Checks for uses of `map` which return the original item.
+    ///
+    /// ### Why is this bad?
+    /// `inspect` is both clearer in intent and shorter.
+    ///
+    /// ### Example
+    /// ```no_run
+    /// let x = Some(0).map(|x| { println!("{x}"); x });
+    /// ```
+    /// Use instead:
+    /// ```no_run
+    /// let x = Some(0).inspect(|x| println!("{x}"));
+    /// ```
+    #[clippy::version = "1.78.0"]
+    pub MANUAL_INSPECT,
+    complexity,
+    "use of `map` returning the original item"
+}
+
 pub struct Methods {
     avoid_breaking_exported_api: bool,
     msrv: Msrv,
@@ -4244,6 +4292,8 @@ impl_lint_pass!(Methods => [
     MANUAL_C_STR_LITERALS,
     UNNECESSARY_GET_THEN_CHECK,
     NEEDLESS_CHARACTER_ITERATION,
+    MANUAL_INSPECT,
+    UNNECESSARY_MIN_OR_MAX,
 ]);
 
 /// Extracts a method call name, args, and `Span` of the method name.
@@ -4543,6 +4593,9 @@ impl Methods {
                     Some(("bytes", recv2, [], _, _)) => bytes_count_to_len::check(cx, expr, recv, recv2),
                     _ => {},
                 },
+                ("min" | "max", [arg]) => {
+                    unnecessary_min_or_max::check(cx, expr, name, recv, arg);
+                },
                 ("drain", ..) => {
                     if let Node::Stmt(Stmt { hir_id: _, kind, .. }) = cx.tcx.parent_hir_node(expr.hir_id)
                         && matches!(kind, StmtKind::Semi(_))
@@ -4747,6 +4800,7 @@ impl Methods {
                         }
                     }
                     map_identity::check(cx, expr, recv, m_arg, name, span);
+                    manual_inspect::check(cx, expr, m_arg, name, span, &self.msrv);
                 },
                 ("map_or", [def, map]) => {
                     option_map_or_none::check(cx, expr, recv, def, map);
diff --git a/clippy_lints/src/methods/unnecessary_min_or_max.rs b/clippy_lints/src/methods/unnecessary_min_or_max.rs
new file mode 100644
index 00000000000..78851d4122f
--- /dev/null
+++ b/clippy_lints/src/methods/unnecessary_min_or_max.rs
@@ -0,0 +1,90 @@
+use std::cmp::Ordering;
+
+use super::UNNECESSARY_MIN_OR_MAX;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+
+use clippy_utils::consts::{constant, constant_with_source, Constant, ConstantSource, FullInt};
+use clippy_utils::source::snippet;
+
+use rustc_errors::Applicability;
+use rustc_hir::Expr;
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_span::Span;
+
+pub(super) fn check<'tcx>(
+    cx: &LateContext<'tcx>,
+    expr: &'tcx Expr<'_>,
+    name: &str,
+    recv: &'tcx Expr<'_>,
+    arg: &'tcx Expr<'_>,
+) {
+    let typeck_results = cx.typeck_results();
+    if let Some((left, ConstantSource::Local | ConstantSource::CoreConstant)) =
+        constant_with_source(cx, typeck_results, recv)
+        && let Some((right, ConstantSource::Local | ConstantSource::CoreConstant)) =
+            constant_with_source(cx, typeck_results, arg)
+    {
+        let Some(ord) = Constant::partial_cmp(cx.tcx, typeck_results.expr_ty(recv), &left, &right) else {
+            return;
+        };
+
+        lint(cx, expr, name, recv.span, arg.span, ord);
+    } else if let Some(extrema) = detect_extrema(cx, recv) {
+        let ord = match extrema {
+            Extrema::Minimum => Ordering::Less,
+            Extrema::Maximum => Ordering::Greater,
+        };
+        lint(cx, expr, name, recv.span, arg.span, ord);
+    } else if let Some(extrema) = detect_extrema(cx, arg) {
+        let ord = match extrema {
+            Extrema::Minimum => Ordering::Greater,
+            Extrema::Maximum => Ordering::Less,
+        };
+        lint(cx, expr, name, recv.span, arg.span, ord);
+    }
+}
+
+fn lint(cx: &LateContext<'_>, expr: &Expr<'_>, name: &str, lhs: Span, rhs: Span, order: Ordering) {
+    let cmp_str = if order.is_ge() { "smaller" } else { "greater" };
+
+    let suggested_value = if (name == "min" && order.is_ge()) || (name == "max" && order.is_le()) {
+        snippet(cx, rhs, "..")
+    } else {
+        snippet(cx, lhs, "..")
+    };
+
+    span_lint_and_sugg(
+        cx,
+        UNNECESSARY_MIN_OR_MAX,
+        expr.span,
+        format!(
+            "`{}` is never {} than `{}` and has therefore no effect",
+            snippet(cx, lhs, ".."),
+            cmp_str,
+            snippet(cx, rhs, "..")
+        ),
+        "try",
+        suggested_value.to_string(),
+        Applicability::MachineApplicable,
+    );
+}
+
+#[derive(Debug)]
+enum Extrema {
+    Minimum,
+    Maximum,
+}
+fn detect_extrema<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<Extrema> {
+    let ty = cx.typeck_results().expr_ty(expr);
+
+    let cv = constant(cx, cx.typeck_results(), expr)?;
+
+    match (cv.int_value(cx, ty)?, ty.kind()) {
+        (FullInt::S(i), &ty::Int(ity)) if i == i128::MIN >> (128 - ity.bit_width()?) => Some(Extrema::Minimum),
+        (FullInt::S(i), &ty::Int(ity)) if i == i128::MAX >> (128 - ity.bit_width()?) => Some(Extrema::Maximum),
+        (FullInt::U(i), &ty::Uint(uty)) if i == u128::MAX >> (128 - uty.bit_width()?) => Some(Extrema::Maximum),
+        (FullInt::U(0), &ty::Uint(_)) => Some(Extrema::Minimum),
+        _ => None,
+    }
+}
diff --git a/clippy_lints/src/methods/unnecessary_sort_by.rs b/clippy_lints/src/methods/unnecessary_sort_by.rs
index 6911da69b94..a92fe9e55a4 100644
--- a/clippy_lints/src/methods/unnecessary_sort_by.rs
+++ b/clippy_lints/src/methods/unnecessary_sort_by.rs
@@ -5,7 +5,8 @@ use clippy_utils::ty::implements_trait;
 use rustc_errors::Applicability;
 use rustc_hir::{Closure, Expr, ExprKind, Mutability, Param, Pat, PatKind, Path, PathSegment, QPath};
 use rustc_lint::LateContext;
-use rustc_middle::ty::{self, GenericArgKind};
+use rustc_middle::ty;
+use rustc_middle::ty::GenericArgKind;
 use rustc_span::sym;
 use rustc_span::symbol::Ident;
 use std::iter;
diff --git a/clippy_lints/src/missing_const_for_fn.rs b/clippy_lints/src/missing_const_for_fn.rs
index 4592324809f..bb0d714a31f 100644
--- a/clippy_lints/src/missing_const_for_fn.rs
+++ b/clippy_lints/src/missing_const_for_fn.rs
@@ -1,11 +1,11 @@
 use clippy_config::msrvs::{self, Msrv};
-use clippy_utils::diagnostics::span_lint;
+use clippy_utils::diagnostics::span_lint_and_then;
 use clippy_utils::qualify_min_const_fn::is_min_const_fn;
 use clippy_utils::{fn_has_unsatisfiable_preds, is_entrypoint_fn, is_from_proc_macro, trait_ref_of_method};
-use rustc_hir as hir;
+use rustc_errors::Applicability;
 use rustc_hir::def_id::CRATE_DEF_ID;
 use rustc_hir::intravisit::FnKind;
-use rustc_hir::{Body, Constness, FnDecl, GenericParamKind};
+use rustc_hir::{self as hir, Body, Constness, FnDecl, GenericParamKind};
 use rustc_lint::{LateContext, LateLintPass};
 use rustc_middle::lint::in_external_macro;
 use rustc_session::impl_lint_pass;
@@ -120,7 +120,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn {
                 }
             },
             FnKind::Method(_, sig, ..) => {
-                if trait_ref_of_method(cx, def_id).is_some() || already_const(sig.header) {
+                if already_const(sig.header) || trait_ref_of_method(cx, def_id).is_some() {
                     return;
                 }
             },
@@ -147,10 +147,22 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn {
 
         let mir = cx.tcx.optimized_mir(def_id);
 
-        if let Ok(()) = is_min_const_fn(cx.tcx, mir, &self.msrv) {
-            span_lint(cx, MISSING_CONST_FOR_FN, span, "this could be a `const fn`");
+        if let Ok(()) = is_min_const_fn(cx.tcx, mir, &self.msrv)
+            && let hir::Node::Item(hir::Item { vis_span, .. }) | hir::Node::ImplItem(hir::ImplItem { vis_span, .. }) =
+                cx.tcx.hir_node_by_def_id(def_id)
+        {
+            let suggestion = if vis_span.is_empty() { "const " } else { " const" };
+            span_lint_and_then(cx, MISSING_CONST_FOR_FN, span, "this could be a `const fn`", |diag| {
+                diag.span_suggestion_verbose(
+                    vis_span.shrink_to_hi(),
+                    "make the function `const`",
+                    suggestion,
+                    Applicability::MachineApplicable,
+                );
+            });
         }
     }
+
     extract_msrv_attr!(LateContext);
 }
 
diff --git a/clippy_lints/src/missing_doc.rs b/clippy_lints/src/missing_doc.rs
index ca344dc5c81..250fd5cbd48 100644
--- a/clippy_lints/src/missing_doc.rs
+++ b/clippy_lints/src/missing_doc.rs
@@ -8,7 +8,7 @@
 use clippy_utils::attrs::is_doc_hidden;
 use clippy_utils::diagnostics::span_lint;
 use clippy_utils::is_from_proc_macro;
-use clippy_utils::source::snippet_opt;
+use clippy_utils::source::SpanRangeExt;
 use rustc_ast::ast::{self, MetaItem, MetaItemKind};
 use rustc_hir as hir;
 use rustc_hir::def_id::LocalDefId;
@@ -266,8 +266,5 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc {
 }
 
 fn span_to_snippet_contains_docs(cx: &LateContext<'_>, search_span: Span) -> bool {
-    let Some(snippet) = snippet_opt(cx, search_span) else {
-        return false;
-    };
-    snippet.lines().rev().any(|line| line.trim().starts_with("///"))
+    search_span.check_source_text(cx, |src| src.lines().rev().any(|line| line.trim().starts_with("///")))
 }
diff --git a/clippy_lints/src/multiple_bound_locations.rs b/clippy_lints/src/multiple_bound_locations.rs
index d608f3bf7b4..d276e29bace 100644
--- a/clippy_lints/src/multiple_bound_locations.rs
+++ b/clippy_lints/src/multiple_bound_locations.rs
@@ -6,7 +6,7 @@ use rustc_session::declare_lint_pass;
 use rustc_span::Span;
 
 use clippy_utils::diagnostics::span_lint;
-use clippy_utils::source::snippet_opt;
+use clippy_utils::source::SpanRangeExt;
 
 declare_clippy_lint! {
     /// ### What it does
@@ -54,8 +54,10 @@ impl EarlyLintPass for MultipleBoundLocations {
                 match clause {
                     WherePredicate::BoundPredicate(pred) => {
                         if (!pred.bound_generic_params.is_empty() || !pred.bounds.is_empty())
-                            && let Some(name) = snippet_opt(cx, pred.bounded_ty.span)
-                            && let Some(bound_span) = generic_params_with_bounds.get(name.as_str())
+                            && let Some(Some(bound_span)) = pred
+                                .bounded_ty
+                                .span
+                                .with_source_text(cx, |src| generic_params_with_bounds.get(src))
                         {
                             emit_lint(cx, *bound_span, pred.bounded_ty.span);
                         }
diff --git a/clippy_lints/src/mutable_debug_assertion.rs b/clippy_lints/src/mutable_debug_assertion.rs
index e92ba93942e..563ce2d82ea 100644
--- a/clippy_lints/src/mutable_debug_assertion.rs
+++ b/clippy_lints/src/mutable_debug_assertion.rs
@@ -96,10 +96,6 @@ impl<'a, 'tcx> Visitor<'tcx> for MutArgVisitor<'a, 'tcx> {
                 self.found = true;
                 return;
             },
-            ExprKind::If(..) => {
-                self.found = true;
-                return;
-            },
             ExprKind::Path(_) => {
                 if let Some(adj) = self.cx.typeck_results().adjustments().get(expr.hir_id) {
                     if adj
diff --git a/clippy_lints/src/needless_borrows_for_generic_args.rs b/clippy_lints/src/needless_borrows_for_generic_args.rs
index f4846a1753f..064ce59c234 100644
--- a/clippy_lints/src/needless_borrows_for_generic_args.rs
+++ b/clippy_lints/src/needless_borrows_for_generic_args.rs
@@ -80,11 +80,13 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBorrowsForGenericArgs<'tcx> {
     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
         if matches!(expr.kind, ExprKind::AddrOf(..))
             && !expr.span.from_expansion()
-            && let Some(use_cx) = expr_use_ctxt(cx, expr)
+            && let use_cx = expr_use_ctxt(cx, expr)
+            && use_cx.same_ctxt
             && !use_cx.is_ty_unified
-            && let Some(DefinedTy::Mir(ty)) = use_cx.node.defined_ty(cx)
+            && let use_node = use_cx.use_node(cx)
+            && let Some(DefinedTy::Mir(ty)) = use_node.defined_ty(cx)
             && let ty::Param(ty) = *ty.value.skip_binder().kind()
-            && let Some((hir_id, fn_id, i)) = match use_cx.node {
+            && let Some((hir_id, fn_id, i)) = match use_node {
                 ExprUseNode::MethodArg(_, _, 0) => None,
                 ExprUseNode::MethodArg(hir_id, None, i) => cx
                     .typeck_results()
diff --git a/clippy_lints/src/needless_else.rs b/clippy_lints/src/needless_else.rs
index b6aad69d166..f8bb72a16db 100644
--- a/clippy_lints/src/needless_else.rs
+++ b/clippy_lints/src/needless_else.rs
@@ -1,8 +1,8 @@
 use clippy_utils::diagnostics::span_lint_and_sugg;
-use clippy_utils::source::{snippet_opt, trim_span};
+use clippy_utils::source::{IntoSpan, SpanRangeExt};
 use rustc_ast::ast::{Expr, ExprKind};
 use rustc_errors::Applicability;
-use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
+use rustc_lint::{EarlyContext, EarlyLintPass};
 use rustc_session::declare_lint_pass;
 
 declare_clippy_lint! {
@@ -41,16 +41,16 @@ impl EarlyLintPass for NeedlessElse {
             && !expr.span.from_expansion()
             && !else_clause.span.from_expansion()
             && block.stmts.is_empty()
-            && let Some(trimmed) = expr.span.trim_start(then_block.span)
-            && let span = trim_span(cx.sess().source_map(), trimmed)
-            && let Some(else_snippet) = snippet_opt(cx, span)
-            // Ignore else blocks that contain comments or #[cfg]s
-            && !else_snippet.contains(['/', '#'])
+            && let range = (then_block.span.hi()..expr.span.hi()).trim_start(cx)
+            && range.clone().check_source_text(cx, |src| {
+                // Ignore else blocks that contain comments or #[cfg]s
+                !src.contains(['/', '#'])
+            })
         {
             span_lint_and_sugg(
                 cx,
                 NEEDLESS_ELSE,
-                span,
+                range.with_ctxt(expr.span.ctxt()),
                 "this `else` branch is empty",
                 "you can remove it",
                 String::new(),
diff --git a/clippy_lints/src/needless_for_each.rs b/clippy_lints/src/needless_for_each.rs
index 630018238f4..143acc2b1cb 100644
--- a/clippy_lints/src/needless_for_each.rs
+++ b/clippy_lints/src/needless_for_each.rs
@@ -25,14 +25,14 @@ declare_clippy_lint! {
     /// ```no_run
     /// let v = vec![0, 1, 2];
     /// v.iter().for_each(|elem| {
-    ///     println!("{}", elem);
+    ///     println!("{elem}");
     /// })
     /// ```
     /// Use instead:
     /// ```no_run
     /// let v = vec![0, 1, 2];
-    /// for elem in v.iter() {
-    ///     println!("{}", elem);
+    /// for elem in &v {
+    ///     println!("{elem}");
     /// }
     /// ```
     ///
diff --git a/clippy_lints/src/needless_if.rs b/clippy_lints/src/needless_if.rs
index 51bee4b51f6..1d6233d432a 100644
--- a/clippy_lints/src/needless_if.rs
+++ b/clippy_lints/src/needless_if.rs
@@ -1,7 +1,7 @@
 use clippy_utils::diagnostics::span_lint_and_sugg;
 use clippy_utils::higher::If;
 use clippy_utils::is_from_proc_macro;
-use clippy_utils::source::snippet_opt;
+use clippy_utils::source::{snippet_opt, SpanRangeExt};
 use rustc_errors::Applicability;
 use rustc_hir::{ExprKind, Stmt, StmtKind};
 use rustc_lint::{LateContext, LateLintPass, LintContext};
@@ -39,18 +39,24 @@ declare_lint_pass!(NeedlessIf => [NEEDLESS_IF]);
 impl LateLintPass<'_> for NeedlessIf {
     fn check_stmt<'tcx>(&mut self, cx: &LateContext<'tcx>, stmt: &Stmt<'tcx>) {
         if let StmtKind::Expr(expr) = stmt.kind
-            && let Some(If {cond, then, r#else: None }) = If::hir(expr)
+            && let Some(If {
+                cond,
+                then,
+                r#else: None,
+            }) = If::hir(expr)
             && let ExprKind::Block(block, ..) = then.kind
             && block.stmts.is_empty()
             && block.expr.is_none()
             && !in_external_macro(cx.sess(), expr.span)
-            && let Some(then_snippet) = snippet_opt(cx, then.span)
-            // Ignore
-            // - empty macro expansions
-            // - empty reptitions in macro expansions
-            // - comments
-            // - #[cfg]'d out code
-            && then_snippet.chars().all(|ch| matches!(ch, '{' | '}') || ch.is_ascii_whitespace())
+            && then.span.check_source_text(cx, |src| {
+                // Ignore
+                // - empty macro expansions
+                // - empty reptitions in macro expansions
+                // - comments
+                // - #[cfg]'d out code
+                src.bytes()
+                    .all(|ch| matches!(ch, b'{' | b'}') || ch.is_ascii_whitespace())
+            })
             && let Some(cond_snippet) = snippet_opt(cx, cond.span)
             && !is_from_proc_macro(cx, expr)
         {
diff --git a/clippy_lints/src/non_octal_unix_permissions.rs b/clippy_lints/src/non_octal_unix_permissions.rs
index 2701d6bdca3..b915df52762 100644
--- a/clippy_lints/src/non_octal_unix_permissions.rs
+++ b/clippy_lints/src/non_octal_unix_permissions.rs
@@ -1,5 +1,5 @@
 use clippy_utils::diagnostics::span_lint_and_sugg;
-use clippy_utils::source::{snippet_opt, snippet_with_applicability};
+use clippy_utils::source::{snippet_with_applicability, SpanRangeExt};
 use clippy_utils::{match_def_path, paths};
 use rustc_errors::Applicability;
 use rustc_hir::{Expr, ExprKind};
@@ -53,8 +53,9 @@ impl<'tcx> LateLintPass<'tcx> for NonOctalUnixPermissions {
                             && cx.tcx.is_diagnostic_item(sym::FsPermissions, adt.did())))
                     && let ExprKind::Lit(_) = param.kind
                     && param.span.eq_ctxt(expr.span)
-                    && let Some(snip) = snippet_opt(cx, param.span)
-                    && !(snip.starts_with("0o") || snip.starts_with("0b"))
+                    && param
+                        .span
+                        .check_source_text(cx, |src| !matches!(src.as_bytes(), [b'0', b'o' | b'b', ..]))
                 {
                     show_error(cx, param);
                 }
@@ -65,8 +66,9 @@ impl<'tcx> LateLintPass<'tcx> for NonOctalUnixPermissions {
                     && match_def_path(cx, def_id, &paths::PERMISSIONS_FROM_MODE)
                     && let ExprKind::Lit(_) = param.kind
                     && param.span.eq_ctxt(expr.span)
-                    && let Some(snip) = snippet_opt(cx, param.span)
-                    && !(snip.starts_with("0o") || snip.starts_with("0b"))
+                    && param
+                        .span
+                        .check_source_text(cx, |src| !matches!(src.as_bytes(), [b'0', b'o' | b'b', ..]))
                 {
                     show_error(cx, param);
                 }
diff --git a/clippy_lints/src/octal_escapes.rs b/clippy_lints/src/octal_escapes.rs
index 2fc039ae886..2eae9b23746 100644
--- a/clippy_lints/src/octal_escapes.rs
+++ b/clippy_lints/src/octal_escapes.rs
@@ -1,12 +1,12 @@
 use clippy_utils::diagnostics::span_lint_and_then;
-use rustc_ast::ast::{Expr, ExprKind};
-use rustc_ast::token::{Lit, LitKind};
+use clippy_utils::source::SpanRangeExt;
+use rustc_ast::token::LitKind;
+use rustc_ast::{Expr, ExprKind};
 use rustc_errors::Applicability;
 use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
 use rustc_middle::lint::in_external_macro;
 use rustc_session::declare_lint_pass;
-use rustc_span::Span;
-use std::fmt::Write;
+use rustc_span::{BytePos, Pos, SpanData};
 
 declare_clippy_lint! {
     /// ### What it does
@@ -52,104 +52,66 @@ declare_lint_pass!(OctalEscapes => [OCTAL_ESCAPES]);
 
 impl EarlyLintPass for OctalEscapes {
     fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
-        if in_external_macro(cx.sess(), expr.span) {
-            return;
-        }
-
-        if let ExprKind::Lit(token_lit) = &expr.kind {
-            if matches!(token_lit.kind, LitKind::Str) {
-                check_lit(cx, token_lit, expr.span, true);
-            } else if matches!(token_lit.kind, LitKind::ByteStr) {
-                check_lit(cx, token_lit, expr.span, false);
-            }
-        }
-    }
-}
-
-fn check_lit(cx: &EarlyContext<'_>, lit: &Lit, span: Span, is_string: bool) {
-    let contents = lit.symbol.as_str();
-    let mut iter = contents.char_indices().peekable();
-    let mut found = vec![];
+        if let ExprKind::Lit(lit) = &expr.kind
+            // The number of bytes from the start of the token to the start of literal's text.
+            && let start_offset = BytePos::from_u32(match lit.kind {
+                LitKind::Str => 1,
+                LitKind::ByteStr | LitKind::CStr => 2,
+                _ => return,
+            })
+            && !in_external_macro(cx.sess(), expr.span)
+        {
+            let s = lit.symbol.as_str();
+            let mut iter = s.as_bytes().iter();
+            while let Some(&c) = iter.next() {
+                if c == b'\\'
+                    // Always move the iterator to read the escape char.
+                    && let Some(b'0') = iter.next()
+                {
+                    // C-style octal escapes read from one to three characters.
+                    // The first character (`0`) has already been read.
+                    let (tail, len, c_hi, c_lo) = match *iter.as_slice() {
+                        [c_hi @ b'0'..=b'7', c_lo @ b'0'..=b'7', ref tail @ ..] => (tail, 4, c_hi, c_lo),
+                        [c_lo @ b'0'..=b'7', ref tail @ ..] => (tail, 3, b'0', c_lo),
+                        _ => continue,
+                    };
+                    iter = tail.iter();
+                    let offset = start_offset + BytePos::from_usize(s.len() - tail.len());
+                    let data = expr.span.data();
+                    let span = SpanData {
+                        lo: data.lo + offset - BytePos::from_u32(len),
+                        hi: data.lo + offset,
+                        ..data
+                    }
+                    .span();
 
-    // go through the string, looking for \0[0-7][0-7]?
-    while let Some((from, ch)) = iter.next() {
-        if ch == '\\' {
-            if let Some((_, '0')) = iter.next() {
-                // collect up to two further octal digits
-                if let Some((mut to, _)) = iter.next_if(|(_, ch)| matches!(ch, '0'..='7')) {
-                    if iter.next_if(|(_, ch)| matches!(ch, '0'..='7')).is_some() {
-                        to += 1;
+                    // Last check to make sure the source text matches what we read from the string.
+                    // Macros are involved somehow if this doesn't match.
+                    if span.check_source_text(cx, |src| match *src.as_bytes() {
+                        [b'\\', b'0', lo] => lo == c_lo,
+                        [b'\\', b'0', hi, lo] => hi == c_hi && lo == c_lo,
+                        _ => false,
+                    }) {
+                        span_lint_and_then(cx, OCTAL_ESCAPES, span, "octal-looking escape in a literal", |diag| {
+                            diag.help_once("octal escapes are not supported, `\\0` is always null")
+                                .span_suggestion(
+                                    span,
+                                    "if an octal escape is intended, use a hex escape instead",
+                                    format!("\\x{:02x}", (((c_hi - b'0') << 3) | (c_lo - b'0'))),
+                                    Applicability::MaybeIncorrect,
+                                )
+                                .span_suggestion(
+                                    span,
+                                    "if a null escape is intended, disambiguate using",
+                                    format!("\\x00{}{}", c_hi as char, c_lo as char),
+                                    Applicability::MaybeIncorrect,
+                                );
+                        });
+                    } else {
+                        break;
                     }
-                    found.push((from, to + 1));
                 }
             }
         }
     }
-
-    if found.is_empty() {
-        return;
-    }
-
-    span_lint_and_then(
-        cx,
-        OCTAL_ESCAPES,
-        span,
-        format!(
-            "octal-looking escape in {} literal",
-            if is_string { "string" } else { "byte string" }
-        ),
-        |diag| {
-            diag.help(format!(
-                "octal escapes are not supported, `\\0` is always a null {}",
-                if is_string { "character" } else { "byte" }
-            ));
-
-            // Generate suggestions if the string is not too long (~ 5 lines)
-            if contents.len() < 400 {
-                // construct two suggestion strings, one with \x escapes with octal meaning
-                // as in C, and one with \x00 for null bytes.
-                let mut suggest_1 = if is_string { "\"" } else { "b\"" }.to_string();
-                let mut suggest_2 = suggest_1.clone();
-                let mut index = 0;
-                for (from, to) in found {
-                    suggest_1.push_str(&contents[index..from]);
-                    suggest_2.push_str(&contents[index..from]);
-
-                    // construct a replacement escape
-                    // the maximum value is \077, or \x3f, so u8 is sufficient here
-                    if let Ok(n) = u8::from_str_radix(&contents[from + 1..to], 8) {
-                        write!(suggest_1, "\\x{n:02x}").unwrap();
-                    }
-
-                    // append the null byte as \x00 and the following digits literally
-                    suggest_2.push_str("\\x00");
-                    suggest_2.push_str(&contents[from + 2..to]);
-
-                    index = to;
-                }
-                suggest_1.push_str(&contents[index..]);
-                suggest_2.push_str(&contents[index..]);
-
-                suggest_1.push('"');
-                suggest_2.push('"');
-                // suggestion 1: equivalent hex escape
-                diag.span_suggestion(
-                    span,
-                    "if an octal escape was intended, use the hexadecimal representation instead",
-                    suggest_1,
-                    Applicability::MaybeIncorrect,
-                );
-                // suggestion 2: unambiguous null byte
-                diag.span_suggestion(
-                    span,
-                    format!(
-                        "if the null {} is intended, disambiguate using",
-                        if is_string { "character" } else { "byte" }
-                    ),
-                    suggest_2,
-                    Applicability::MaybeIncorrect,
-                );
-            }
-        },
-    );
 }
diff --git a/clippy_lints/src/ranges.rs b/clippy_lints/src/ranges.rs
index 186e548d373..4fdaa9f00a1 100644
--- a/clippy_lints/src/ranges.rs
+++ b/clippy_lints/src/ranges.rs
@@ -1,7 +1,7 @@
 use clippy_config::msrvs::{self, Msrv};
 use clippy_utils::consts::{constant, Constant};
 use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then};
-use clippy_utils::source::{snippet, snippet_opt, snippet_with_applicability};
+use clippy_utils::source::{snippet, snippet_with_applicability, SpanRangeExt};
 use clippy_utils::sugg::Sugg;
 use clippy_utils::{get_parent_expr, higher, in_constant, is_integer_const, path_to_local};
 use rustc_ast::ast::RangeLimits;
@@ -285,9 +285,10 @@ fn check_possible_range_contains(
     if let ExprKind::Binary(ref lhs_op, _left, new_lhs) = left.kind
         && op == lhs_op.node
         && let new_span = Span::new(new_lhs.span.lo(), right.span.hi(), expr.span.ctxt(), expr.span.parent())
-        && let Some(snip) = &snippet_opt(cx, new_span)
-        // Do not continue if we have mismatched number of parens, otherwise the suggestion is wrong
-        && snip.matches('(').count() == snip.matches(')').count()
+        && new_span.check_source_text(cx, |src| {
+            // Do not continue if we have mismatched number of parens, otherwise the suggestion is wrong
+            src.matches('(').count() == src.matches(')').count()
+        })
     {
         check_possible_range_contains(cx, op, new_lhs, right, expr, new_span);
     }
@@ -363,17 +364,19 @@ fn check_exclusive_range_plus_one(cx: &LateContext<'_>, expr: &Expr<'_>) {
             |diag| {
                 let start = start.map_or(String::new(), |x| Sugg::hir(cx, x, "x").maybe_par().to_string());
                 let end = Sugg::hir(cx, y, "y").maybe_par();
-                if let Some(is_wrapped) = &snippet_opt(cx, span) {
-                    if is_wrapped.starts_with('(') && is_wrapped.ends_with(')') {
+                match span.with_source_text(cx, |src| src.starts_with('(') && src.ends_with(')')) {
+                    Some(true) => {
                         diag.span_suggestion(span, "use", format!("({start}..={end})"), Applicability::MaybeIncorrect);
-                    } else {
+                    },
+                    Some(false) => {
                         diag.span_suggestion(
                             span,
                             "use",
                             format!("{start}..={end}"),
                             Applicability::MachineApplicable, // snippet
                         );
-                    }
+                    },
+                    None => {},
                 }
             },
         );
diff --git a/clippy_lints/src/string_patterns.rs b/clippy_lints/src/string_patterns.rs
index 64b5b8f9f27..7ba58942a17 100644
--- a/clippy_lints/src/string_patterns.rs
+++ b/clippy_lints/src/string_patterns.rs
@@ -1,5 +1,6 @@
 use std::ops::ControlFlow;
 
+use clippy_config::msrvs::{self, Msrv};
 use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
 use clippy_utils::eager_or_lazy::switch_to_eager_eval;
 use clippy_utils::macros::matching_root_macro_call;
@@ -12,7 +13,7 @@ use rustc_errors::Applicability;
 use rustc_hir::{Expr, ExprKind, PatKind};
 use rustc_lint::{LateContext, LateLintPass};
 use rustc_middle::ty;
-use rustc_session::declare_lint_pass;
+use rustc_session::impl_lint_pass;
 use rustc_span::{sym, Span};
 
 declare_clippy_lint! {
@@ -69,7 +70,18 @@ declare_clippy_lint! {
     "using a single-character str where a char could be used, e.g., `_.split(\"x\")`"
 }
 
-declare_lint_pass!(StringPatterns => [MANUAL_PATTERN_CHAR_COMPARISON, SINGLE_CHAR_PATTERN]);
+pub struct StringPatterns {
+    msrv: Msrv,
+}
+
+impl StringPatterns {
+    #[must_use]
+    pub fn new(msrv: Msrv) -> Self {
+        Self { msrv }
+    }
+}
+
+impl_lint_pass!(StringPatterns => [MANUAL_PATTERN_CHAR_COMPARISON, SINGLE_CHAR_PATTERN]);
 
 const PATTERN_METHODS: [(&str, usize); 22] = [
     ("contains", 0),
@@ -122,7 +134,7 @@ fn get_char_span<'tcx>(cx: &'_ LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Optio
     }
 }
 
-fn check_manual_pattern_char_comparison(cx: &LateContext<'_>, method_arg: &Expr<'_>) {
+fn check_manual_pattern_char_comparison(cx: &LateContext<'_>, method_arg: &Expr<'_>, msrv: &Msrv) {
     if let ExprKind::Closure(closure) = method_arg.kind
         && let body = cx.tcx.hir().body(closure.body)
         && let Some(PatKind::Binding(_, binding, ..)) = body.params.first().map(|p| p.pat.kind)
@@ -178,6 +190,9 @@ fn check_manual_pattern_char_comparison(cx: &LateContext<'_>, method_arg: &Expr<
         {
             return;
         }
+        if set_char_spans.len() > 1 && !msrv.meets(msrvs::PATTERN_TRAIT_CHAR_ARRAY) {
+            return;
+        }
         span_lint_and_then(
             cx,
             MANUAL_PATTERN_CHAR_COMPARISON,
@@ -221,7 +236,9 @@ impl<'tcx> LateLintPass<'tcx> for StringPatterns {
         {
             check_single_char_pattern_lint(cx, arg);
 
-            check_manual_pattern_char_comparison(cx, arg);
+            check_manual_pattern_char_comparison(cx, arg, &self.msrv);
         }
     }
+
+    extract_msrv_attr!(LateContext);
 }
diff --git a/clippy_lints/src/upper_case_acronyms.rs b/clippy_lints/src/upper_case_acronyms.rs
index f376d349646..72392f8e1f7 100644
--- a/clippy_lints/src/upper_case_acronyms.rs
+++ b/clippy_lints/src/upper_case_acronyms.rs
@@ -1,5 +1,5 @@
 use clippy_utils::diagnostics::span_lint_hir_and_then;
-use itertools::Itertools;
+use core::mem::replace;
 use rustc_errors::Applicability;
 use rustc_hir::{HirId, Item, ItemKind};
 use rustc_lint::{LateContext, LateLintPass, LintContext};
@@ -56,55 +56,71 @@ impl UpperCaseAcronyms {
 
 impl_lint_pass!(UpperCaseAcronyms => [UPPER_CASE_ACRONYMS]);
 
-fn correct_ident(ident: &str) -> String {
-    let ident = ident.chars().rev().collect::<String>();
-    let fragments = ident
-        .split_inclusive(|x: char| !x.is_ascii_lowercase())
-        .rev()
-        .map(|x| x.chars().rev().collect::<String>());
-
-    let mut ident = fragments.clone().next().unwrap();
-    for (ref prev, ref curr) in fragments.tuple_windows() {
-        if <[&String; 2]>::from((prev, curr))
-            .iter()
-            .all(|s| s.len() == 1 && s.chars().next().unwrap().is_ascii_uppercase())
-        {
-            ident.push_str(&curr.to_ascii_lowercase());
+fn contains_acronym(s: &str) -> bool {
+    let mut count = 0;
+    for c in s.chars() {
+        if c.is_ascii_uppercase() {
+            count += 1;
+            if count == 3 {
+                return true;
+            }
         } else {
-            ident.push_str(curr);
+            count = 0;
         }
     }
-    ident
+    count == 2
 }
 
 fn check_ident(cx: &LateContext<'_>, ident: &Ident, hir_id: HirId, be_aggressive: bool) {
-    let span = ident.span;
-    let ident = ident.as_str();
-    let corrected = correct_ident(ident);
-    // warn if we have pure-uppercase idents
-    // assume that two-letter words are some kind of valid abbreviation like FP for false positive
-    // (and don't warn)
-    if (ident.chars().all(|c| c.is_ascii_uppercase()) && ident.len() > 2)
-    // otherwise, warn if we have SOmeTHING lIKE THIs but only warn with the aggressive
-    // upper-case-acronyms-aggressive config option enabled
-    || (be_aggressive && ident != corrected)
+    let s = ident.as_str();
+
+    // By default, only warn for upper case identifiers with at least 3 characters.
+    let replacement = if s.len() > 2 && s.bytes().all(|c| c.is_ascii_uppercase()) {
+        let mut r = String::with_capacity(s.len());
+        let mut s = s.chars();
+        r.push(s.next().unwrap());
+        r.extend(s.map(|c| c.to_ascii_lowercase()));
+        r
+    } else if be_aggressive
+        // Only lint if the ident starts with an upper case character.
+        && let unprefixed = s.trim_start_matches('_')
+        && unprefixed.starts_with(|c: char| c.is_ascii_uppercase())
+        && contains_acronym(unprefixed)
     {
-        span_lint_hir_and_then(
-            cx,
-            UPPER_CASE_ACRONYMS,
-            hir_id,
-            span,
-            format!("name `{ident}` contains a capitalized acronym"),
-            |diag| {
-                diag.span_suggestion(
-                    span,
-                    "consider making the acronym lowercase, except the initial letter",
-                    corrected,
-                    Applicability::MaybeIncorrect,
-                );
-            },
-        );
-    }
+        let mut r = String::with_capacity(s.len());
+        let mut s = s.chars();
+        let mut prev_upper = false;
+        while let Some(c) = s.next() {
+            r.push(
+                if replace(&mut prev_upper, c.is_ascii_uppercase())
+                    && s.clone().next().map_or(true, |c| c.is_ascii_uppercase())
+                {
+                    c.to_ascii_lowercase()
+                } else {
+                    c
+                },
+            );
+        }
+        r
+    } else {
+        return;
+    };
+
+    span_lint_hir_and_then(
+        cx,
+        UPPER_CASE_ACRONYMS,
+        hir_id,
+        ident.span,
+        format!("name `{ident}` contains a capitalized acronym"),
+        |diag| {
+            diag.span_suggestion(
+                ident.span,
+                "consider making the acronym lowercase, except the initial letter",
+                replacement,
+                Applicability::MaybeIncorrect,
+            );
+        },
+    );
 }
 
 impl LateLintPass<'_> for UpperCaseAcronyms {
diff --git a/clippy_lints/src/utils/internal_lints/invalid_paths.rs b/clippy_lints/src/utils/internal_lints/invalid_paths.rs
index c62ae8d718d..0beb0bb8ed4 100644
--- a/clippy_lints/src/utils/internal_lints/invalid_paths.rs
+++ b/clippy_lints/src/utils/internal_lints/invalid_paths.rs
@@ -69,6 +69,7 @@ pub fn check_path(cx: &LateContext<'_>, path: &[&str]) -> bool {
         SimplifiedType::Float(FloatTy::F64),
         SimplifiedType::Slice,
         SimplifiedType::Str,
+        SimplifiedType::Bool,
     ]
     .iter()
     .flat_map(|&ty| cx.tcx.incoherent_impls(ty).into_iter())