about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--COPYRIGHT2
-rw-r--r--LICENSE-APACHE2
-rw-r--r--LICENSE-MIT2
-rw-r--r--README.md2
-rw-r--r--book/src/development/macro_expansions.md76
-rw-r--r--book/src/development/the_team.md3
-rw-r--r--clippy_lints/src/declared_lints.rs1
-rw-r--r--clippy_lints/src/doc/doc_comment_double_space_linebreaks.rs33
-rw-r--r--clippy_lints/src/doc/mod.rs53
-rw-r--r--clippy_lints/src/from_over_into.rs14
-rw-r--r--clippy_lints/src/incompatible_msrv.rs26
-rw-r--r--clippy_lints/src/needless_pass_by_value.rs16
-rw-r--r--clippy_lints/src/question_mark.rs43
-rw-r--r--clippy_utils/README.md2
-rw-r--r--clippy_utils/src/lib.rs30
-rw-r--r--rustc_tools_util/README.md2
-rw-r--r--tests/ui/crashes/needless_pass_by_value-w-late-bound.stderr6
-rw-r--r--tests/ui/doc/doc_comment_double_space_linebreaks.fixed98
-rw-r--r--tests/ui/doc/doc_comment_double_space_linebreaks.rs98
-rw-r--r--tests/ui/doc/doc_comment_double_space_linebreaks.stderr50
-rw-r--r--tests/ui/from_over_into.fixed11
-rw-r--r--tests/ui/from_over_into.rs11
-rw-r--r--tests/ui/from_over_into.stderr18
-rw-r--r--tests/ui/incompatible_msrv.rs39
-rw-r--r--tests/ui/incompatible_msrv.stderr37
-rw-r--r--tests/ui/needless_pass_by_value.rs29
-rw-r--r--tests/ui/needless_pass_by_value.stderr168
-rw-r--r--tests/ui/needless_return.fixed12
-rw-r--r--tests/ui/needless_return.rs12
-rw-r--r--tests/ui/needless_return.stderr122
-rw-r--r--tests/ui/question_mark.fixed34
-rw-r--r--tests/ui/question_mark.rs44
-rw-r--r--tests/ui/question_mark.stderr42
34 files changed, 1014 insertions, 125 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 00323da7745..1bf4b51ff0f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5570,6 +5570,7 @@ Released 2018-09-13
 [`disallowed_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_type
 [`disallowed_types`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_types
 [`diverging_sub_expression`]: https://rust-lang.github.io/rust-clippy/master/index.html#diverging_sub_expression
+[`doc_comment_double_space_linebreaks`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_comment_double_space_linebreaks
 [`doc_include_without_cfg`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_include_without_cfg
 [`doc_lazy_continuation`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_lazy_continuation
 [`doc_link_code`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_link_code
diff --git a/COPYRIGHT b/COPYRIGHT
index 219693d63d9..f402dcf465a 100644
--- a/COPYRIGHT
+++ b/COPYRIGHT
@@ -1,6 +1,6 @@
 // REUSE-IgnoreStart
 
-Copyright 2014-2024 The Rust Project Developers
+Copyright 2014-2025 The Rust Project Developers
 
 Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
 http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
diff --git a/LICENSE-APACHE b/LICENSE-APACHE
index 506582c31d6..9990a0cec47 100644
--- a/LICENSE-APACHE
+++ b/LICENSE-APACHE
@@ -186,7 +186,7 @@ APPENDIX: How to apply the Apache License to your work.
    same "printed page" as the copyright notice for easier
    identification within third-party archives.
 
-Copyright 2014-2024 The Rust Project Developers
+Copyright 2014-2025 The Rust Project Developers
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
diff --git a/LICENSE-MIT b/LICENSE-MIT
index 6d8ee9afb61..5d6e36ef6bf 100644
--- a/LICENSE-MIT
+++ b/LICENSE-MIT
@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) 2014-2024 The Rust Project Developers
+Copyright (c) 2014-2025 The Rust Project Developers
 
 Permission is hereby granted, free of charge, to any
 person obtaining a copy of this software and associated
diff --git a/README.md b/README.md
index 32c1d33e2ed..20a5e997e62 100644
--- a/README.md
+++ b/README.md
@@ -277,7 +277,7 @@ If you want to contribute to Clippy, you can find more information in [CONTRIBUT
 
 <!-- REUSE-IgnoreStart -->
 
-Copyright 2014-2024 The Rust Project Developers
+Copyright 2014-2025 The Rust Project Developers
 
 Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
 [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0)> or the MIT license
diff --git a/book/src/development/macro_expansions.md b/book/src/development/macro_expansions.md
index 36092f82e26..ed547130b35 100644
--- a/book/src/development/macro_expansions.md
+++ b/book/src/development/macro_expansions.md
@@ -150,9 +150,85 @@ if foo_span.in_external_macro(cx.sess().source_map()) {
 }
 ```
 
+### The `is_from_proc_macro` function
+A common point of confusion is the existence of [`is_from_proc_macro`]
+and how it differs from the other [`in_external_macro`]/[`from_expansion`] functions.
+
+While [`in_external_macro`] and [`from_expansion`] both work perfectly fine for detecting expanded code
+from *declarative* macros (i.e. `macro_rules!` and macros 2.0),
+detecting *proc macro*-generated code is a bit more tricky, as proc macros can (and often do)
+freely manipulate the span of returned tokens.
+
+In practice, this often happens through the use of [`quote::quote_spanned!`] with a span from the input tokens. 
+
+In those cases, there is no *reliable* way for the compiler (and tools like Clippy)
+to distinguish code that comes from such a proc macro from code that the user wrote directly,
+and [`in_external_macro`] will return `false`.
+
+This is usually not an issue for the compiler and actually helps proc macro authors create better error messages,
+as it allows associating parts of the expansion with parts of the macro input and lets the compiler
+point the user to the relevant code in case of a compile error.
+
+However, for Clippy this is inconvenient, because most of the time *we don't* want
+to lint proc macro-generated code and this makes it impossible to tell what is and isn't proc macro code.
+
+> NOTE: this is specifically only an issue when a proc macro explicitly sets the span to that of an **input span**.
+>
+> For example, other common ways of creating `TokenStream`s, such as `"fn foo() {...}".parse::<TokenStream>()`,
+> sets each token's span to `Span::call_site()`, which already marks the span as coming from a proc macro
+> and the usual span methods have no problem detecting that as a macro span.
+
+As such, Clippy has its own `is_from_proc_macro` function which tries to *approximate*
+whether a span comes from a proc macro, by checking whether the source text at the given span
+lines up with the given AST node.
+
+This function is typically used in combination with the other mentioned macro span functions,
+but is usually called much later into the condition chain as it's a bit heavier than most other conditions,
+so that the other cheaper conditions can fail faster. For example, the `borrow_deref_ref` lint:
+```rs
+impl<'tcx> LateLintPass<'tcx> for BorrowDerefRef {
+    fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &rustc_hir::Expr<'tcx>) {
+        if let ... = ...
+            && ...
+            && !e.span.from_expansion()
+            && ...
+            && ...
+            && !is_from_proc_macro(cx, e)
+            && ...
+        {
+            ...
+        }
+    }
+}
+```
+
+### Testing lints with macro expansions
+To test that all of these cases are handled correctly in your lint,
+we have a helper auxiliary crate that exposes various macros, used by tests like so:
+```rust
+//@aux-build:proc_macros.rs
+
+extern crate proc_macros;
+
+fn main() {
+    proc_macros::external!{ code_that_should_trigger_your_lint }
+    proc_macros::with_span!{ span code_that_should_trigger_your_lint }
+}
+```
+This exercises two cases:
+- `proc_macros::external!` is a simple proc macro that echos the input tokens back but with a macro span:
+this represents the usual, common case where an external macro expands to code that your lint would trigger,
+and is correctly handled by `in_external_macro` and `Span::from_expansion`.
+
+- `proc_macros::with_span!` echos back the input tokens starting from the second token
+with the span of the first token: this is where the other functions will fail and `is_from_proc_macro` is needed
+
+
 [`ctxt`]: https://doc.rust-lang.org/stable/nightly-rustc/rustc_span/struct.Span.html#method.ctxt
 [expansion]: https://rustc-dev-guide.rust-lang.org/macro-expansion.html#expansion-and-ast-integration
 [`from_expansion`]: https://doc.rust-lang.org/stable/nightly-rustc/rustc_span/struct.Span.html#method.from_expansion
 [`in_external_macro`]: https://doc.rust-lang.org/stable/nightly-rustc/rustc_span/struct.Span.html#method.in_external_macro
 [Span]: https://doc.rust-lang.org/stable/nightly-rustc/rustc_span/struct.Span.html
 [SyntaxContext]: https://doc.rust-lang.org/stable/nightly-rustc/rustc_span/hygiene/struct.SyntaxContext.html
+[`is_from_proc_macro`]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/fn.is_from_proc_macro.html
+[`quote::quote_spanned!`]: https://docs.rs/quote/latest/quote/macro.quote_spanned.html
diff --git a/book/src/development/the_team.md b/book/src/development/the_team.md
index 6bc0783b166..da5d084ed97 100644
--- a/book/src/development/the_team.md
+++ b/book/src/development/the_team.md
@@ -102,8 +102,7 @@ is responsible for maintaining Clippy.
 
 5. **Update the changelog**
 
-    This needs to be done for every release, every six weeks. This is usually
-    done by @xFrednet.
+    This needs to be done for every release, every six weeks.
 
 ### Membership
 
diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs
index ae779af2a89..7fa23dad698 100644
--- a/clippy_lints/src/declared_lints.rs
+++ b/clippy_lints/src/declared_lints.rs
@@ -137,6 +137,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
     crate::disallowed_names::DISALLOWED_NAMES_INFO,
     crate::disallowed_script_idents::DISALLOWED_SCRIPT_IDENTS_INFO,
     crate::disallowed_types::DISALLOWED_TYPES_INFO,
+    crate::doc::DOC_COMMENT_DOUBLE_SPACE_LINEBREAKS_INFO,
     crate::doc::DOC_INCLUDE_WITHOUT_CFG_INFO,
     crate::doc::DOC_LAZY_CONTINUATION_INFO,
     crate::doc::DOC_LINK_CODE_INFO,
diff --git a/clippy_lints/src/doc/doc_comment_double_space_linebreaks.rs b/clippy_lints/src/doc/doc_comment_double_space_linebreaks.rs
new file mode 100644
index 00000000000..4cc02702233
--- /dev/null
+++ b/clippy_lints/src/doc/doc_comment_double_space_linebreaks.rs
@@ -0,0 +1,33 @@
+use clippy_utils::diagnostics::span_lint_and_then;
+use rustc_errors::Applicability;
+use rustc_lint::LateContext;
+use rustc_span::{BytePos, Span};
+
+use super::DOC_COMMENT_DOUBLE_SPACE_LINEBREAKS;
+
+pub fn check(cx: &LateContext<'_>, collected_breaks: &[Span]) {
+    if collected_breaks.is_empty() {
+        return;
+    }
+
+    let breaks: Vec<_> = collected_breaks
+        .iter()
+        .map(|span| span.with_hi(span.lo() + BytePos(2)))
+        .collect();
+
+    span_lint_and_then(
+        cx,
+        DOC_COMMENT_DOUBLE_SPACE_LINEBREAKS,
+        breaks.clone(),
+        "doc comment uses two spaces for a hard line break",
+        |diag| {
+            let suggs: Vec<_> = breaks.iter().map(|span| (*span, "\\".to_string())).collect();
+            diag.tool_only_multipart_suggestion(
+                "replace this double space with a backslash:",
+                suggs,
+                Applicability::MachineApplicable,
+            );
+            diag.help("replace this double space with a backslash: `\\`");
+        },
+    );
+}
diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs
index c742d6abb5c..36fd396cc1d 100644
--- a/clippy_lints/src/doc/mod.rs
+++ b/clippy_lints/src/doc/mod.rs
@@ -1,12 +1,10 @@
 #![allow(clippy::lint_without_lint_pass)]
 
-mod lazy_continuation;
-mod too_long_first_doc_paragraph;
-
 use clippy_config::Conf;
 use clippy_utils::attrs::is_doc_hidden;
 use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_then};
 use clippy_utils::macros::{is_panic, root_macro_call_first_node};
+use clippy_utils::source::snippet_opt;
 use clippy_utils::ty::is_type_diagnostic_item;
 use clippy_utils::visitors::Visitable;
 use clippy_utils::{is_entrypoint_fn, is_trait_impl_item, method_chain_args};
@@ -33,12 +31,15 @@ use rustc_span::{Span, sym};
 use std::ops::Range;
 use url::Url;
 
+mod doc_comment_double_space_linebreaks;
 mod include_in_doc_without_cfg;
+mod lazy_continuation;
 mod link_with_quotes;
 mod markdown;
 mod missing_headers;
 mod needless_doctest_main;
 mod suspicious_doc_comments;
+mod too_long_first_doc_paragraph;
 
 declare_clippy_lint! {
     /// ### What it does
@@ -567,6 +568,39 @@ declare_clippy_lint! {
     "link reference defined in list item or quote"
 }
 
+declare_clippy_lint! {
+    /// ### What it does
+    /// Detects doc comment linebreaks that use double spaces to separate lines, instead of back-slash (`\`).
+    ///
+    /// ### Why is this bad?
+    /// Double spaces, when used as doc comment linebreaks, can be difficult to see, and may
+    /// accidentally be removed during automatic formatting or manual refactoring. The use of a back-slash (`\`)
+    /// is clearer in this regard.
+    ///
+    /// ### Example
+    /// The two replacement dots in this example represent a double space.
+    /// ```no_run
+    /// /// This command takes two numbers as inputs and··
+    /// /// adds them together, and then returns the result.
+    /// fn add(l: i32, r: i32) -> i32 {
+    ///     l + r
+    /// }
+    /// ```
+    ///
+    /// Use instead:
+    /// ```no_run
+    /// /// This command takes two numbers as inputs and\
+    /// /// adds them together, and then returns the result.
+    /// fn add(l: i32, r: i32) -> i32 {
+    ///     l + r
+    /// }
+    /// ```
+    #[clippy::version = "1.87.0"]
+    pub DOC_COMMENT_DOUBLE_SPACE_LINEBREAKS,
+    pedantic,
+    "double-space used for doc comment linebreak instead of `\\`"
+}
+
 pub struct Documentation {
     valid_idents: FxHashSet<String>,
     check_private_items: bool,
@@ -598,6 +632,7 @@ impl_lint_pass!(Documentation => [
     DOC_OVERINDENTED_LIST_ITEMS,
     TOO_LONG_FIRST_DOC_PARAGRAPH,
     DOC_INCLUDE_WITHOUT_CFG,
+    DOC_COMMENT_DOUBLE_SPACE_LINEBREAKS
 ]);
 
 impl EarlyLintPass for Documentation {
@@ -894,6 +929,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
     let mut paragraph_range = 0..0;
     let mut code_level = 0;
     let mut blockquote_level = 0;
+    let mut collected_breaks: Vec<Span> = Vec::new();
     let mut is_first_paragraph = true;
 
     let mut containers = Vec::new();
@@ -1069,6 +1105,14 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
                         &containers[..],
                     );
                 }
+
+                if let Some(span) = fragments.span(cx, range.clone())
+                    && !span.from_expansion()
+                    && let Some(snippet) = snippet_opt(cx, span)
+                    && !snippet.trim().starts_with('\\')
+                    && event == HardBreak {
+                    collected_breaks.push(span);
+                }
             },
             Text(text) => {
                 paragraph_range.end = range.end;
@@ -1119,6 +1163,9 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
             FootnoteReference(_) => {}
         }
     }
+
+    doc_comment_double_space_linebreaks::check(cx, &collected_breaks);
+
     headers
 }
 
diff --git a/clippy_lints/src/from_over_into.rs b/clippy_lints/src/from_over_into.rs
index 6da5567d9c7..be887b03ae4 100644
--- a/clippy_lints/src/from_over_into.rs
+++ b/clippy_lints/src/from_over_into.rs
@@ -176,8 +176,8 @@ fn convert_to_from(
         return None;
     };
     let body = cx.tcx.hir_body(body_id);
-    let [input] = body.params else { return None };
-    let PatKind::Binding(.., self_ident, None) = input.pat.kind else {
+    let [self_param] = body.params else { return None };
+    let PatKind::Binding(.., self_ident, None) = self_param.pat.kind else {
         return None;
     };
 
@@ -194,12 +194,12 @@ fn convert_to_from(
         // impl Into<T> for U  ->  impl Into<T> for T
         //                  ~                       ~
         (self_ty.span, into.to_owned()),
-        // fn into(self) -> T  ->  fn from(self) -> T
-        //    ~~~~                    ~~~~
+        // fn into(self: U) -> T  ->  fn from(self) -> T
+        //    ~~~~                       ~~~~
         (impl_item.ident.span, String::from("from")),
-        // fn into([mut] self) -> T  ->  fn into([mut] v: T) -> T
-        //               ~~~~                          ~~~~
-        (self_ident.span, format!("val: {from}")),
+        // fn into([mut] self: U) -> T  ->  fn into([mut] val: T) -> T
+        //               ~~~~~~~                          ~~~~~~
+        (self_ident.span.to(self_param.ty_span), format!("val: {from}")),
     ];
 
     if let FnRetTy::Return(_) = sig.decl.output {
diff --git a/clippy_lints/src/incompatible_msrv.rs b/clippy_lints/src/incompatible_msrv.rs
index 12dfb14c454..e55edb1fcaa 100644
--- a/clippy_lints/src/incompatible_msrv.rs
+++ b/clippy_lints/src/incompatible_msrv.rs
@@ -4,12 +4,12 @@ use clippy_utils::is_in_test;
 use clippy_utils::msrvs::Msrv;
 use rustc_attr_parsing::{RustcVersion, StabilityLevel, StableSince};
 use rustc_data_structures::fx::FxHashMap;
-use rustc_hir::{Expr, ExprKind, HirId};
+use rustc_hir::{Expr, ExprKind, HirId, QPath};
 use rustc_lint::{LateContext, LateLintPass};
 use rustc_middle::ty::TyCtxt;
 use rustc_session::impl_lint_pass;
 use rustc_span::def_id::DefId;
-use rustc_span::{ExpnKind, Span};
+use rustc_span::{ExpnKind, Span, sym};
 
 declare_clippy_lint! {
     /// ### What it does
@@ -93,6 +93,21 @@ impl IncompatibleMsrv {
             // Intentionally not using `.from_expansion()`, since we do still care about macro expansions
             return;
         }
+
+        // Functions coming from `core` while expanding a macro such as `assert*!()` get to cheat too: the
+        // macros may have existed prior to the checked MSRV, but their expansion with a recent compiler
+        // might use recent functions or methods. Compiling with an older compiler would not use those.
+        if span.from_expansion()
+            && cx.tcx.crate_name(def_id.krate) == sym::core
+            && span
+                .ctxt()
+                .outer_expn_data()
+                .macro_def_id
+                .is_some_and(|def_id| cx.tcx.crate_name(def_id.krate) == sym::core)
+        {
+            return;
+        }
+
         if (self.check_in_tests || !is_in_test(cx.tcx, node))
             && let Some(current) = self.msrv.current(cx)
             && let version = self.get_def_id_version(cx.tcx, def_id)
@@ -118,8 +133,11 @@ impl<'tcx> LateLintPass<'tcx> for IncompatibleMsrv {
                     self.emit_lint_if_under_msrv(cx, method_did, expr.hir_id, span);
                 }
             },
-            ExprKind::Call(call, [_]) => {
-                if let ExprKind::Path(qpath) = call.kind
+            ExprKind::Call(call, _) => {
+                // Desugaring into function calls by the compiler will use `QPath::LangItem` variants. Those should
+                // not be linted as they will not be generated in older compilers if the function is not available,
+                // and the compiler is allowed to call unstable functions.
+                if let ExprKind::Path(qpath @ (QPath::Resolved(..) | QPath::TypeRelative(..))) = call.kind
                     && let Some(path_def_id) = cx.qpath_res(&qpath, call.hir_id).opt_def_id()
                 {
                     self.emit_lint_if_under_msrv(cx, path_def_id, expr.hir_id, call.span);
diff --git a/clippy_lints/src/needless_pass_by_value.rs b/clippy_lints/src/needless_pass_by_value.rs
index f5652e7b832..7aa1d63c660 100644
--- a/clippy_lints/src/needless_pass_by_value.rs
+++ b/clippy_lints/src/needless_pass_by_value.rs
@@ -1,10 +1,10 @@
 use clippy_utils::diagnostics::span_lint_and_then;
-use clippy_utils::is_self;
 use clippy_utils::ptr::get_spans;
 use clippy_utils::source::{SpanRangeExt, snippet};
 use clippy_utils::ty::{
     implements_trait, implements_trait_with_env_from_iter, is_copy, is_type_diagnostic_item, is_type_lang_item,
 };
+use clippy_utils::{is_self, peel_hir_ty_options};
 use rustc_abi::ExternAbi;
 use rustc_errors::{Applicability, Diag};
 use rustc_hir::intravisit::FnKind;
@@ -279,18 +279,10 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue {
                         }
                     }
 
-                    let suggestion = if is_type_diagnostic_item(cx, ty, sym::Option)
-                        && let snip = snippet(cx, input.span, "_")
-                        && let Some((first, rest)) = snip.split_once('<')
-                    {
-                        format!("{first}<&{rest}")
-                    } else {
-                        format!("&{}", snippet(cx, input.span, "_"))
-                    };
-                    diag.span_suggestion(
-                        input.span,
+                    diag.span_suggestion_verbose(
+                        peel_hir_ty_options(cx, input).span.shrink_to_lo(),
                         "consider taking a reference instead",
-                        suggestion,
+                        '&',
                         Applicability::MaybeIncorrect,
                     );
                 };
diff --git a/clippy_lints/src/question_mark.rs b/clippy_lints/src/question_mark.rs
index 4f5f3eb6c15..5d5b3bf44ab 100644
--- a/clippy_lints/src/question_mark.rs
+++ b/clippy_lints/src/question_mark.rs
@@ -145,8 +145,47 @@ fn check_let_some_else_return_none(cx: &LateContext<'_>, stmt: &Stmt<'_>) {
     {
         let mut applicability = Applicability::MaybeIncorrect;
         let init_expr_str = snippet_with_applicability(cx, init_expr.span, "..", &mut applicability);
-        let receiver_str = snippet_with_applicability(cx, inner_pat.span, "..", &mut applicability);
-        let sugg = format!("let {receiver_str} = {init_expr_str}?;",);
+        // Take care when binding is `ref`
+        let sugg = if let PatKind::Binding(
+            BindingMode(ByRef::Yes(ref_mutability), binding_mutability),
+            _hir_id,
+            ident,
+            subpattern,
+        ) = inner_pat.kind
+        {
+            let (from_method, replace_to) = match ref_mutability {
+                Mutability::Mut => (".as_mut()", "&mut "),
+                Mutability::Not => (".as_ref()", "&"),
+            };
+
+            let mutability_str = match binding_mutability {
+                Mutability::Mut => "mut ",
+                Mutability::Not => "",
+            };
+
+            // Handle subpattern (@ subpattern)
+            let maybe_subpattern = match subpattern {
+                Some(Pat {
+                    kind: PatKind::Binding(BindingMode(ByRef::Yes(_), _), _, subident, None),
+                    ..
+                }) => {
+                    // avoid `&ref`
+                    // note that, because you can't have aliased, mutable references, we don't have to worry about
+                    // the outer and inner mutability being different
+                    format!(" @ {subident}")
+                },
+                Some(subpattern) => {
+                    let substr = snippet_with_applicability(cx, subpattern.span, "..", &mut applicability);
+                    format!(" @ {replace_to}{substr}")
+                },
+                None => String::new(),
+            };
+
+            format!("let {mutability_str}{ident}{maybe_subpattern} = {init_expr_str}{from_method}?;")
+        } else {
+            let receiver_str = snippet_with_applicability(cx, inner_pat.span, "..", &mut applicability);
+            format!("let {receiver_str} = {init_expr_str}?;")
+        };
         span_lint_and_sugg(
             cx,
             QUESTION_MARK,
diff --git a/clippy_utils/README.md b/clippy_utils/README.md
index 5dd31b52f88..58c95b317b3 100644
--- a/clippy_utils/README.md
+++ b/clippy_utils/README.md
@@ -30,7 +30,7 @@ Function signatures can change or be removed without replacement without any pri
 
 <!-- REUSE-IgnoreStart -->
 
-Copyright 2014-2024 The Rust Project Developers
+Copyright 2014-2025 The Rust Project Developers
 
 Licensed under the Apache License, Version 2.0
 <[https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0)> or the MIT license
diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs
index 2d2a19c7b78..d537dc8321f 100644
--- a/clippy_utils/src/lib.rs
+++ b/clippy_utils/src/lib.rs
@@ -107,10 +107,10 @@ use rustc_hir::hir_id::{HirIdMap, HirIdSet};
 use rustc_hir::intravisit::{FnKind, Visitor, walk_expr};
 use rustc_hir::{
     self as hir, Arm, BindingMode, Block, BlockCheckMode, Body, ByRef, Closure, ConstArgKind, ConstContext,
-    Destination, Expr, ExprField, ExprKind, FnDecl, FnRetTy, GenericArgs, HirId, Impl, ImplItem, ImplItemKind,
-    ImplItemRef, Item, ItemKind, LangItem, LetStmt, MatchSource, Mutability, Node, OwnerId, OwnerNode, Param, Pat,
-    PatExpr, PatExprKind, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitFn, TraitItem, TraitItemKind,
-    TraitItemRef, TraitRef, TyKind, UnOp, def,
+    Destination, Expr, ExprField, ExprKind, FnDecl, FnRetTy, GenericArg, GenericArgs, HirId, Impl, ImplItem,
+    ImplItemKind, ImplItemRef, Item, ItemKind, LangItem, LetStmt, MatchSource, Mutability, Node, OwnerId, OwnerNode,
+    Param, Pat, PatExpr, PatExprKind, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitFn, TraitItem,
+    TraitItemKind, TraitItemRef, TraitRef, TyKind, UnOp, def,
 };
 use rustc_lexer::{TokenKind, tokenize};
 use rustc_lint::{LateContext, Level, Lint, LintContext};
@@ -435,7 +435,7 @@ pub fn qpath_generic_tys<'tcx>(qpath: &QPath<'tcx>) -> impl Iterator<Item = &'tc
         .map_or(&[][..], |a| a.args)
         .iter()
         .filter_map(|a| match a {
-            hir::GenericArg::Type(ty) => Some(ty.as_unambig_ty()),
+            GenericArg::Type(ty) => Some(ty.as_unambig_ty()),
             _ => None,
         })
 }
@@ -3562,7 +3562,7 @@ pub fn is_block_like(expr: &Expr<'_>) -> bool {
 pub fn binary_expr_needs_parentheses(expr: &Expr<'_>) -> bool {
     fn contains_block(expr: &Expr<'_>, is_operand: bool) -> bool {
         match expr.kind {
-            ExprKind::Binary(_, lhs, _) => contains_block(lhs, true),
+            ExprKind::Binary(_, lhs, _) | ExprKind::Cast(lhs, _) => contains_block(lhs, true),
             _ if is_block_like(expr) => is_operand,
             _ => false,
         }
@@ -3709,3 +3709,21 @@ pub fn is_mutable(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
         true
     }
 }
+
+/// Peel `Option<…>` from `hir_ty` as long as the HIR name is `Option` and it corresponds to the
+/// `core::Option<_>` type.
+pub fn peel_hir_ty_options<'tcx>(cx: &LateContext<'tcx>, mut hir_ty: &'tcx hir::Ty<'tcx>) -> &'tcx hir::Ty<'tcx> {
+    let Some(option_def_id) = cx.tcx.get_diagnostic_item(sym::Option) else {
+        return hir_ty;
+    };
+    while let TyKind::Path(QPath::Resolved(None, path)) = hir_ty.kind
+        && let Some(segment) = path.segments.last()
+        && segment.ident.name == sym::Option
+        && let Res::Def(DefKind::Enum, def_id) = segment.res
+        && def_id == option_def_id
+        && let [GenericArg::Type(arg_ty)] = segment.args().args
+    {
+        hir_ty = arg_ty.as_unambig_ty();
+    }
+    hir_ty
+}
diff --git a/rustc_tools_util/README.md b/rustc_tools_util/README.md
index ff4ca6f830e..f47a4c69c2c 100644
--- a/rustc_tools_util/README.md
+++ b/rustc_tools_util/README.md
@@ -51,7 +51,7 @@ The changelog for `rustc_tools_util` is available under:
 
 <!-- REUSE-IgnoreStart -->
 
-Copyright 2014-2024 The Rust Project Developers
+Copyright 2014-2025 The Rust Project Developers
 
 Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
 http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
diff --git a/tests/ui/crashes/needless_pass_by_value-w-late-bound.stderr b/tests/ui/crashes/needless_pass_by_value-w-late-bound.stderr
index 28479570006..b4fb1222539 100644
--- a/tests/ui/crashes/needless_pass_by_value-w-late-bound.stderr
+++ b/tests/ui/crashes/needless_pass_by_value-w-late-bound.stderr
@@ -2,7 +2,7 @@ error: this argument is passed by value, but not consumed in the function body
   --> tests/ui/crashes/needless_pass_by_value-w-late-bound.rs:7:12
    |
 LL | fn test(x: Foo<'_>) {}
-   |            ^^^^^^^ help: consider taking a reference instead: `&Foo<'_>`
+   |            ^^^^^^^
    |
 help: or consider marking this type as `Copy`
   --> tests/ui/crashes/needless_pass_by_value-w-late-bound.rs:5:1
@@ -11,6 +11,10 @@ LL | struct Foo<'a>(&'a [(); 100]);
    | ^^^^^^^^^^^^^^
    = note: `-D clippy::needless-pass-by-value` implied by `-D warnings`
    = help: to override `-D warnings` add `#[allow(clippy::needless_pass_by_value)]`
+help: consider taking a reference instead
+   |
+LL | fn test(x: &Foo<'_>) {}
+   |            +
 
 error: aborting due to 1 previous error
 
diff --git a/tests/ui/doc/doc_comment_double_space_linebreaks.fixed b/tests/ui/doc/doc_comment_double_space_linebreaks.fixed
new file mode 100644
index 00000000000..6a616b2c7e1
--- /dev/null
+++ b/tests/ui/doc/doc_comment_double_space_linebreaks.fixed
@@ -0,0 +1,98 @@
+#![feature(custom_inner_attributes)]
+#![rustfmt::skip]
+
+#![warn(clippy::doc_comment_double_space_linebreaks)]
+#![allow(unused, clippy::empty_docs)]
+
+//~v doc_comment_double_space_linebreaks
+//! Should warn on double space linebreaks\
+//! in file/module doc comment
+
+/// Should not warn on single-line doc comments
+fn single_line() {}
+
+/// Should not warn on single-line doc comments
+/// split across multiple lines
+fn single_line_split() {}
+
+// Should not warn on normal comments
+
+// note: cargo fmt can remove double spaces from normal and block comments
+// Should not warn on normal comments  
+// with double spaces at the end of a line  
+
+#[doc = "This is a doc attribute, which should not be linted"]
+fn normal_comment() {
+   /*
+   Should not warn on block comments
+   */
+  
+  /*
+  Should not warn on block comments  
+  with double space at the end of a line
+  */
+}
+
+//~v doc_comment_double_space_linebreaks
+/// Should warn when doc comment uses double space\
+/// as a line-break, even when there are multiple\
+/// in a row
+fn double_space_doc_comment() {}
+
+/// Should not warn when back-slash is used \
+/// as a line-break
+fn back_slash_doc_comment() {}
+
+//~v doc_comment_double_space_linebreaks
+/// 🌹 are 🟥\
+/// 🌷 are 🟦\
+/// 📎 is 😎\
+/// and so are 🫵\
+/// (hopefully no formatting weirdness linting this)
+fn multi_byte_chars_tada() {}
+
+macro_rules! macro_that_makes_function {
+   () => {
+      /// Shouldn't lint on this!  
+      /// (hopefully)
+      fn my_macro_created_function() {}
+   }
+}
+
+macro_that_makes_function!();
+
+// dont lint when its alone on a line
+///  
+fn alone() {}
+
+/// | First column | Second column |  
+/// | ------------ | ------------- |  
+/// | Not a line   | break when    |  
+/// | after a line | in a table    |  
+fn table() {}
+
+/// ```text  
+/// It's also not a hard line break if  
+/// there's two spaces at the end of a  
+/// line in a block code.  
+/// ```  
+fn codeblock() {}
+
+/// It's also not a hard line break `if  
+/// there's` two spaces in the middle of inline code.  
+fn inline() {}
+
+/// It's also not a hard line break [when](  
+/// https://example.com) in a URL.  
+fn url() {}
+
+//~v doc_comment_double_space_linebreaks
+/// here we mix\
+/// double spaces\
+/// and also\
+/// adding backslash\
+/// to some of them\
+/// to see how that looks
+fn mixed() {}
+
+fn main() {}
diff --git a/tests/ui/doc/doc_comment_double_space_linebreaks.rs b/tests/ui/doc/doc_comment_double_space_linebreaks.rs
new file mode 100644
index 00000000000..e36cf7dea23
--- /dev/null
+++ b/tests/ui/doc/doc_comment_double_space_linebreaks.rs
@@ -0,0 +1,98 @@
+#![feature(custom_inner_attributes)]
+#![rustfmt::skip]
+
+#![warn(clippy::doc_comment_double_space_linebreaks)]
+#![allow(unused, clippy::empty_docs)]
+
+//~v doc_comment_double_space_linebreaks
+//! Should warn on double space linebreaks  
+//! in file/module doc comment
+
+/// Should not warn on single-line doc comments
+fn single_line() {}
+
+/// Should not warn on single-line doc comments
+/// split across multiple lines
+fn single_line_split() {}
+
+// Should not warn on normal comments
+
+// note: cargo fmt can remove double spaces from normal and block comments
+// Should not warn on normal comments  
+// with double spaces at the end of a line  
+
+#[doc = "This is a doc attribute, which should not be linted"]
+fn normal_comment() {
+   /*
+   Should not warn on block comments
+   */
+  
+  /*
+  Should not warn on block comments  
+  with double space at the end of a line
+  */
+}
+
+//~v doc_comment_double_space_linebreaks
+/// Should warn when doc comment uses double space  
+/// as a line-break, even when there are multiple  
+/// in a row
+fn double_space_doc_comment() {}
+
+/// Should not warn when back-slash is used \
+/// as a line-break
+fn back_slash_doc_comment() {}
+
+//~v doc_comment_double_space_linebreaks
+/// 🌹 are 🟥  
+/// 🌷 are 🟦  
+/// 📎 is 😎  
+/// and so are 🫵  
+/// (hopefully no formatting weirdness linting this)
+fn multi_byte_chars_tada() {}
+
+macro_rules! macro_that_makes_function {
+   () => {
+      /// Shouldn't lint on this!  
+      /// (hopefully)
+      fn my_macro_created_function() {}
+   }
+}
+
+macro_that_makes_function!();
+
+// dont lint when its alone on a line
+///  
+fn alone() {}
+
+/// | First column | Second column |  
+/// | ------------ | ------------- |  
+/// | Not a line   | break when    |  
+/// | after a line | in a table    |  
+fn table() {}
+
+/// ```text  
+/// It's also not a hard line break if  
+/// there's two spaces at the end of a  
+/// line in a block code.  
+/// ```  
+fn codeblock() {}
+
+/// It's also not a hard line break `if  
+/// there's` two spaces in the middle of inline code.  
+fn inline() {}
+
+/// It's also not a hard line break [when](  
+/// https://example.com) in a URL.  
+fn url() {}
+
+//~v doc_comment_double_space_linebreaks
+/// here we mix  
+/// double spaces\
+/// and also  
+/// adding backslash\
+/// to some of them  
+/// to see how that looks
+fn mixed() {}
+
+fn main() {}
diff --git a/tests/ui/doc/doc_comment_double_space_linebreaks.stderr b/tests/ui/doc/doc_comment_double_space_linebreaks.stderr
new file mode 100644
index 00000000000..08c6956c3d0
--- /dev/null
+++ b/tests/ui/doc/doc_comment_double_space_linebreaks.stderr
@@ -0,0 +1,50 @@
+error: doc comment uses two spaces for a hard line break
+  --> tests/ui/doc/doc_comment_double_space_linebreaks.rs:8:43
+   |
+LL | //! Should warn on double space linebreaks  
+   |                                           ^^
+   |
+   = help: replace this double space with a backslash: `\`
+   = note: `-D clippy::doc-comment-double-space-linebreaks` implied by `-D warnings`
+   = help: to override `-D warnings` add `#[allow(clippy::doc_comment_double_space_linebreaks)]`
+
+error: doc comment uses two spaces for a hard line break
+  --> tests/ui/doc/doc_comment_double_space_linebreaks.rs:37:51
+   |
+LL | /// Should warn when doc comment uses double space  
+   |                                                   ^^
+LL | /// as a line-break, even when there are multiple  
+   |                                                  ^^
+   |
+   = help: replace this double space with a backslash: `\`
+
+error: doc comment uses two spaces for a hard line break
+  --> tests/ui/doc/doc_comment_double_space_linebreaks.rs:47:12
+   |
+LL | /// 🌹 are 🟥  
+   |              ^^
+LL | /// 🌷 are 🟦  
+   |              ^^
+LL | /// 📎 is 😎  
+   |             ^^
+LL | /// and so are 🫵  
+   |                  ^^
+   |
+   = help: replace this double space with a backslash: `\`
+
+error: doc comment uses two spaces for a hard line break
+  --> tests/ui/doc/doc_comment_double_space_linebreaks.rs:90:16
+   |
+LL | /// here we mix  
+   |                ^^
+LL | /// double spaces\
+LL | /// and also  
+   |             ^^
+LL | /// adding backslash\
+LL | /// to some of them  
+   |                    ^^
+   |
+   = help: replace this double space with a backslash: `\`
+
+error: aborting due to 4 previous errors
+
diff --git a/tests/ui/from_over_into.fixed b/tests/ui/from_over_into.fixed
index 7d6780a0e02..7229e5a2d35 100644
--- a/tests/ui/from_over_into.fixed
+++ b/tests/ui/from_over_into.fixed
@@ -105,4 +105,15 @@ fn issue_12138() {
     }
 }
 
+fn issue_112502() {
+    struct MyInt(i64);
+
+    impl From<MyInt> for i64 {
+        //~^ from_over_into
+        fn from(val: MyInt) -> Self {
+            val.0
+        }
+    }
+}
+
 fn main() {}
diff --git a/tests/ui/from_over_into.rs b/tests/ui/from_over_into.rs
index 387ddde359c..9c75969c5c1 100644
--- a/tests/ui/from_over_into.rs
+++ b/tests/ui/from_over_into.rs
@@ -105,4 +105,15 @@ fn issue_12138() {
     }
 }
 
+fn issue_112502() {
+    struct MyInt(i64);
+
+    impl Into<i64> for MyInt {
+        //~^ from_over_into
+        fn into(self: MyInt) -> i64 {
+            self.0
+        }
+    }
+}
+
 fn main() {}
diff --git a/tests/ui/from_over_into.stderr b/tests/ui/from_over_into.stderr
index a564bccbaf7..fe779544dd5 100644
--- a/tests/ui/from_over_into.stderr
+++ b/tests/ui/from_over_into.stderr
@@ -107,5 +107,21 @@ LL |
 LL ~         fn from(val: Hello) {}
    |
 
-error: aborting due to 7 previous errors
+error: an implementation of `From` is preferred since it gives you `Into<_>` for free where the reverse isn't true
+  --> tests/ui/from_over_into.rs:111:5
+   |
+LL |     impl Into<i64> for MyInt {
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = help: `impl From<Local> for Foreign` is allowed by the orphan rules, for more information see
+           https://doc.rust-lang.org/reference/items/implementations.html#trait-implementation-coherence
+help: replace the `Into` implementation with `From<issue_112502::MyInt>`
+   |
+LL ~     impl From<MyInt> for i64 {
+LL |
+LL ~         fn from(val: MyInt) -> Self {
+LL ~             val.0
+   |
+
+error: aborting due to 8 previous errors
 
diff --git a/tests/ui/incompatible_msrv.rs b/tests/ui/incompatible_msrv.rs
index b4fea4cae5e..99101b2bb8f 100644
--- a/tests/ui/incompatible_msrv.rs
+++ b/tests/ui/incompatible_msrv.rs
@@ -1,5 +1,6 @@
 #![warn(clippy::incompatible_msrv)]
 #![feature(custom_inner_attributes)]
+#![feature(panic_internals)]
 #![clippy::msrv = "1.3.0"]
 
 use std::collections::HashMap;
@@ -34,4 +35,42 @@ async fn issue12273(v: impl Future<Output = ()>) {
     v.await;
 }
 
+fn core_special_treatment(p: bool) {
+    // Do not lint code coming from `core` macros expanding into `core` function calls
+    if p {
+        panic!("foo"); // Do not lint
+    }
+
+    // But still lint code calling `core` functions directly
+    if p {
+        core::panicking::panic("foo");
+        //~^ ERROR: is `1.3.0` but this item is stable since `1.6.0`
+    }
+
+    // Lint code calling `core` from non-`core` macros
+    macro_rules! my_panic {
+        ($msg:expr) => {
+            core::panicking::panic($msg)
+        }; //~^ ERROR: is `1.3.0` but this item is stable since `1.6.0`
+    }
+    my_panic!("foo");
+
+    // Lint even when the macro comes from `core` and calls `core` functions
+    assert!(core::panicking::panic("out of luck"));
+    //~^ ERROR: is `1.3.0` but this item is stable since `1.6.0`
+}
+
+#[clippy::msrv = "1.26.0"]
+fn lang_items() {
+    // Do not lint lang items. `..=` will expand into `RangeInclusive::new()`, which was introduced
+    // in Rust 1.27.0.
+    let _ = 1..=3;
+}
+
+#[clippy::msrv = "1.80.0"]
+fn issue14212() {
+    let _ = std::iter::repeat_n((), 5);
+    //~^ ERROR: is `1.80.0` but this item is stable since `1.82.0`
+}
+
 fn main() {}
diff --git a/tests/ui/incompatible_msrv.stderr b/tests/ui/incompatible_msrv.stderr
index 56c9eae5aaf..5ea2bb9cc58 100644
--- a/tests/ui/incompatible_msrv.stderr
+++ b/tests/ui/incompatible_msrv.stderr
@@ -1,5 +1,5 @@
 error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.10.0`
-  --> tests/ui/incompatible_msrv.rs:13:39
+  --> tests/ui/incompatible_msrv.rs:14:39
    |
 LL |     assert_eq!(map.entry("poneyland").key(), &"poneyland");
    |                                       ^^^^^
@@ -8,16 +8,45 @@ LL |     assert_eq!(map.entry("poneyland").key(), &"poneyland");
    = help: to override `-D warnings` add `#[allow(clippy::incompatible_msrv)]`
 
 error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.12.0`
-  --> tests/ui/incompatible_msrv.rs:17:11
+  --> tests/ui/incompatible_msrv.rs:18:11
    |
 LL |         v.into_key();
    |           ^^^^^^^^^^
 
 error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.4.0`
-  --> tests/ui/incompatible_msrv.rs:21:5
+  --> tests/ui/incompatible_msrv.rs:22:5
    |
 LL |     sleep(Duration::new(1, 0));
    |     ^^^^^
 
-error: aborting due to 3 previous errors
+error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.6.0`
+  --> tests/ui/incompatible_msrv.rs:46:9
+   |
+LL |         core::panicking::panic("foo");
+   |         ^^^^^^^^^^^^^^^^^^^^^^
+
+error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.6.0`
+  --> tests/ui/incompatible_msrv.rs:53:13
+   |
+LL |             core::panicking::panic($msg)
+   |             ^^^^^^^^^^^^^^^^^^^^^^
+...
+LL |     my_panic!("foo");
+   |     ---------------- in this macro invocation
+   |
+   = note: this error originates in the macro `my_panic` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.6.0`
+  --> tests/ui/incompatible_msrv.rs:59:13
+   |
+LL |     assert!(core::panicking::panic("out of luck"));
+   |             ^^^^^^^^^^^^^^^^^^^^^^
+
+error: current MSRV (Minimum Supported Rust Version) is `1.80.0` but this item is stable since `1.82.0`
+  --> tests/ui/incompatible_msrv.rs:72:13
+   |
+LL |     let _ = std::iter::repeat_n((), 5);
+   |             ^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 7 previous errors
 
diff --git a/tests/ui/needless_pass_by_value.rs b/tests/ui/needless_pass_by_value.rs
index adea373fd55..aef7fff2870 100644
--- a/tests/ui/needless_pass_by_value.rs
+++ b/tests/ui/needless_pass_by_value.rs
@@ -196,6 +196,35 @@ fn option_inner_ref(x: Option<String>) {
     assert!(x.is_some());
 }
 
+mod non_standard {
+    #[derive(Debug)]
+    pub struct Option<T>(T);
+}
+
+fn non_standard_option(x: non_standard::Option<String>) {
+    //~^ needless_pass_by_value
+    dbg!(&x);
+}
+
+fn option_by_name(x: Option<std::option::Option<core::option::Option<non_standard::Option<String>>>>) {
+    //~^ needless_pass_by_value
+    dbg!(&x);
+}
+
+type OptStr = Option<String>;
+
+fn non_option(x: OptStr) {
+    //~^ needless_pass_by_value
+    dbg!(&x);
+}
+
+type Opt<T> = Option<T>;
+
+fn non_option_either(x: Opt<String>) {
+    //~^ needless_pass_by_value
+    dbg!(&x);
+}
+
 fn main() {
     // This should not cause an ICE either
     // https://github.com/rust-lang/rust-clippy/issues/3144
diff --git a/tests/ui/needless_pass_by_value.stderr b/tests/ui/needless_pass_by_value.stderr
index 987bfc4affc..e4381d1db53 100644
--- a/tests/ui/needless_pass_by_value.stderr
+++ b/tests/ui/needless_pass_by_value.stderr
@@ -17,43 +17,78 @@ error: this argument is passed by value, but not consumed in the function body
   --> tests/ui/needless_pass_by_value.rs:35:22
    |
 LL | fn bar(x: String, y: Wrapper) {
-   |                      ^^^^^^^ help: consider taking a reference instead: `&Wrapper`
+   |                      ^^^^^^^
+   |
+help: consider taking a reference instead
+   |
+LL | fn bar(x: String, y: &Wrapper) {
+   |                      +
 
 error: this argument is passed by value, but not consumed in the function body
   --> tests/ui/needless_pass_by_value.rs:44:71
    |
 LL | fn test_borrow_trait<T: Borrow<str>, U: AsRef<str>, V>(t: T, u: U, v: V) {
-   |                                                                       ^ help: consider taking a reference instead: `&V`
+   |                                                                       ^
+   |
+help: consider taking a reference instead
+   |
+LL | fn test_borrow_trait<T: Borrow<str>, U: AsRef<str>, V>(t: T, u: U, v: &V) {
+   |                                                                       +
 
 error: this argument is passed by value, but not consumed in the function body
   --> tests/ui/needless_pass_by_value.rs:58:18
    |
 LL | fn test_match(x: Option<Option<String>>, y: Option<Option<String>>) {
-   |                  ^^^^^^^^^^^^^^^^^^^^^^ help: consider taking a reference instead: `Option<&Option<String>>`
+   |                  ^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: consider taking a reference instead
+   |
+LL | fn test_match(x: Option<Option<&String>>, y: Option<Option<String>>) {
+   |                                +
 
 error: this argument is passed by value, but not consumed in the function body
   --> tests/ui/needless_pass_by_value.rs:73:24
    |
 LL | fn test_destructure(x: Wrapper, y: Wrapper, z: Wrapper) {
-   |                        ^^^^^^^ help: consider taking a reference instead: `&Wrapper`
+   |                        ^^^^^^^
+   |
+help: consider taking a reference instead
+   |
+LL | fn test_destructure(x: &Wrapper, y: Wrapper, z: Wrapper) {
+   |                        +
 
 error: this argument is passed by value, but not consumed in the function body
   --> tests/ui/needless_pass_by_value.rs:73:36
    |
 LL | fn test_destructure(x: Wrapper, y: Wrapper, z: Wrapper) {
-   |                                    ^^^^^^^ help: consider taking a reference instead: `&Wrapper`
+   |                                    ^^^^^^^
+   |
+help: consider taking a reference instead
+   |
+LL | fn test_destructure(x: Wrapper, y: &Wrapper, z: Wrapper) {
+   |                                    +
 
 error: this argument is passed by value, but not consumed in the function body
   --> tests/ui/needless_pass_by_value.rs:92:49
    |
 LL | fn test_blanket_ref<T: Foo, S: Serialize>(vals: T, serializable: S) {}
-   |                                                 ^ help: consider taking a reference instead: `&T`
+   |                                                 ^
+   |
+help: consider taking a reference instead
+   |
+LL | fn test_blanket_ref<T: Foo, S: Serialize>(vals: &T, serializable: S) {}
+   |                                                 +
 
 error: this argument is passed by value, but not consumed in the function body
   --> tests/ui/needless_pass_by_value.rs:95:18
    |
 LL | fn issue_2114(s: String, t: String, u: Vec<i32>, v: Vec<i32>) {
-   |                  ^^^^^^ help: consider taking a reference instead: `&String`
+   |                  ^^^^^^
+   |
+help: consider taking a reference instead
+   |
+LL | fn issue_2114(s: &String, t: String, u: Vec<i32>, v: Vec<i32>) {
+   |                  +
 
 error: this argument is passed by value, but not consumed in the function body
   --> tests/ui/needless_pass_by_value.rs:95:29
@@ -76,7 +111,12 @@ error: this argument is passed by value, but not consumed in the function body
   --> tests/ui/needless_pass_by_value.rs:95:40
    |
 LL | fn issue_2114(s: String, t: String, u: Vec<i32>, v: Vec<i32>) {
-   |                                        ^^^^^^^^ help: consider taking a reference instead: `&Vec<i32>`
+   |                                        ^^^^^^^^
+   |
+help: consider taking a reference instead
+   |
+LL | fn issue_2114(s: String, t: String, u: &Vec<i32>, v: Vec<i32>) {
+   |                                        +
 
 error: this argument is passed by value, but not consumed in the function body
   --> tests/ui/needless_pass_by_value.rs:95:53
@@ -105,85 +145,175 @@ error: this argument is passed by value, but not consumed in the function body
   --> tests/ui/needless_pass_by_value.rs:115:12
    |
 LL |         t: String,
-   |            ^^^^^^ help: consider taking a reference instead: `&String`
+   |            ^^^^^^
+   |
+help: consider taking a reference instead
+   |
+LL |         t: &String,
+   |            +
 
 error: this argument is passed by value, but not consumed in the function body
   --> tests/ui/needless_pass_by_value.rs:125:23
    |
 LL |     fn baz(&self, uu: U, ss: Self) {}
-   |                       ^ help: consider taking a reference instead: `&U`
+   |                       ^
+   |
+help: consider taking a reference instead
+   |
+LL |     fn baz(&self, uu: &U, ss: Self) {}
+   |                       +
 
 error: this argument is passed by value, but not consumed in the function body
   --> tests/ui/needless_pass_by_value.rs:125:30
    |
 LL |     fn baz(&self, uu: U, ss: Self) {}
-   |                              ^^^^ help: consider taking a reference instead: `&Self`
+   |                              ^^^^
+   |
+help: consider taking a reference instead
+   |
+LL |     fn baz(&self, uu: U, ss: &Self) {}
+   |                              +
 
 error: this argument is passed by value, but not consumed in the function body
   --> tests/ui/needless_pass_by_value.rs:149:24
    |
 LL | fn bar_copy(x: u32, y: CopyWrapper) {
-   |                        ^^^^^^^^^^^ help: consider taking a reference instead: `&CopyWrapper`
+   |                        ^^^^^^^^^^^
    |
 help: or consider marking this type as `Copy`
   --> tests/ui/needless_pass_by_value.rs:147:1
    |
 LL | struct CopyWrapper(u32);
    | ^^^^^^^^^^^^^^^^^^
+help: consider taking a reference instead
+   |
+LL | fn bar_copy(x: u32, y: &CopyWrapper) {
+   |                        +
 
 error: this argument is passed by value, but not consumed in the function body
   --> tests/ui/needless_pass_by_value.rs:157:29
    |
 LL | fn test_destructure_copy(x: CopyWrapper, y: CopyWrapper, z: CopyWrapper) {
-   |                             ^^^^^^^^^^^ help: consider taking a reference instead: `&CopyWrapper`
+   |                             ^^^^^^^^^^^
    |
 help: or consider marking this type as `Copy`
   --> tests/ui/needless_pass_by_value.rs:147:1
    |
 LL | struct CopyWrapper(u32);
    | ^^^^^^^^^^^^^^^^^^
+help: consider taking a reference instead
+   |
+LL | fn test_destructure_copy(x: &CopyWrapper, y: CopyWrapper, z: CopyWrapper) {
+   |                             +
 
 error: this argument is passed by value, but not consumed in the function body
   --> tests/ui/needless_pass_by_value.rs:157:45
    |
 LL | fn test_destructure_copy(x: CopyWrapper, y: CopyWrapper, z: CopyWrapper) {
-   |                                             ^^^^^^^^^^^ help: consider taking a reference instead: `&CopyWrapper`
+   |                                             ^^^^^^^^^^^
    |
 help: or consider marking this type as `Copy`
   --> tests/ui/needless_pass_by_value.rs:147:1
    |
 LL | struct CopyWrapper(u32);
    | ^^^^^^^^^^^^^^^^^^
+help: consider taking a reference instead
+   |
+LL | fn test_destructure_copy(x: CopyWrapper, y: &CopyWrapper, z: CopyWrapper) {
+   |                                             +
 
 error: this argument is passed by value, but not consumed in the function body
   --> tests/ui/needless_pass_by_value.rs:157:61
    |
 LL | fn test_destructure_copy(x: CopyWrapper, y: CopyWrapper, z: CopyWrapper) {
-   |                                                             ^^^^^^^^^^^ help: consider taking a reference instead: `&CopyWrapper`
+   |                                                             ^^^^^^^^^^^
    |
 help: or consider marking this type as `Copy`
   --> tests/ui/needless_pass_by_value.rs:147:1
    |
 LL | struct CopyWrapper(u32);
    | ^^^^^^^^^^^^^^^^^^
+help: consider taking a reference instead
+   |
+LL | fn test_destructure_copy(x: CopyWrapper, y: CopyWrapper, z: &CopyWrapper) {
+   |                                                             +
 
 error: this argument is passed by value, but not consumed in the function body
   --> tests/ui/needless_pass_by_value.rs:173:40
    |
 LL | fn some_fun<'b, S: Bar<'b, ()>>(items: S) {}
-   |                                        ^ help: consider taking a reference instead: `&S`
+   |                                        ^
+   |
+help: consider taking a reference instead
+   |
+LL | fn some_fun<'b, S: Bar<'b, ()>>(items: &S) {}
+   |                                        +
 
 error: this argument is passed by value, but not consumed in the function body
   --> tests/ui/needless_pass_by_value.rs:179:20
    |
 LL | fn more_fun(items: impl Club<'static, i32>) {}
-   |                    ^^^^^^^^^^^^^^^^^^^^^^^ help: consider taking a reference instead: `&impl Club<'static, i32>`
+   |                    ^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: consider taking a reference instead
+   |
+LL | fn more_fun(items: &impl Club<'static, i32>) {}
+   |                    +
 
 error: this argument is passed by value, but not consumed in the function body
   --> tests/ui/needless_pass_by_value.rs:194:24
    |
 LL | fn option_inner_ref(x: Option<String>) {
-   |                        ^^^^^^^^^^^^^^ help: consider taking a reference instead: `Option<&String>`
+   |                        ^^^^^^^^^^^^^^
+   |
+help: consider taking a reference instead
+   |
+LL | fn option_inner_ref(x: Option<&String>) {
+   |                               +
+
+error: this argument is passed by value, but not consumed in the function body
+  --> tests/ui/needless_pass_by_value.rs:204:27
+   |
+LL | fn non_standard_option(x: non_standard::Option<String>) {
+   |                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: consider taking a reference instead
+   |
+LL | fn non_standard_option(x: &non_standard::Option<String>) {
+   |                           +
+
+error: this argument is passed by value, but not consumed in the function body
+  --> tests/ui/needless_pass_by_value.rs:209:22
+   |
+LL | fn option_by_name(x: Option<std::option::Option<core::option::Option<non_standard::Option<String>>>>) {
+   |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: consider taking a reference instead
+   |
+LL | fn option_by_name(x: Option<std::option::Option<core::option::Option<&non_standard::Option<String>>>>) {
+   |                                                                      +
+
+error: this argument is passed by value, but not consumed in the function body
+  --> tests/ui/needless_pass_by_value.rs:216:18
+   |
+LL | fn non_option(x: OptStr) {
+   |                  ^^^^^^
+   |
+help: consider taking a reference instead
+   |
+LL | fn non_option(x: &OptStr) {
+   |                  +
+
+error: this argument is passed by value, but not consumed in the function body
+  --> tests/ui/needless_pass_by_value.rs:223:25
+   |
+LL | fn non_option_either(x: Opt<String>) {
+   |                         ^^^^^^^^^^^
+   |
+help: consider taking a reference instead
+   |
+LL | fn non_option_either(x: &Opt<String>) {
+   |                         +
 
-error: aborting due to 23 previous errors
+error: aborting due to 27 previous errors
 
diff --git a/tests/ui/needless_return.fixed b/tests/ui/needless_return.fixed
index efc073ebe87..ad625ad6d50 100644
--- a/tests/ui/needless_return.fixed
+++ b/tests/ui/needless_return.fixed
@@ -6,7 +6,8 @@
     clippy::single_match,
     clippy::needless_bool,
     clippy::equatable_if_let,
-    clippy::needless_else
+    clippy::needless_else,
+    clippy::missing_safety_doc
 )]
 #![warn(clippy::needless_return)]
 
@@ -442,3 +443,12 @@ fn b(x: Option<u8>) -> Option<u8> {
         },
     }
 }
+
+unsafe fn todo() -> *const u8 {
+    todo!()
+}
+
+pub unsafe fn issue_12157() -> *const i32 {
+    (unsafe { todo() } as *const i32)
+    //~^ needless_return
+}
diff --git a/tests/ui/needless_return.rs b/tests/ui/needless_return.rs
index 283d86f25fe..41d7e5bdd50 100644
--- a/tests/ui/needless_return.rs
+++ b/tests/ui/needless_return.rs
@@ -6,7 +6,8 @@
     clippy::single_match,
     clippy::needless_bool,
     clippy::equatable_if_let,
-    clippy::needless_else
+    clippy::needless_else,
+    clippy::missing_safety_doc
 )]
 #![warn(clippy::needless_return)]
 
@@ -451,3 +452,12 @@ fn b(x: Option<u8>) -> Option<u8> {
         },
     }
 }
+
+unsafe fn todo() -> *const u8 {
+    todo!()
+}
+
+pub unsafe fn issue_12157() -> *const i32 {
+    return unsafe { todo() } as *const i32;
+    //~^ needless_return
+}
diff --git a/tests/ui/needless_return.stderr b/tests/ui/needless_return.stderr
index 04c97a41d67..80863b9b62b 100644
--- a/tests/ui/needless_return.stderr
+++ b/tests/ui/needless_return.stderr
@@ -1,5 +1,5 @@
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:29:5
+  --> tests/ui/needless_return.rs:30:5
    |
 LL |     return true;
    |     ^^^^^^^^^^^
@@ -13,7 +13,7 @@ LL +     true
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:34:5
+  --> tests/ui/needless_return.rs:35:5
    |
 LL |     return true;
    |     ^^^^^^^^^^^
@@ -25,7 +25,7 @@ LL +     true
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:40:5
+  --> tests/ui/needless_return.rs:41:5
    |
 LL |     return true;;;
    |     ^^^^^^^^^^^
@@ -37,7 +37,7 @@ LL +     true
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:46:5
+  --> tests/ui/needless_return.rs:47:5
    |
 LL |     return true;; ; ;
    |     ^^^^^^^^^^^
@@ -49,7 +49,7 @@ LL +     true
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:52:9
+  --> tests/ui/needless_return.rs:53:9
    |
 LL |         return true;
    |         ^^^^^^^^^^^
@@ -61,7 +61,7 @@ LL +         true
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:55:9
+  --> tests/ui/needless_return.rs:56:9
    |
 LL |         return false;
    |         ^^^^^^^^^^^^
@@ -73,7 +73,7 @@ LL +         false
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:62:17
+  --> tests/ui/needless_return.rs:63:17
    |
 LL |         true => return false,
    |                 ^^^^^^^^^^^^
@@ -85,7 +85,7 @@ LL +         true => false,
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:65:13
+  --> tests/ui/needless_return.rs:66:13
    |
 LL |             return true;
    |             ^^^^^^^^^^^
@@ -97,7 +97,7 @@ LL +             true
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:73:9
+  --> tests/ui/needless_return.rs:74:9
    |
 LL |         return true;
    |         ^^^^^^^^^^^
@@ -109,7 +109,7 @@ LL +         true
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:76:16
+  --> tests/ui/needless_return.rs:77:16
    |
 LL |     let _ = || return true;
    |                ^^^^^^^^^^^
@@ -121,7 +121,7 @@ LL +     let _ = || true;
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:81:5
+  --> tests/ui/needless_return.rs:82:5
    |
 LL |     return the_answer!();
    |     ^^^^^^^^^^^^^^^^^^^^
@@ -133,7 +133,7 @@ LL +     the_answer!()
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:85:21
+  --> tests/ui/needless_return.rs:86:21
    |
 LL |   fn test_void_fun() {
    |  _____________________^
@@ -148,7 +148,7 @@ LL + fn test_void_fun() {
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:91:11
+  --> tests/ui/needless_return.rs:92:11
    |
 LL |       if b {
    |  ___________^
@@ -163,7 +163,7 @@ LL +     if b {
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:94:13
+  --> tests/ui/needless_return.rs:95:13
    |
 LL |       } else {
    |  _____________^
@@ -178,7 +178,7 @@ LL +     } else {
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:103:14
+  --> tests/ui/needless_return.rs:104:14
    |
 LL |         _ => return,
    |              ^^^^^^
@@ -190,7 +190,7 @@ LL +         _ => (),
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:112:24
+  --> tests/ui/needless_return.rs:113:24
    |
 LL |               let _ = 42;
    |  ________________________^
@@ -205,7 +205,7 @@ LL +             let _ = 42;
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:116:14
+  --> tests/ui/needless_return.rs:117:14
    |
 LL |         _ => return,
    |              ^^^^^^
@@ -217,7 +217,7 @@ LL +         _ => (),
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:130:9
+  --> tests/ui/needless_return.rs:131:9
    |
 LL |         return String::from("test");
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -229,7 +229,7 @@ LL +         String::from("test")
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:133:9
+  --> tests/ui/needless_return.rs:134:9
    |
 LL |         return String::new();
    |         ^^^^^^^^^^^^^^^^^^^^
@@ -241,7 +241,7 @@ LL +         String::new()
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:156:32
+  --> tests/ui/needless_return.rs:157:32
    |
 LL |         bar.unwrap_or_else(|_| return)
    |                                ^^^^^^
@@ -253,7 +253,7 @@ LL +         bar.unwrap_or_else(|_| {})
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:161:21
+  --> tests/ui/needless_return.rs:162:21
    |
 LL |           let _ = || {
    |  _____________________^
@@ -268,7 +268,7 @@ LL +         let _ = || {
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:165:20
+  --> tests/ui/needless_return.rs:166:20
    |
 LL |         let _ = || return;
    |                    ^^^^^^
@@ -280,7 +280,7 @@ LL +         let _ = || {};
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:172:32
+  --> tests/ui/needless_return.rs:173:32
    |
 LL |         res.unwrap_or_else(|_| return Foo)
    |                                ^^^^^^^^^^
@@ -292,7 +292,7 @@ LL +         res.unwrap_or_else(|_| Foo)
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:182:5
+  --> tests/ui/needless_return.rs:183:5
    |
 LL |     return true;
    |     ^^^^^^^^^^^
@@ -304,7 +304,7 @@ LL +     true
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:187:5
+  --> tests/ui/needless_return.rs:188:5
    |
 LL |     return true;
    |     ^^^^^^^^^^^
@@ -316,7 +316,7 @@ LL +     true
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:193:9
+  --> tests/ui/needless_return.rs:194:9
    |
 LL |         return true;
    |         ^^^^^^^^^^^
@@ -328,7 +328,7 @@ LL +         true
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:196:9
+  --> tests/ui/needless_return.rs:197:9
    |
 LL |         return false;
    |         ^^^^^^^^^^^^
@@ -340,7 +340,7 @@ LL +         false
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:203:17
+  --> tests/ui/needless_return.rs:204:17
    |
 LL |         true => return false,
    |                 ^^^^^^^^^^^^
@@ -352,7 +352,7 @@ LL +         true => false,
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:206:13
+  --> tests/ui/needless_return.rs:207:13
    |
 LL |             return true;
    |             ^^^^^^^^^^^
@@ -364,7 +364,7 @@ LL +             true
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:214:9
+  --> tests/ui/needless_return.rs:215:9
    |
 LL |         return true;
    |         ^^^^^^^^^^^
@@ -376,7 +376,7 @@ LL +         true
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:217:16
+  --> tests/ui/needless_return.rs:218:16
    |
 LL |     let _ = || return true;
    |                ^^^^^^^^^^^
@@ -388,7 +388,7 @@ LL +     let _ = || true;
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:222:5
+  --> tests/ui/needless_return.rs:223:5
    |
 LL |     return the_answer!();
    |     ^^^^^^^^^^^^^^^^^^^^
@@ -400,7 +400,7 @@ LL +     the_answer!()
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:226:33
+  --> tests/ui/needless_return.rs:227:33
    |
 LL |   async fn async_test_void_fun() {
    |  _________________________________^
@@ -415,7 +415,7 @@ LL + async fn async_test_void_fun() {
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:232:11
+  --> tests/ui/needless_return.rs:233:11
    |
 LL |       if b {
    |  ___________^
@@ -430,7 +430,7 @@ LL +     if b {
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:235:13
+  --> tests/ui/needless_return.rs:236:13
    |
 LL |       } else {
    |  _____________^
@@ -445,7 +445,7 @@ LL +     } else {
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:244:14
+  --> tests/ui/needless_return.rs:245:14
    |
 LL |         _ => return,
    |              ^^^^^^
@@ -457,7 +457,7 @@ LL +         _ => (),
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:258:9
+  --> tests/ui/needless_return.rs:259:9
    |
 LL |         return String::from("test");
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -469,7 +469,7 @@ LL +         String::from("test")
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:261:9
+  --> tests/ui/needless_return.rs:262:9
    |
 LL |         return String::new();
    |         ^^^^^^^^^^^^^^^^^^^^
@@ -481,7 +481,7 @@ LL +         String::new()
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:278:5
+  --> tests/ui/needless_return.rs:279:5
    |
 LL |     return format!("Hello {}", "world!");
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -493,7 +493,7 @@ LL +     format!("Hello {}", "world!")
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:320:9
+  --> tests/ui/needless_return.rs:321:9
    |
 LL |         return true;
    |         ^^^^^^^^^^^
@@ -508,7 +508,7 @@ LL ~     }
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:323:9
+  --> tests/ui/needless_return.rs:324:9
    |
 LL |         return false;
    |         ^^^^^^^^^^^^
@@ -521,7 +521,7 @@ LL ~     }
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:331:13
+  --> tests/ui/needless_return.rs:332:13
    |
 LL |             return 10;
    |             ^^^^^^^^^
@@ -536,7 +536,7 @@ LL ~     }
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:335:13
+  --> tests/ui/needless_return.rs:336:13
    |
 LL |             return 100;
    |             ^^^^^^^^^^
@@ -550,7 +550,7 @@ LL ~     }
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:344:9
+  --> tests/ui/needless_return.rs:345:9
    |
 LL |         return 0;
    |         ^^^^^^^^
@@ -563,7 +563,7 @@ LL ~     }
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:352:13
+  --> tests/ui/needless_return.rs:353:13
    |
 LL |             return *(x as *const isize);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -579,7 +579,7 @@ LL ~     }
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:355:13
+  --> tests/ui/needless_return.rs:356:13
    |
 LL |             return !*(x as *const isize);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -593,7 +593,7 @@ LL ~     }
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:363:20
+  --> tests/ui/needless_return.rs:364:20
    |
 LL |           let _ = 42;
    |  ____________________^
@@ -608,7 +608,7 @@ LL +         let _ = 42;
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:370:20
+  --> tests/ui/needless_return.rs:371:20
    |
 LL |         let _ = 42; return;
    |                    ^^^^^^^
@@ -620,7 +620,7 @@ LL +         let _ = 42;
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:383:9
+  --> tests/ui/needless_return.rs:384:9
    |
 LL |         return Ok(format!("ok!"));
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -632,7 +632,7 @@ LL +         Ok(format!("ok!"))
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:386:9
+  --> tests/ui/needless_return.rs:387:9
    |
 LL |         return Err(format!("err!"));
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -644,7 +644,7 @@ LL +         Err(format!("err!"))
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:393:9
+  --> tests/ui/needless_return.rs:394:9
    |
 LL |         return if true { 1 } else { 2 };
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -656,7 +656,7 @@ LL +         if true { 1 } else { 2 }
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:398:9
+  --> tests/ui/needless_return.rs:399:9
    |
 LL |         return if b1 { 0 } else { 1 } | if b2 { 2 } else { 3 } | if b3 { 4 } else { 5 };
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -668,7 +668,7 @@ LL +         (if b1 { 0 } else { 1 } | if b2 { 2 } else { 3 } | if b3 { 4 } else
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:420:5
+  --> tests/ui/needless_return.rs:421:5
    |
 LL |     return { "a".to_string() } + "b" + { "c" };
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -680,7 +680,7 @@ LL +     ({ "a".to_string() } + "b" + { "c" })
    |
 
 error: unneeded `return` statement
-  --> tests/ui/needless_return.rs:425:5
+  --> tests/ui/needless_return.rs:426:5
    |
 LL |     return "".split("").next().unwrap().to_string();
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -691,5 +691,17 @@ LL -     return "".split("").next().unwrap().to_string();
 LL +     "".split("").next().unwrap().to_string()
    |
 
-error: aborting due to 54 previous errors
+error: unneeded `return` statement
+  --> tests/ui/needless_return.rs:461:5
+   |
+LL |     return unsafe { todo() } as *const i32;
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: remove `return` and wrap the sequence with parentheses
+   |
+LL -     return unsafe { todo() } as *const i32;
+LL +     (unsafe { todo() } as *const i32)
+   |
+
+error: aborting due to 55 previous errors
 
diff --git a/tests/ui/question_mark.fixed b/tests/ui/question_mark.fixed
index 8dfef3202be..41e3910ad48 100644
--- a/tests/ui/question_mark.fixed
+++ b/tests/ui/question_mark.fixed
@@ -375,3 +375,37 @@ fn issue12412(foo: &Foo, bar: &Bar) -> Option<()> {
     //~^^^ question_mark
     Some(())
 }
+
+struct StructWithOptionString {
+    opt_x: Option<String>,
+}
+
+struct WrapperStructWithString(String);
+
+#[allow(clippy::disallowed_names)]
+fn issue_13417(foo: &mut StructWithOptionString) -> Option<String> {
+    let x = foo.opt_x.as_ref()?;
+    //~^^^ question_mark
+    let opt_y = Some(x.clone());
+    std::mem::replace(&mut foo.opt_x, opt_y)
+}
+
+#[allow(clippy::disallowed_names)]
+fn issue_13417_mut(foo: &mut StructWithOptionString) -> Option<String> {
+    let x = foo.opt_x.as_mut()?;
+    //~^^^ question_mark
+    let opt_y = Some(x.clone());
+    std::mem::replace(&mut foo.opt_x, opt_y)
+}
+
+#[allow(clippy::disallowed_names)]
+#[allow(unused)]
+fn issue_13417_weirder(foo: &mut StructWithOptionString, mut bar: Option<WrapperStructWithString>) -> Option<()> {
+    let x @ y = foo.opt_x.as_ref()?;
+    //~^^^ question_mark
+    let x @ &WrapperStructWithString(_) = bar.as_ref()?;
+    //~^^^ question_mark
+    let x @ &mut WrapperStructWithString(_) = bar.as_mut()?;
+    //~^^^ question_mark
+    Some(())
+}
diff --git a/tests/ui/question_mark.rs b/tests/ui/question_mark.rs
index fffaa803f39..e570788bfdf 100644
--- a/tests/ui/question_mark.rs
+++ b/tests/ui/question_mark.rs
@@ -452,3 +452,47 @@ fn issue12412(foo: &Foo, bar: &Bar) -> Option<()> {
     //~^^^ question_mark
     Some(())
 }
+
+struct StructWithOptionString {
+    opt_x: Option<String>,
+}
+
+struct WrapperStructWithString(String);
+
+#[allow(clippy::disallowed_names)]
+fn issue_13417(foo: &mut StructWithOptionString) -> Option<String> {
+    let Some(ref x) = foo.opt_x else {
+        return None;
+    };
+    //~^^^ question_mark
+    let opt_y = Some(x.clone());
+    std::mem::replace(&mut foo.opt_x, opt_y)
+}
+
+#[allow(clippy::disallowed_names)]
+fn issue_13417_mut(foo: &mut StructWithOptionString) -> Option<String> {
+    let Some(ref mut x) = foo.opt_x else {
+        return None;
+    };
+    //~^^^ question_mark
+    let opt_y = Some(x.clone());
+    std::mem::replace(&mut foo.opt_x, opt_y)
+}
+
+#[allow(clippy::disallowed_names)]
+#[allow(unused)]
+fn issue_13417_weirder(foo: &mut StructWithOptionString, mut bar: Option<WrapperStructWithString>) -> Option<()> {
+    let Some(ref x @ ref y) = foo.opt_x else {
+        return None;
+    };
+    //~^^^ question_mark
+    let Some(ref x @ WrapperStructWithString(_)) = bar else {
+        return None;
+    };
+    //~^^^ question_mark
+    let Some(ref mut x @ WrapperStructWithString(_)) = bar else {
+        return None;
+    };
+    //~^^^ question_mark
+    Some(())
+}
diff --git a/tests/ui/question_mark.stderr b/tests/ui/question_mark.stderr
index c4db0fbc302..7c80878fe81 100644
--- a/tests/ui/question_mark.stderr
+++ b/tests/ui/question_mark.stderr
@@ -215,5 +215,45 @@ LL | |         return None;
 LL | |     };
    | |______^ help: replace it with: `let v = bar.foo.owned.clone()?;`
 
-error: aborting due to 22 previous errors
+error: this `let...else` may be rewritten with the `?` operator
+  --> tests/ui/question_mark.rs:464:5
+   |
+LL | /     let Some(ref x) = foo.opt_x else {
+LL | |         return None;
+LL | |     };
+   | |______^ help: replace it with: `let x = foo.opt_x.as_ref()?;`
+
+error: this `let...else` may be rewritten with the `?` operator
+  --> tests/ui/question_mark.rs:474:5
+   |
+LL | /     let Some(ref mut x) = foo.opt_x else {
+LL | |         return None;
+LL | |     };
+   | |______^ help: replace it with: `let x = foo.opt_x.as_mut()?;`
+
+error: this `let...else` may be rewritten with the `?` operator
+  --> tests/ui/question_mark.rs:485:5
+   |
+LL | /     let Some(ref x @ ref y) = foo.opt_x else {
+LL | |         return None;
+LL | |     };
+   | |______^ help: replace it with: `let x @ y = foo.opt_x.as_ref()?;`
+
+error: this `let...else` may be rewritten with the `?` operator
+  --> tests/ui/question_mark.rs:489:5
+   |
+LL | /     let Some(ref x @ WrapperStructWithString(_)) = bar else {
+LL | |         return None;
+LL | |     };
+   | |______^ help: replace it with: `let x @ &WrapperStructWithString(_) = bar.as_ref()?;`
+
+error: this `let...else` may be rewritten with the `?` operator
+  --> tests/ui/question_mark.rs:493:5
+   |
+LL | /     let Some(ref mut x @ WrapperStructWithString(_)) = bar else {
+LL | |         return None;
+LL | |     };
+   | |______^ help: replace it with: `let x @ &mut WrapperStructWithString(_) = bar.as_mut()?;`
+
+error: aborting due to 27 previous errors