about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--clippy_lints/src/attrs/empty_line_after.rs4
-rw-r--r--clippy_lints/src/attrs/non_minimal_cfg.rs11
-rw-r--r--clippy_lints/src/attrs/unnecessary_clippy_cfg.rs11
-rw-r--r--clippy_lints/src/attrs/useless_attribute.rs10
-rw-r--r--clippy_lints/src/casts/as_ptr_cast_mut.rs4
-rw-r--r--clippy_lints/src/manual_async_fn.rs8
-rw-r--r--clippy_lints/src/matches/single_match.rs2
-rw-r--r--clippy_lints/src/needless_pass_by_value.rs10
-rw-r--r--clippy_lints/src/pathbuf_init_then_push.rs8
-rw-r--r--clippy_lints/src/ptr.rs14
-rw-r--r--clippy_lints/src/ptr_offset_with_cast.rs6
-rw-r--r--clippy_lints/src/redundant_clone.rs4
-rw-r--r--clippy_lints/src/reference.rs10
-rw-r--r--clippy_lints/src/regex.rs4
-rw-r--r--clippy_lints/src/returns.rs25
-rw-r--r--clippy_lints/src/single_range_in_vec_init.rs8
-rw-r--r--clippy_lints/src/trait_bounds.rs10
-rw-r--r--clippy_lints/src/unused_unit.rs22
-rw-r--r--clippy_lints/src/visibility.rs14
-rw-r--r--clippy_lints/src/write.rs8
-rw-r--r--clippy_utils/src/consts.rs2
-rw-r--r--clippy_utils/src/hir_utils.rs4
-rw-r--r--clippy_utils/src/source.rs80
23 files changed, 161 insertions, 118 deletions
diff --git a/clippy_lints/src/attrs/empty_line_after.rs b/clippy_lints/src/attrs/empty_line_after.rs
index ca43e76ac57..7ff644b4c44 100644
--- a/clippy_lints/src/attrs/empty_line_after.rs
+++ b/clippy_lints/src/attrs/empty_line_after.rs
@@ -1,6 +1,6 @@
 use super::{EMPTY_LINE_AFTER_DOC_COMMENTS, EMPTY_LINE_AFTER_OUTER_ATTR};
 use clippy_utils::diagnostics::span_lint;
-use clippy_utils::source::{is_present_in_source, snippet_opt, without_block_comments};
+use clippy_utils::source::{is_present_in_source, without_block_comments, SpanRangeExt};
 use rustc_ast::{AttrKind, AttrStyle};
 use rustc_lint::EarlyContext;
 use rustc_span::Span;
@@ -26,7 +26,7 @@ pub(super) fn check(cx: &EarlyContext<'_>, item: &rustc_ast::Item) {
                 item.span.parent(),
             );
 
-            if let Some(snippet) = snippet_opt(cx, end_of_attr_to_next_attr_or_item) {
+            if let Some(snippet) = end_of_attr_to_next_attr_or_item.get_source_text(cx) {
                 let lines = snippet.split('\n').collect::<Vec<_>>();
                 let lines = without_block_comments(lines);
 
diff --git a/clippy_lints/src/attrs/non_minimal_cfg.rs b/clippy_lints/src/attrs/non_minimal_cfg.rs
index 3fde7061585..877025cce4c 100644
--- a/clippy_lints/src/attrs/non_minimal_cfg.rs
+++ b/clippy_lints/src/attrs/non_minimal_cfg.rs
@@ -1,6 +1,6 @@
 use super::{Attribute, NON_MINIMAL_CFG};
 use clippy_utils::diagnostics::span_lint_and_then;
-use clippy_utils::source::snippet_opt;
+use clippy_utils::source::SpanRangeExt;
 use rustc_ast::{MetaItemKind, NestedMetaItem};
 use rustc_errors::Applicability;
 use rustc_lint::EarlyContext;
@@ -29,8 +29,13 @@ fn check_nested_cfg(cx: &EarlyContext<'_>, items: &[NestedMetaItem]) {
                         meta.span,
                         "unneeded sub `cfg` when there is only one condition",
                         |diag| {
-                            if let Some(snippet) = snippet_opt(cx, list[0].span()) {
-                                diag.span_suggestion(meta.span, "try", snippet, Applicability::MaybeIncorrect);
+                            if let Some(snippet) = list[0].span().get_source_text(cx) {
+                                diag.span_suggestion(
+                                    meta.span,
+                                    "try",
+                                    snippet.to_owned(),
+                                    Applicability::MaybeIncorrect,
+                                );
                             }
                         },
                     );
diff --git a/clippy_lints/src/attrs/unnecessary_clippy_cfg.rs b/clippy_lints/src/attrs/unnecessary_clippy_cfg.rs
index 486e7c6ec4f..478ba7a187b 100644
--- a/clippy_lints/src/attrs/unnecessary_clippy_cfg.rs
+++ b/clippy_lints/src/attrs/unnecessary_clippy_cfg.rs
@@ -1,6 +1,7 @@
 use super::{Attribute, UNNECESSARY_CLIPPY_CFG};
 use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_sugg};
-use clippy_utils::source::snippet_opt;
+use clippy_utils::source::SpanRangeExt;
+use itertools::Itertools;
 use rustc_ast::AttrStyle;
 use rustc_errors::Applicability;
 use rustc_lint::{EarlyContext, Level};
@@ -31,7 +32,7 @@ pub(super) fn check(
             return;
         }
         if nb_items == clippy_lints.len() {
-            if let Some(snippet) = snippet_opt(cx, behind_cfg_attr.span) {
+            if let Some(snippet) = behind_cfg_attr.span.get_source_text(cx) {
                 span_lint_and_sugg(
                     cx,
                     UNNECESSARY_CLIPPY_CFG,
@@ -47,11 +48,7 @@ pub(super) fn check(
                 );
             }
         } else {
-            let snippet = clippy_lints
-                .iter()
-                .filter_map(|sp| snippet_opt(cx, *sp))
-                .collect::<Vec<_>>()
-                .join(",");
+            let snippet = clippy_lints.iter().filter_map(|sp| sp.get_source_text(cx)).join(",");
             span_lint_and_note(
                 cx,
                 UNNECESSARY_CLIPPY_CFG,
diff --git a/clippy_lints/src/attrs/useless_attribute.rs b/clippy_lints/src/attrs/useless_attribute.rs
index f0868231d01..67ba605a59f 100644
--- a/clippy_lints/src/attrs/useless_attribute.rs
+++ b/clippy_lints/src/attrs/useless_attribute.rs
@@ -1,7 +1,7 @@
 use super::utils::{extract_clippy_lint, is_lint_level, is_word};
 use super::{Attribute, USELESS_ATTRIBUTE};
 use clippy_utils::diagnostics::span_lint_and_then;
-use clippy_utils::source::{first_line_of_span, snippet_opt};
+use clippy_utils::source::{first_line_of_span, SpanRangeExt};
 use rustc_ast::NestedMetaItem;
 use rustc_errors::Applicability;
 use rustc_hir::{Item, ItemKind};
@@ -69,14 +69,14 @@ pub(super) fn check(cx: &LateContext<'_>, item: &Item<'_>, attrs: &[Attribute])
                 }
                 let line_span = first_line_of_span(cx, attr.span);
 
-                if let Some(mut sugg) = snippet_opt(cx, line_span) {
-                    if sugg.contains("#[") {
+                if let Some(src) = line_span.get_source_text(cx) {
+                    if src.contains("#[") {
+                        #[expect(clippy::collapsible_span_lint_calls)]
                         span_lint_and_then(cx, USELESS_ATTRIBUTE, line_span, "useless lint attribute", |diag| {
-                            sugg = sugg.replacen("#[", "#![", 1);
                             diag.span_suggestion(
                                 line_span,
                                 "if you just forgot a `!`, use",
-                                sugg,
+                                src.replacen("#[", "#![", 1),
                                 Applicability::MaybeIncorrect,
                             );
                         });
diff --git a/clippy_lints/src/casts/as_ptr_cast_mut.rs b/clippy_lints/src/casts/as_ptr_cast_mut.rs
index f05fd3fcde5..15ecba20a0b 100644
--- a/clippy_lints/src/casts/as_ptr_cast_mut.rs
+++ b/clippy_lints/src/casts/as_ptr_cast_mut.rs
@@ -1,5 +1,5 @@
 use clippy_utils::diagnostics::span_lint_and_sugg;
-use clippy_utils::source::snippet_opt;
+use clippy_utils::source::SpanRangeExt;
 use rustc_errors::Applicability;
 use rustc_hir::{Expr, ExprKind};
 use rustc_lint::LateContext;
@@ -19,7 +19,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>,
         && let as_ptr_sig = cx.tcx.fn_sig(as_ptr_did).instantiate_identity()
         && let Some(first_param_ty) = as_ptr_sig.skip_binder().inputs().iter().next()
         && let ty::Ref(_, _, Mutability::Not) = first_param_ty.kind()
-        && let Some(recv) = snippet_opt(cx, receiver.span)
+        && let Some(recv) = receiver.span.get_source_text(cx)
     {
         // `as_mut_ptr` might not exist
         let applicability = Applicability::MaybeIncorrect;
diff --git a/clippy_lints/src/manual_async_fn.rs b/clippy_lints/src/manual_async_fn.rs
index 25c7e5d38b3..61723aec590 100644
--- a/clippy_lints/src/manual_async_fn.rs
+++ b/clippy_lints/src/manual_async_fn.rs
@@ -1,5 +1,5 @@
 use clippy_utils::diagnostics::span_lint_and_then;
-use clippy_utils::source::{position_before_rarrow, snippet_block, snippet_opt};
+use clippy_utils::source::{position_before_rarrow, snippet_block, SpanRangeExt};
 use rustc_errors::Applicability;
 use rustc_hir::intravisit::FnKind;
 use rustc_hir::{
@@ -68,8 +68,8 @@ impl<'tcx> LateLintPass<'tcx> for ManualAsyncFn {
                 header_span,
                 "this function can be simplified using the `async fn` syntax",
                 |diag| {
-                    if let Some(vis_snip) = snippet_opt(cx, *vis_span)
-                        && let Some(header_snip) = snippet_opt(cx, header_span)
+                    if let Some(vis_snip) = vis_span.get_source_text(cx)
+                        && let Some(header_snip) = header_span.get_source_text(cx)
                         && let Some(ret_pos) = position_before_rarrow(&header_snip)
                         && let Some((ret_sugg, ret_snip)) = suggested_ret(cx, output)
                     {
@@ -190,6 +190,6 @@ fn suggested_ret(cx: &LateContext<'_>, output: &Ty<'_>) -> Option<(&'static str,
         Some((sugg, String::new()))
     } else {
         let sugg = "return the output of the future directly";
-        snippet_opt(cx, output.span).map(|snip| (sugg, format!(" -> {snip}")))
+        output.span.get_source_text(cx).map(|src| (sugg, format!(" -> {src}")))
     }
 }
diff --git a/clippy_lints/src/matches/single_match.rs b/clippy_lints/src/matches/single_match.rs
index 24dea03601c..b6930f7b9d1 100644
--- a/clippy_lints/src/matches/single_match.rs
+++ b/clippy_lints/src/matches/single_match.rs
@@ -22,7 +22,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) = span.get_source_text(cx)
+    if let Some(ff) = span.get_source_range(cx)
         && let Some(text) = ff.as_str()
     {
         text.as_bytes().windows(2).any(|w| w == b"//" || w == b"/*")
diff --git a/clippy_lints/src/needless_pass_by_value.rs b/clippy_lints/src/needless_pass_by_value.rs
index a0bbf6b14b2..addb4b1aee8 100644
--- a/clippy_lints/src/needless_pass_by_value.rs
+++ b/clippy_lints/src/needless_pass_by_value.rs
@@ -1,7 +1,7 @@
 use clippy_utils::diagnostics::span_lint_and_then;
 use clippy_utils::is_self;
 use clippy_utils::ptr::get_spans;
-use clippy_utils::source::{snippet, snippet_opt};
+use clippy_utils::source::{snippet, SpanRangeExt};
 use clippy_utils::ty::{
     implements_trait, implements_trait_with_env_from_iter, is_copy, is_type_diagnostic_item, is_type_lang_item,
 };
@@ -242,8 +242,8 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue {
                         for (span, suggestion) in clone_spans {
                             diag.span_suggestion(
                                 span,
-                                snippet_opt(cx, span)
-                                    .map_or("change the call to".into(), |x| format!("change `{x}` to")),
+                                span.get_source_text(cx)
+                                    .map_or("change the call to".to_owned(), |src| format!("change `{src}` to")),
                                 suggestion,
                                 Applicability::Unspecified,
                             );
@@ -267,8 +267,8 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue {
                             for (span, suggestion) in clone_spans {
                                 diag.span_suggestion(
                                     span,
-                                    snippet_opt(cx, span)
-                                        .map_or("change the call to".into(), |x| format!("change `{x}` to")),
+                                    span.get_source_text(cx)
+                                        .map_or("change the call to".to_owned(), |src| format!("change `{src}` to")),
                                     suggestion,
                                     Applicability::Unspecified,
                                 );
diff --git a/clippy_lints/src/pathbuf_init_then_push.rs b/clippy_lints/src/pathbuf_init_then_push.rs
index 18bfb588a11..d7fa48c1e38 100644
--- a/clippy_lints/src/pathbuf_init_then_push.rs
+++ b/clippy_lints/src/pathbuf_init_then_push.rs
@@ -1,6 +1,6 @@
 use clippy_utils::diagnostics::span_lint_and_sugg;
 use clippy_utils::path_to_local_id;
-use clippy_utils::source::{snippet, snippet_opt};
+use clippy_utils::source::{snippet, SpanRangeExt};
 use clippy_utils::ty::is_type_diagnostic_item;
 use rustc_ast::{LitKind, StrStyle};
 use rustc_errors::Applicability;
@@ -74,7 +74,7 @@ impl<'tcx> PathbufPushSearcher<'tcx> {
             && let Some(arg) = self.arg
             && let ExprKind::Lit(x) = arg.kind
             && let LitKind::Str(_, StrStyle::Cooked) = x.node
-            && let Some(s) = snippet_opt(cx, arg.span)
+            && let Some(s) = arg.span.get_source_text(cx)
         {
             Some(format!(" = PathBuf::from({s});"))
         } else {
@@ -84,8 +84,8 @@ impl<'tcx> PathbufPushSearcher<'tcx> {
 
     fn gen_pathbuf_join(&self, cx: &LateContext<'_>) -> Option<String> {
         let arg = self.arg?;
-        let arg_str = snippet_opt(cx, arg.span)?;
-        let init_val = snippet_opt(cx, self.init_val.span)?;
+        let arg_str = arg.span.get_source_text(cx)?;
+        let init_val = self.init_val.span.get_source_text(cx)?;
         Some(format!(" = {init_val}.join({arg_str});"))
     }
 
diff --git a/clippy_lints/src/ptr.rs b/clippy_lints/src/ptr.rs
index 02c05e0aaf9..1e824681fa1 100644
--- a/clippy_lints/src/ptr.rs
+++ b/clippy_lints/src/ptr.rs
@@ -1,7 +1,7 @@
 //! Checks for usage of  `&Vec[_]` and `&String`.
 
 use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then, span_lint_hir_and_then};
-use clippy_utils::source::snippet_opt;
+use clippy_utils::source::SpanRangeExt;
 use clippy_utils::ty::expr_sig;
 use clippy_utils::visitors::contains_unsafe_block;
 use clippy_utils::{get_expr_use_or_unification_node, is_lint_allowed, path_def_id, path_to_local};
@@ -243,7 +243,7 @@ impl<'tcx> LateLintPass<'tcx> for Ptr {
                         .chain(result.replacements.iter().map(|r| {
                             (
                                 r.expr_span,
-                                format!("{}{}", snippet_opt(cx, r.self_span).unwrap(), r.replacement),
+                                format!("{}{}", r.self_span.get_source_text(cx).unwrap(), r.replacement),
                             )
                         }))
                         .collect(),
@@ -372,7 +372,7 @@ impl fmt::Display for DerefTyDisplay<'_, '_> {
             DerefTy::Path => f.write_str("Path"),
             DerefTy::Slice(hir_ty, ty) => {
                 f.write_char('[')?;
-                match hir_ty.and_then(|s| snippet_opt(self.0, s)) {
+                match hir_ty.and_then(|s| s.get_source_text(self.0)) {
                     Some(s) => f.write_str(&s)?,
                     None => ty.fmt(f)?,
                 }
@@ -413,6 +413,7 @@ impl<'tcx> DerefTy<'tcx> {
     }
 }
 
+#[expect(clippy::too_many_lines)]
 fn check_fn_args<'cx, 'tcx: 'cx>(
     cx: &'cx LateContext<'tcx>,
     fn_sig: ty::FnSig<'tcx>,
@@ -488,8 +489,6 @@ fn check_fn_args<'cx, 'tcx: 'cx>(
                                 return None;
                             }
 
-                            let ty_name = snippet_opt(cx, ty.span()).unwrap_or_else(|| args.type_at(1).to_string());
-
                             span_lint_hir_and_then(
                                 cx,
                                 PTR_ARG,
@@ -500,7 +499,10 @@ fn check_fn_args<'cx, 'tcx: 'cx>(
                                     diag.span_suggestion(
                                         hir_ty.span,
                                         "change this to",
-                                        format!("&{}{ty_name}", mutability.prefix_str()),
+                                        match ty.span().get_source_text(cx) {
+                                            Some(s) => format!("&{}{s}", mutability.prefix_str()),
+                                            None => format!("&{}{}", mutability.prefix_str(), args.type_at(1)),
+                                        },
                                         Applicability::Unspecified,
                                     );
                                 },
diff --git a/clippy_lints/src/ptr_offset_with_cast.rs b/clippy_lints/src/ptr_offset_with_cast.rs
index 7c82895d609..87a52cb2186 100644
--- a/clippy_lints/src/ptr_offset_with_cast.rs
+++ b/clippy_lints/src/ptr_offset_with_cast.rs
@@ -1,5 +1,5 @@
 use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
-use clippy_utils::source::snippet_opt;
+use clippy_utils::source::SpanRangeExt;
 use rustc_errors::Applicability;
 use rustc_hir::{Expr, ExprKind};
 use rustc_lint::{LateContext, LateLintPass};
@@ -120,8 +120,8 @@ fn build_suggestion(
     receiver_expr: &Expr<'_>,
     cast_lhs_expr: &Expr<'_>,
 ) -> Option<String> {
-    let receiver = snippet_opt(cx, receiver_expr.span)?;
-    let cast_lhs = snippet_opt(cx, cast_lhs_expr.span)?;
+    let receiver = receiver_expr.span.get_source_text(cx)?;
+    let cast_lhs = cast_lhs_expr.span.get_source_text(cx)?;
     Some(format!("{receiver}.{}({cast_lhs})", method.suggestion()))
 }
 
diff --git a/clippy_lints/src/redundant_clone.rs b/clippy_lints/src/redundant_clone.rs
index a1231c082e6..bfdc1cbeed7 100644
--- a/clippy_lints/src/redundant_clone.rs
+++ b/clippy_lints/src/redundant_clone.rs
@@ -1,6 +1,6 @@
 use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then};
 use clippy_utils::mir::{visit_local_usage, LocalUsage, PossibleBorrowerMap};
-use clippy_utils::source::snippet_opt;
+use clippy_utils::source::SpanRangeExt;
 use clippy_utils::ty::{has_drop, is_copy, is_type_diagnostic_item, is_type_lang_item, walk_ptrs_ty_depth};
 use clippy_utils::{fn_has_unsatisfiable_preds, match_def_path, paths};
 use rustc_errors::Applicability;
@@ -208,7 +208,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClone {
                 .assert_crate_local()
                 .lint_root;
 
-            if let Some(snip) = snippet_opt(cx, span)
+            if let Some(snip) = span.get_source_text(cx)
                 && let Some(dot) = snip.rfind('.')
             {
                 let sugg_span = span.with_lo(span.lo() + BytePos(u32::try_from(dot).unwrap()));
diff --git a/clippy_lints/src/reference.rs b/clippy_lints/src/reference.rs
index 8f32cf5f2a1..2b4ef21fc48 100644
--- a/clippy_lints/src/reference.rs
+++ b/clippy_lints/src/reference.rs
@@ -1,10 +1,10 @@
 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 rustc_ast::ast::{Expr, ExprKind, Mutability, UnOp};
 use rustc_errors::Applicability;
 use rustc_lint::{EarlyContext, EarlyLintPass};
 use rustc_session::declare_lint_pass;
-use rustc_span::BytePos;
+use rustc_span::{BytePos, Span};
 
 declare_clippy_lint! {
     /// ### What it does
@@ -56,11 +56,11 @@ impl EarlyLintPass for DerefAddrOf {
         {
             let mut applicability = Applicability::MachineApplicable;
             let sugg = if e.span.from_expansion() {
-                if let Some(macro_source) = snippet_opt(cx, e.span) {
+                if let Some(macro_source) = e.span.get_source_text(cx) {
                     // Remove leading whitespace from the given span
                     // e.g: ` $visitor` turns into `$visitor`
-                    let trim_leading_whitespaces = |span| {
-                        snippet_opt(cx, span)
+                    let trim_leading_whitespaces = |span: Span| {
+                        span.get_source_text(cx)
                             .and_then(|snip| {
                                 #[expect(clippy::cast_possible_truncation)]
                                 snip.find(|c: char| !c.is_whitespace())
diff --git a/clippy_lints/src/regex.rs b/clippy_lints/src/regex.rs
index 95014b23043..f6ef02b7c23 100644
--- a/clippy_lints/src/regex.rs
+++ b/clippy_lints/src/regex.rs
@@ -2,7 +2,7 @@ use std::fmt::Display;
 
 use clippy_utils::consts::{ConstEvalCtxt, Constant};
 use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
-use clippy_utils::source::snippet_opt;
+use clippy_utils::source::SpanRangeExt;
 use clippy_utils::{def_path_def_ids, path_def_id, paths};
 use rustc_ast::ast::{LitKind, StrStyle};
 use rustc_hir::def_id::DefIdMap;
@@ -122,7 +122,7 @@ fn lint_syntax_error(cx: &LateContext<'_>, error: &regex_syntax::Error, unescape
     };
 
     if let Some((primary, auxiliary, kind)) = parts
-        && let Some(literal_snippet) = snippet_opt(cx, base)
+        && let Some(literal_snippet) = base.get_source_text(cx)
         && let Some(inner) = literal_snippet.get(offset as usize..)
         // Only convert to native rustc spans if the parsed regex matches the
         // source snippet exactly, to ensure the span offsets are correct
diff --git a/clippy_lints/src/returns.rs b/clippy_lints/src/returns.rs
index 13016cdadb0..ffc9475cf9c 100644
--- a/clippy_lints/src/returns.rs
+++ b/clippy_lints/src/returns.rs
@@ -1,5 +1,5 @@
 use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
-use clippy_utils::source::{snippet_opt, snippet_with_context};
+use clippy_utils::source::{snippet_with_context, SpanRangeExt};
 use clippy_utils::sugg::has_enclosing_paren;
 use clippy_utils::visitors::{for_each_expr, Descend};
 use clippy_utils::{
@@ -250,20 +250,25 @@ impl<'tcx> LateLintPass<'tcx> for Return {
                 |err| {
                     err.span_label(local.span, "unnecessary `let` binding");
 
-                    if let Some(mut snippet) = snippet_opt(cx, initexpr.span) {
-                        if binary_expr_needs_parentheses(initexpr) {
-                            if !has_enclosing_paren(&snippet) {
-                                snippet = format!("({snippet})");
+                    if let Some(src) = initexpr.span.get_source_text(cx) {
+                        let sugg = if binary_expr_needs_parentheses(initexpr) {
+                            if has_enclosing_paren(&src) {
+                                src.to_owned()
+                            } else {
+                                format!("({src})")
                             }
                         } else if !cx.typeck_results().expr_adjustments(retexpr).is_empty() {
-                            if !has_enclosing_paren(&snippet) {
-                                snippet = format!("({snippet})");
+                            if has_enclosing_paren(&src) {
+                                format!("{src} as _")
+                            } else {
+                                format!("({src}) as _")
                             }
-                            snippet.push_str(" as _");
-                        }
+                        } else {
+                            src.to_owned()
+                        };
                         err.multipart_suggestion(
                             "return the expression directly",
-                            vec![(local.span, String::new()), (retexpr.span, snippet)],
+                            vec![(local.span, String::new()), (retexpr.span, sugg)],
                             Applicability::MachineApplicable,
                         );
                     } else {
diff --git a/clippy_lints/src/single_range_in_vec_init.rs b/clippy_lints/src/single_range_in_vec_init.rs
index e0558429638..44e585953bf 100644
--- a/clippy_lints/src/single_range_in_vec_init.rs
+++ b/clippy_lints/src/single_range_in_vec_init.rs
@@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_then;
 use clippy_utils::get_trait_def_id;
 use clippy_utils::higher::VecArgs;
 use clippy_utils::macros::root_macro_call_first_node;
-use clippy_utils::source::snippet_opt;
+use clippy_utils::source::SpanRangeExt;
 use clippy_utils::ty::implements_trait;
 use rustc_ast::{LitIntType, LitKind, UintTy};
 use rustc_errors::Applicability;
@@ -92,12 +92,12 @@ impl LateLintPass<'_> for SingleRangeInVecInit {
 
         if matches!(lang_item, LangItem::Range)
             && let ty = cx.typeck_results().expr_ty(start.expr)
-            && let Some(snippet) = snippet_opt(cx, span)
+            && let Some(snippet) = span.get_source_text(cx)
             // `is_from_proc_macro` will skip any `vec![]`. Let's not!
             && snippet.starts_with(suggested_type.starts_with())
             && snippet.ends_with(suggested_type.ends_with())
-            && let Some(start_snippet) = snippet_opt(cx, start.span)
-            && let Some(end_snippet) = snippet_opt(cx, end.span)
+            && let Some(start_snippet) = start.span.get_source_text(cx)
+            && let Some(end_snippet) = end.span.get_source_text(cx)
         {
             let should_emit_every_value = if let Some(step_def_id) = get_trait_def_id(cx.tcx, &["core", "iter", "Step"])
                 && implements_trait(cx, ty, step_def_id, &[])
diff --git a/clippy_lints/src/trait_bounds.rs b/clippy_lints/src/trait_bounds.rs
index 59b74122f30..2e87d36df31 100644
--- a/clippy_lints/src/trait_bounds.rs
+++ b/clippy_lints/src/trait_bounds.rs
@@ -1,7 +1,7 @@
 use clippy_config::msrvs::{self, Msrv};
 use clippy_config::Conf;
 use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
-use clippy_utils::source::{snippet, snippet_opt, snippet_with_applicability};
+use clippy_utils::source::{snippet, snippet_with_applicability, SpanRangeExt};
 use clippy_utils::{is_from_proc_macro, SpanlessEq, SpanlessHash};
 use core::hash::{Hash, Hasher};
 use itertools::Itertools;
@@ -206,8 +206,7 @@ impl<'tcx> LateLintPass<'tcx> for TraitBounds {
 
                 let fixed_trait_snippet = unique_traits
                     .iter()
-                    .filter_map(|b| snippet_opt(cx, b.span))
-                    .collect::<Vec<_>>()
+                    .filter_map(|b| b.span.get_source_text(cx))
                     .join(" + ");
 
                 span_lint_and_sugg(
@@ -462,9 +461,8 @@ fn rollup_traits(
 
         let traits = comparable_bounds
             .iter()
-            .filter_map(|&(_, span)| snippet_opt(cx, span))
-            .collect::<Vec<_>>();
-        let traits = traits.join(" + ");
+            .filter_map(|&(_, span)| span.get_source_text(cx))
+            .join(" + ");
 
         span_lint_and_sugg(
             cx,
diff --git a/clippy_lints/src/unused_unit.rs b/clippy_lints/src/unused_unit.rs
index b70aa768b46..65f431d338b 100644
--- a/clippy_lints/src/unused_unit.rs
+++ b/clippy_lints/src/unused_unit.rs
@@ -1,5 +1,5 @@
 use clippy_utils::diagnostics::span_lint_and_sugg;
-use clippy_utils::source::{position_before_rarrow, snippet_opt};
+use clippy_utils::source::{position_before_rarrow, SpanRangeExt};
 use rustc_ast::visit::FnKind;
 use rustc_ast::{ast, ClosureBinder};
 use rustc_errors::Applicability;
@@ -128,15 +128,17 @@ fn is_unit_expr(expr: &ast::Expr) -> bool {
 
 fn lint_unneeded_unit_return(cx: &EarlyContext<'_>, ty: &ast::Ty, span: Span) {
     let (ret_span, appl) =
-        snippet_opt(cx, span.with_hi(ty.span.hi())).map_or((ty.span, Applicability::MaybeIncorrect), |fn_source| {
-            position_before_rarrow(&fn_source).map_or((ty.span, Applicability::MaybeIncorrect), |rpos| {
-                (
-                    #[expect(clippy::cast_possible_truncation)]
-                    ty.span.with_lo(BytePos(span.lo().0 + rpos as u32)),
-                    Applicability::MachineApplicable,
-                )
-            })
-        });
+        span.with_hi(ty.span.hi())
+            .get_source_text(cx)
+            .map_or((ty.span, Applicability::MaybeIncorrect), |src| {
+                position_before_rarrow(&src).map_or((ty.span, Applicability::MaybeIncorrect), |rpos| {
+                    (
+                        #[expect(clippy::cast_possible_truncation)]
+                        ty.span.with_lo(BytePos(span.lo().0 + rpos as u32)),
+                        Applicability::MachineApplicable,
+                    )
+                })
+            });
     span_lint_and_sugg(
         cx,
         UNUSED_UNIT,
diff --git a/clippy_lints/src/visibility.rs b/clippy_lints/src/visibility.rs
index 63f3a5d7f83..7a85196ceca 100644
--- a/clippy_lints/src/visibility.rs
+++ b/clippy_lints/src/visibility.rs
@@ -1,5 +1,5 @@
 use clippy_utils::diagnostics::span_lint_and_then;
-use clippy_utils::source::snippet_opt;
+use clippy_utils::source::SpanRangeExt;
 use rustc_ast::ast::{Item, VisibilityKind};
 use rustc_errors::Applicability;
 use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
@@ -82,9 +82,7 @@ impl EarlyLintPass for Visibility {
         if !in_external_macro(cx.sess(), item.span)
             && let VisibilityKind::Restricted { path, shorthand, .. } = &item.vis.kind
         {
-            if **path == kw::SelfLower
-                && let Some(false) = is_from_proc_macro(cx, item.vis.span)
-            {
+            if **path == kw::SelfLower && !is_from_proc_macro(cx, item.vis.span) {
                 span_lint_and_then(
                     cx,
                     NEEDLESS_PUB_SELF,
@@ -104,7 +102,7 @@ impl EarlyLintPass for Visibility {
             if (**path == kw::Super || **path == kw::SelfLower || **path == kw::Crate)
                 && !*shorthand
                 && let [.., last] = &*path.segments
-                && let Some(false) = is_from_proc_macro(cx, item.vis.span)
+                && !is_from_proc_macro(cx, item.vis.span)
             {
                 #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
                 span_lint_and_then(
@@ -125,7 +123,7 @@ impl EarlyLintPass for Visibility {
 
             if *shorthand
                 && let [.., last] = &*path.segments
-                && let Some(false) = is_from_proc_macro(cx, item.vis.span)
+                && !is_from_proc_macro(cx, item.vis.span)
             {
                 #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
                 span_lint_and_then(
@@ -147,6 +145,6 @@ impl EarlyLintPass for Visibility {
     }
 }
 
-fn is_from_proc_macro(cx: &EarlyContext<'_>, span: Span) -> Option<bool> {
-    snippet_opt(cx, span).map(|s| !s.starts_with("pub"))
+fn is_from_proc_macro(cx: &EarlyContext<'_>, span: Span) -> bool {
+    !span.check_source_text(cx, |src| src.starts_with("pub"))
 }
diff --git a/clippy_lints/src/write.rs b/clippy_lints/src/write.rs
index 54e7e92f0c4..ec5a5896fb2 100644
--- a/clippy_lints/src/write.rs
+++ b/clippy_lints/src/write.rs
@@ -2,7 +2,7 @@ use clippy_config::Conf;
 use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
 use clippy_utils::is_in_test;
 use clippy_utils::macros::{format_arg_removal_span, root_macro_call_first_node, FormatArgsStorage, MacroCall};
-use clippy_utils::source::{expand_past_previous_comma, snippet_opt};
+use clippy_utils::source::{expand_past_previous_comma, SpanRangeExt};
 use rustc_ast::token::LitKind;
 use rustc_ast::{
     FormatArgPosition, FormatArgPositionKind, FormatArgs, FormatArgsPiece, FormatOptions, FormatPlaceholder,
@@ -397,7 +397,7 @@ fn check_newline(cx: &LateContext<'_>, format_args: &FormatArgs, macro_call: &Ma
             format!("using `{name}!()` with a format string that ends in a single newline"),
             |diag| {
                 let name_span = cx.sess().source_map().span_until_char(macro_call.span, '!');
-                let Some(format_snippet) = snippet_opt(cx, format_string_span) else {
+                let Some(format_snippet) = format_string_span.get_source_text(cx) else {
                     return;
                 };
 
@@ -492,7 +492,7 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) {
             && let Some(arg) = format_args.arguments.by_index(index)
             && let rustc_ast::ExprKind::Lit(lit) = &arg.expr.kind
             && !arg.expr.span.from_expansion()
-            && let Some(value_string) = snippet_opt(cx, arg.expr.span)
+            && let Some(value_string) = arg.expr.span.get_source_text(cx)
         {
             let (replacement, replace_raw) = match lit.kind {
                 LitKind::Str | LitKind::StrRaw(_) => match extract_str_literal(&value_string) {
@@ -515,7 +515,7 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) {
                 _ => continue,
             };
 
-            let Some(format_string_snippet) = snippet_opt(cx, format_args.span) else {
+            let Some(format_string_snippet) = format_args.span.get_source_text(cx) else {
                 continue;
             };
             let format_string_is_raw = format_string_snippet.starts_with('r');
diff --git a/clippy_utils/src/consts.rs b/clippy_utils/src/consts.rs
index e907e4058e5..760d5bc95f7 100644
--- a/clippy_utils/src/consts.rs
+++ b/clippy_utils/src/consts.rs
@@ -687,7 +687,7 @@ impl<'tcx> ConstEvalCtxt<'tcx> {
                 if let Some(expr_span) = walk_span_to_context(expr.span, span.ctxt)
                     && let expr_lo = expr_span.lo()
                     && expr_lo >= span.lo
-                    && let Some(src) = (span.lo..expr_lo).get_source_text(&self.tcx)
+                    && let Some(src) = (span.lo..expr_lo).get_source_range(&self.tcx)
                     && let Some(src) = src.as_str()
                 {
                     use rustc_lexer::TokenKind::{BlockComment, LineComment, OpenBrace, Semi, Whitespace};
diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs
index f325e4eaf15..b0a668329e9 100644
--- a/clippy_utils/src/hir_utils.rs
+++ b/clippy_utils/src/hir_utils.rs
@@ -1191,9 +1191,9 @@ fn eq_span_tokens(
     pred: impl Fn(TokenKind) -> bool,
 ) -> bool {
     fn f(cx: &LateContext<'_>, left: Range<BytePos>, right: Range<BytePos>, pred: impl Fn(TokenKind) -> bool) -> bool {
-        if let Some(lsrc) = left.get_source_text(cx)
+        if let Some(lsrc) = left.get_source_range(cx)
             && let Some(lsrc) = lsrc.as_str()
-            && let Some(rsrc) = right.get_source_text(cx)
+            && let Some(rsrc) = right.get_source_range(cx)
             && let Some(rsrc) = rsrc.as_str()
         {
             let pred = |t: &(_, _)| pred(t.0);
diff --git a/clippy_utils/src/source.rs b/clippy_utils/src/source.rs
index 96dd3c55d37..8b49b013e03 100644
--- a/clippy_utils/src/source.rs
+++ b/clippy_utils/src/source.rs
@@ -16,7 +16,7 @@ use rustc_span::{
 };
 use std::borrow::Cow;
 use std::fmt;
-use std::ops::Range;
+use std::ops::{Deref, Index, Range};
 
 pub trait HasSession {
     fn sess(&self) -> &Session;
@@ -94,10 +94,16 @@ impl IntoSpan for Range<BytePos> {
 }
 
 pub trait SpanRangeExt: SpanRange {
+    /// Attempts to get a handle to the source text. Returns `None` if either the span is malformed,
+    /// or the source text is not accessible.
+    fn get_source_text(self, cx: &impl HasSession) -> Option<SourceText> {
+        get_source_range(cx.sess().source_map(), self.into_range()).and_then(SourceText::new)
+    }
+
     /// Gets the source file, and range in the file, of the given span. Returns `None` if the span
     /// extends through multiple files, or is malformed.
-    fn get_source_text(self, cx: &impl HasSession) -> Option<SourceFileRange> {
-        get_source_text(cx.sess().source_map(), self.into_range())
+    fn get_source_range(self, cx: &impl HasSession) -> Option<SourceFileRange> {
+        get_source_range(cx.sess().source_map(), self.into_range())
     }
 
     /// Calls the given function with the source text referenced and returns the value. Returns
@@ -144,21 +150,58 @@ pub trait SpanRangeExt: SpanRange {
     fn trim_start(self, cx: &impl HasSession) -> Range<BytePos> {
         trim_start(cx.sess().source_map(), self.into_range())
     }
+}
+impl<T: SpanRange> SpanRangeExt for T {}
+
+/// Handle to a range of text in a source file.
+pub struct SourceText(SourceFileRange);
+impl SourceText {
+    /// Takes ownership of the source file handle if the source text is accessible.
+    pub fn new(text: SourceFileRange) -> Option<Self> {
+        if text.as_str().is_some() {
+            Some(Self(text))
+        } else {
+            None
+        }
+    }
 
-    /// Writes the referenced source text to the given writer. Will return `Err` if the source text
-    /// could not be retrieved.
-    fn write_source_text_to(self, cx: &impl HasSession, dst: &mut impl fmt::Write) -> fmt::Result {
-        write_source_text_to(cx.sess().source_map(), self.into_range(), dst)
+    /// Gets the source text.
+    pub fn as_str(&self) -> &str {
+        self.0.as_str().unwrap()
     }
 
-    /// Extracts the referenced source text as an owned string.
-    fn source_text_to_string(self, cx: &impl HasSession) -> Option<String> {
-        self.with_source_text(cx, ToOwned::to_owned)
+    /// Converts this into an owned string.
+    pub fn to_owned(&self) -> String {
+        self.as_str().to_owned()
+    }
+}
+impl Deref for SourceText {
+    type Target = str;
+    fn deref(&self) -> &Self::Target {
+        self.as_str()
+    }
+}
+impl AsRef<str> for SourceText {
+    fn as_ref(&self) -> &str {
+        self.as_str()
+    }
+}
+impl<T> Index<T> for SourceText
+where
+    str: Index<T>,
+{
+    type Output = <str as Index<T>>::Output;
+    fn index(&self, idx: T) -> &Self::Output {
+        &self.as_str()[idx]
+    }
+}
+impl fmt::Display for SourceText {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        self.as_str().fmt(f)
     }
 }
-impl<T: SpanRange> SpanRangeExt for T {}
 
-fn get_source_text(sm: &SourceMap, sp: Range<BytePos>) -> Option<SourceFileRange> {
+fn get_source_range(sm: &SourceMap, sp: Range<BytePos>) -> Option<SourceFileRange> {
     let start = sm.lookup_byte_offset(sp.start);
     let end = sm.lookup_byte_offset(sp.end);
     if !Lrc::ptr_eq(&start.sf, &end.sf) || start.pos > end.pos {
@@ -169,7 +212,7 @@ fn get_source_text(sm: &SourceMap, sp: Range<BytePos>) -> Option<SourceFileRange
 }
 
 fn with_source_text<T>(sm: &SourceMap, sp: Range<BytePos>, f: impl for<'a> FnOnce(&'a str) -> T) -> Option<T> {
-    if let Some(src) = get_source_text(sm, sp)
+    if let Some(src) = get_source_range(sm, sp)
         && let Some(src) = src.as_str()
     {
         Some(f(src))
@@ -183,7 +226,7 @@ fn with_source_text_and_range<T>(
     sp: Range<BytePos>,
     f: impl for<'a> FnOnce(&'a str, Range<usize>) -> T,
 ) -> Option<T> {
-    if let Some(src) = get_source_text(sm, sp)
+    if let Some(src) = get_source_range(sm, sp)
         && let Some(text) = &src.sf.src
     {
         Some(f(text, src.range))
@@ -198,7 +241,7 @@ fn map_range(
     sp: Range<BytePos>,
     f: impl for<'a> FnOnce(&'a str, Range<usize>) -> Option<Range<usize>>,
 ) -> Option<Range<BytePos>> {
-    if let Some(src) = get_source_text(sm, sp.clone())
+    if let Some(src) = get_source_range(sm, sp.clone())
         && let Some(text) = &src.sf.src
         && let Some(range) = f(text, src.range.clone())
     {
@@ -232,13 +275,6 @@ fn trim_start(sm: &SourceMap, sp: Range<BytePos>) -> Range<BytePos> {
     .unwrap_or(sp)
 }
 
-fn write_source_text_to(sm: &SourceMap, sp: Range<BytePos>, dst: &mut impl fmt::Write) -> fmt::Result {
-    match with_source_text(sm, sp, |src| dst.write_str(src)) {
-        Some(x) => x,
-        None => Err(fmt::Error),
-    }
-}
-
 pub struct SourceFileRange {
     pub sf: Lrc<SourceFile>,
     pub range: Range<usize>,