Repository: rust-lang/rust-clippy
Pull request #14637: `match_same_arms`, `ifs_same_cond`: lint once per same arm/condition
Author: login: Alexendoo
Labels: name: S-waiting-on-review
description: Status: Awaiting review from the assignee but also interested parties
Created: 2025-04-16T22:27:49Z | Merged: 2025-05-16T14:23:55Z

A large fraction of the lints emitted in CI lintcheck come from [this `match`](https://github.com/unicode-rs/unicode-normalization/blob/v0.1.23/src/tables.rs#L34289), currently for `n` same arms `((n - 1) * n)/2` lints are emitted, with this change it will be emitted as a single lint

Also fixes https://github.com/rust-lang/rust-clippy/issues/13835

changelog: none



Diff:
diff --git a/clippy_lints/src/copies.rs b/clippy_lints/src/copies.rs
index 42fbe6438d4e..48bf3dafa6ef 100644
--- a/clippy_lints/src/copies.rs
+++ b/clippy_lints/src/copies.rs
@@ -1,5 +1,5 @@
 use clippy_config::Conf;
-use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_then};
+use clippy_utils::diagnostics::{span_lint, span_lint_and_note, span_lint_and_then};
 use clippy_utils::source::{IntoSpan, SpanRangeExt, first_line_of_span, indent_of, reindent_multiline, snippet};
 use clippy_utils::ty::{InteriorMut, needs_ordered_drop};
 use clippy_utils::visitors::for_each_expr_without_closures;
@@ -567,7 +567,7 @@ fn method_caller_is_mutable<'tcx>(
 
 /// Implementation of `IFS_SAME_COND`.
 fn lint_same_cond<'tcx>(cx: &LateContext<'tcx>, conds: &[&Expr<'_>], interior_mut: &mut InteriorMut<'tcx>) {
-    for (i, j) in search_same(
+    for group in search_same(
         conds,
         |e| hash_expr(cx, e),
         |lhs, rhs| {
@@ -584,14 +584,8 @@ fn lint_same_cond<'tcx>(cx: &LateContext<'tcx>, conds: &[&Expr<'_>], interior_mu
             }
         },
     ) {
-        span_lint_and_note(
-            cx,
-            IFS_SAME_COND,
-            j.span,
-            "this `if` has the same condition as a previous `if`",
-            Some(i.span),
-            "same as this",
-        );
+        let spans: Vec<_> = group.into_iter().map(|expr| expr.span).collect();
+        span_lint(cx, IFS_SAME_COND, spans, "these `if` branches have the same condition");
     }
 }
 
@@ -609,14 +603,13 @@ fn lint_same_fns_in_if_cond(cx: &LateContext<'_>, conds: &[&Expr<'_>]) {
         SpanlessEq::new(cx).eq_expr(lhs, rhs)
     };
 
-    for (i, j) in search_same(conds, |e| hash_expr(cx, e), eq) {
-        span_lint_and_note(
+    for group in search_same(conds, |e| hash_expr(cx, e), eq) {
+        let spans: Vec<_> = group.into_iter().map(|expr| expr.span).collect();
+        span_lint(
             cx,
             SAME_FUNCTIONS_IN_IF_CONDITION,
-            j.span,
-            "this `if` has the same function call as a previous `if`",
-            Some(i.span),
-            "same as this",
+            spans,
+            "these `if` branches have the same function call",
         );
     }
 }
diff --git a/clippy_lints/src/matches/match_same_arms.rs b/clippy_lints/src/matches/match_same_arms.rs
index 250f17fa9025..6de5b5efef0f 100644
--- a/clippy_lints/src/matches/match_same_arms.rs
+++ b/clippy_lints/src/matches/match_same_arms.rs
@@ -1,8 +1,9 @@
-use clippy_utils::diagnostics::span_lint_hir_and_then;
-use clippy_utils::source::snippet_with_applicability;
-use clippy_utils::{SpanlessEq, SpanlessHash, is_lint_allowed, path_to_local, search_same};
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::SpanRangeExt;
+use clippy_utils::{SpanlessEq, SpanlessHash, fulfill_or_allowed, is_lint_allowed, path_to_local, search_same};
 use core::cmp::Ordering;
 use core::{iter, slice};
+use itertools::Itertools;
 use rustc_arena::DroplessArena;
 use rustc_ast::ast::LitKind;
 use rustc_errors::Applicability;
@@ -110,57 +111,68 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) {
             && check_same_body()
     };
 
-    let mut appl = Applicability::MaybeIncorrect;
     let indexed_arms: Vec<(usize, &Arm<'_>)> = arms.iter().enumerate().collect();
-    for (&(i, arm1), &(j, arm2)) in search_same(&indexed_arms, hash, eq) {
-        if matches!(arm2.pat.kind, PatKind::Wild) {
-            if !cx.tcx.features().non_exhaustive_omitted_patterns_lint()
-                || is_lint_allowed(cx, NON_EXHAUSTIVE_OMITTED_PATTERNS, arm2.hir_id)
-            {
-                let arm_span = adjusted_arm_span(cx, arm1.span);
-                span_lint_hir_and_then(
-                    cx,
-                    MATCH_SAME_ARMS,
-                    arm1.hir_id,
-                    arm_span,
-                    "this match arm has an identical body to the `_` wildcard arm",
-                    |diag| {
-                        diag.span_suggestion(arm_span, "try removing the arm", "", appl)
-                            .help("or try changing either arm body")
-                            .span_note(arm2.span, "`_` wildcard arm here");
-                    },
-                );
-            }
-        } else {
-            let back_block = backwards_blocking_idxs[j];
-            let (keep_arm, move_arm) = if back_block < i || (back_block == 0 && forwards_blocking_idxs[i] <= j) {
-                (arm1, arm2)
-            } else {
-                (arm2, arm1)
-            };
-
-            span_lint_hir_and_then(
-                cx,
-                MATCH_SAME_ARMS,
-                keep_arm.hir_id,
-                keep_arm.span,
-                "this match arm has an identical body to another arm",
-                |diag| {
-                    let move_pat_snip = snippet_with_applicability(cx, move_arm.pat.span, "<pat2>", &mut appl);
-                    let keep_pat_snip = snippet_with_applicability(cx, keep_arm.pat.span, "<pat1>", &mut appl);
+    for mut group in search_same(&indexed_arms, hash, eq) {
+        // Filter out (and fulfill) `#[allow]`ed and `#[expect]`ed arms
+        group.retain(|(_, arm)| !fulfill_or_allowed(cx, MATCH_SAME_ARMS, [arm.hir_id]));
 
-                    diag.multipart_suggestion(
-                        "or try merging the arm patterns and removing the obsolete arm",
-                        vec![
-                            (keep_arm.pat.span, format!("{keep_pat_snip} | {move_pat_snip}")),
-                            (adjusted_arm_span(cx, move_arm.span), String::new()),
-                        ],
-                        appl,
-                    )
-                    .help("try changing either arm body");
-                },
-            );
+        if group.len() < 2 {
+            continue;
         }
+
+        span_lint_and_then(
+            cx,
+            MATCH_SAME_ARMS,
+            group.iter().map(|(_, arm)| arm.span).collect_vec(),
+            "these match arms have identical bodies",
+            |diag| {
+                diag.help("if this is unintentional make the arms return different values");
+
+                if let [prev @ .., (_, last)] = group.as_slice()
+                    && is_wildcard_arm(last.pat)
+                    && is_lint_allowed(cx, NON_EXHAUSTIVE_OMITTED_PATTERNS, last.hir_id)
+                {
+                    diag.span_label(last.span, "the wildcard arm");
+
+                    let s = if prev.len() > 1 { "s" } else { "" };
+                    diag.multipart_suggestion_verbose(
+                        format!("otherwise remove the non-wildcard arm{s}"),
+                        prev.iter()
+                            .map(|(_, arm)| (adjusted_arm_span(cx, arm.span), String::new()))
+                            .collect(),
+                        Applicability::MaybeIncorrect,
+                    );
+                } else if let &[&(first_idx, _), .., &(last_idx, _)] = group.as_slice() {
+                    let back_block = backwards_blocking_idxs[last_idx];
+                    let split = if back_block < first_idx
+                        || (back_block == 0 && forwards_blocking_idxs[first_idx] <= last_idx)
+                    {
+                        group.split_first()
+                    } else {
+                        group.split_last()
+                    };
+
+                    if let Some(((_, dest), src)) = split
+                        && let Some(pat_snippets) = group
+                            .iter()
+                            .map(|(_, arm)| arm.pat.span.get_source_text(cx))
+                            .collect::<Option<Vec<_>>>()
+                    {
+                        let mut suggs = src
+                            .iter()
+                            .map(|(_, arm)| (adjusted_arm_span(cx, arm.span), String::new()))
+                            .collect_vec();
+
+                        suggs.push((dest.pat.span, pat_snippets.iter().join(" | ")));
+                        diag.multipart_suggestion_verbose(
+                            "otherwise merge the patterns into a single arm",
+                            suggs,
+                            Applicability::MaybeIncorrect,
+                        );
+                    }
+                }
+            },
+        );
     }
 }
 
@@ -449,3 +461,11 @@ fn bindings_eq(pat: &Pat<'_>, mut ids: HirIdSet) -> bool {
     pat.each_binding_or_first(&mut |_, id, _, _| result &= ids.swap_remove(&id));
     result && ids.is_empty()
 }
+
+fn is_wildcard_arm(pat: &Pat<'_>) -> bool {
+    match pat.kind {
+        PatKind::Wild => true,
+        PatKind::Or([.., last]) => matches!(last.kind, PatKind::Wild),
+        _ => false,
+    }
+}
diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs
index 551266da0294..cf31cc5281e4 100644
--- a/clippy_utils/src/lib.rs
+++ b/clippy_utils/src/lib.rs
@@ -27,6 +27,7 @@
 
 // FIXME: switch to something more ergonomic here, once available.
 // (Currently there is no way to opt into sysroot crates without `extern crate`.)
+extern crate indexmap;
 extern crate rustc_abi;
 extern crate rustc_ast;
 extern crate rustc_attr_parsing;
@@ -85,7 +86,6 @@ pub use self::hir_utils::{
 use core::mem;
 use core::ops::ControlFlow;
 use std::collections::hash_map::Entry;
-use std::hash::BuildHasherDefault;
 use std::iter::{once, repeat_n};
 use std::sync::{Mutex, MutexGuard, OnceLock};
 
@@ -95,7 +95,7 @@ use rustc_ast::ast::{self, LitKind, RangeLimits};
 use rustc_attr_parsing::{AttributeKind, find_attr};
 use rustc_data_structures::fx::FxHashMap;
 use rustc_data_structures::packed::Pu128;
-use rustc_data_structures::unhash::UnhashMap;
+use rustc_data_structures::unhash::UnindexMap;
 use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk};
 use rustc_hir::def::{DefKind, Res};
 use rustc_hir::def_id::{CrateNum, DefId, LOCAL_CRATE, LocalDefId, LocalModDefId};
@@ -2486,45 +2486,46 @@ pub fn is_slice_of_primitives(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<S
     None
 }
 
-/// Returns list of all pairs `(a, b)` where `eq(a, b) == true`
-/// and `a` is before `b` in `exprs` for all `a` and `b` in
-/// `exprs`
+/// Returns a list of groups where elements in each group are equal according to `eq`
+///
+/// - Within each group the elements are sorted by the order they appear in `exprs`
+/// - The groups themselves are sorted by their first element's appearence in `exprs`
 ///
 /// Given functions `eq` and `hash` such that `eq(a, b) == true`
 /// implies `hash(a) == hash(b)`
-pub fn search_same<T, Hash, Eq>(exprs: &[T], mut hash: Hash, mut eq: Eq) -> Vec<(&T, &T)>
+pub fn search_same<T, Hash, Eq>(exprs: &[T], mut hash: Hash, mut eq: Eq) -> Vec<Vec<&T>>
 where
     Hash: FnMut(&T) -> u64,
     Eq: FnMut(&T, &T) -> bool,
 {
     match exprs {
-        [a, b] if eq(a, b) => return vec![(a, b)],
+        [a, b] if eq(a, b) => return vec![vec![a, b]],
         _ if exprs.len() <= 2 => return vec![],
         _ => {},
     }
 
-    let mut match_expr_list: Vec<(&T, &T)> = Vec::new();
-
-    let mut map: UnhashMap<u64, Vec<&_>> =
-        UnhashMap::with_capacity_and_hasher(exprs.len(), BuildHasherDefault::default());
+    let mut buckets: UnindexMap<u64, Vec<Vec<&T>>> = UnindexMap::default();
 
     for expr in exprs {
-        match map.entry(hash(expr)) {
-            Entry::Occupied(mut o) => {
-                for o in o.get() {
-                    if eq(o, expr) {
-                        match_expr_list.push((o, expr));
-                    }
+        match buckets.entry(hash(expr)) {
+            indexmap::map::Entry::Occupied(mut o) => {
+                let bucket = o.get_mut();
+                match bucket.iter_mut().find(|group| eq(expr, group[0])) {
+                    Some(group) => group.push(expr),
+                    None => bucket.push(vec![expr]),
                 }
-                o.get_mut().push(expr);
             },
-            Entry::Vacant(v) => {
-                v.insert(vec![expr]);
+            indexmap::map::Entry::Vacant(v) => {
+                v.insert(vec![vec![expr]]);
             },
         }
     }
 
-    match_expr_list
+    buckets
+        .into_values()
+        .flatten()
+        .filter(|group| group.len() > 1)
+        .collect()
 }
 
 /// Peels off all references on the pattern. Returns the underlying pattern and the number of
diff --git a/tests/ui-toml/ifs_same_cond/ifs_same_cond.rs b/tests/ui-toml/ifs_same_cond/ifs_same_cond.rs
index e953a2a4e90b..2a6097fb5795 100644
--- a/tests/ui-toml/ifs_same_cond/ifs_same_cond.rs
+++ b/tests/ui-toml/ifs_same_cond/ifs_same_cond.rs
@@ -11,9 +11,9 @@ fn issue10272() {
     // should trigger warning
     let x = Cell::new(true);
     if x.get() {
+        //~^ ifs_same_cond
     } else if !x.take() {
     } else if x.get() {
-        //~^ ifs_same_cond
     } else {
     }
 }
diff --git a/tests/ui-toml/ifs_same_cond/ifs_same_cond.stderr b/tests/ui-toml/ifs_same_cond/ifs_same_cond.stderr
index d67e7fca6565..adc44358c4c7 100644
--- a/tests/ui-toml/ifs_same_cond/ifs_same_cond.stderr
+++ b/tests/ui-toml/ifs_same_cond/ifs_same_cond.stderr
@@ -1,14 +1,12 @@
-error: this `if` has the same condition as a previous `if`
-  --> tests/ui-toml/ifs_same_cond/ifs_same_cond.rs:15:15
-   |
-LL |     } else if x.get() {
-   |               ^^^^^^^
-   |
-note: same as this
+error: these `if` branches have the same condition
   --> tests/ui-toml/ifs_same_cond/ifs_same_cond.rs:13:8
    |
 LL |     if x.get() {
    |        ^^^^^^^
+...
+LL |     } else if x.get() {
+   |               ^^^^^^^
+   |
    = note: `-D clippy::ifs-same-cond` implied by `-D warnings`
    = help: to override `-D warnings` add `#[allow(clippy::ifs_same_cond)]`
 
diff --git a/tests/ui/ifs_same_cond.rs b/tests/ui/ifs_same_cond.rs
index ebc3acb1b77f..7067434953d6 100644
--- a/tests/ui/ifs_same_cond.rs
+++ b/tests/ui/ifs_same_cond.rs
@@ -6,19 +6,25 @@ fn ifs_same_cond() {
     let b = false;
 
     if b {
+        //~^ ifs_same_cond
     } else if b {
+    }
+
+    if b {
         //~^ ifs_same_cond
+    } else if b {
+    } else if b {
     }
 
     if a == 1 {
-    } else if a == 1 {
         //~^ ifs_same_cond
+    } else if a == 1 {
     }
 
     if 2 * a == 1 {
+        //~^ ifs_same_cond
     } else if 2 * a == 2 {
     } else if 2 * a == 1 {
-        //~^ ifs_same_cond
     } else if a == 1 {
     }
 
@@ -50,8 +56,8 @@ fn ifs_same_cond() {
 fn issue10272() {
     let a = String::from("ha");
     if a.contains("ah") {
-    } else if a.contains("ah") {
         //~^ ifs_same_cond
+    } else if a.contains("ah") {
 
         // Trigger this lint
     } else if a.contains("ha") {
diff --git a/tests/ui/ifs_same_cond.stderr b/tests/ui/ifs_same_cond.stderr
index df21e6f1b826..7acbc1a6399b 100644
--- a/tests/ui/ifs_same_cond.stderr
+++ b/tests/ui/ifs_same_cond.stderr
@@ -1,52 +1,52 @@
-error: this `if` has the same condition as a previous `if`
-  --> tests/ui/ifs_same_cond.rs:9:15
-   |
-LL |     } else if b {
-   |               ^
-   |
-note: same as this
+error: these `if` branches have the same condition
   --> tests/ui/ifs_same_cond.rs:8:8
    |
 LL |     if b {
    |        ^
+LL |
+LL |     } else if b {
+   |               ^
+   |
    = note: `-D clippy::ifs-same-cond` implied by `-D warnings`
    = help: to override `-D warnings` add `#[allow(clippy::ifs_same_cond)]`
 
-error: this `if` has the same condition as a previous `if`
-  --> tests/ui/ifs_same_cond.rs:14:15
-   |
-LL |     } else if a == 1 {
-   |               ^^^^^^
-   |
-note: same as this
+error: these `if` branches have the same condition
   --> tests/ui/ifs_same_cond.rs:13:8
    |
+LL |     if b {
+   |        ^
+LL |
+LL |     } else if b {
+   |               ^
+LL |     } else if b {
+   |               ^
+
+error: these `if` branches have the same condition
+  --> tests/ui/ifs_same_cond.rs:19:8
+   |
 LL |     if a == 1 {
    |        ^^^^^^
+LL |
+LL |     } else if a == 1 {
+   |               ^^^^^^
 
-error: this `if` has the same condition as a previous `if`
-  --> tests/ui/ifs_same_cond.rs:20:15
-   |
-LL |     } else if 2 * a == 1 {
-   |               ^^^^^^^^^^
-   |
-note: same as this
-  --> tests/ui/ifs_same_cond.rs:18:8
+error: these `if` branches have the same condition
+  --> tests/ui/ifs_same_cond.rs:24:8
    |
 LL |     if 2 * a == 1 {
    |        ^^^^^^^^^^
+...
+LL |     } else if 2 * a == 1 {
+   |               ^^^^^^^^^^
 
-error: this `if` has the same condition as a previous `if`
-  --> tests/ui/ifs_same_cond.rs:53:15
-   |
-LL |     } else if a.contains("ah") {
-   |               ^^^^^^^^^^^^^^^^
-   |
-note: same as this
-  --> tests/ui/ifs_same_cond.rs:52:8
+error: these `if` branches have the same condition
+  --> tests/ui/ifs_same_cond.rs:58:8
    |
 LL |     if a.contains("ah") {
    |        ^^^^^^^^^^^^^^^^
+LL |
+LL |     } else if a.contains("ah") {
+   |               ^^^^^^^^^^^^^^^^
 
-error: aborting due to 4 previous errors
+error: aborting due to 5 previous errors
 
diff --git a/tests/ui/match_same_arms.fixed b/tests/ui/match_same_arms.fixed
new file mode 100644
index 000000000000..31684a5759fe
--- /dev/null
+++ b/tests/ui/match_same_arms.fixed
@@ -0,0 +1,142 @@
+#![allow(clippy::manual_range_patterns)]
+#![warn(clippy::match_same_arms)]
+
+pub enum Abc {
+    A,
+    B,
+    C,
+}
+
+fn match_same_arms() {
+    let _ = match Abc::A {
+        Abc::B => 1,
+        _ => 0,
+        //~^ match_same_arms
+    };
+
+    match 0 {
+        1 => 'a',
+        _ => 'b',
+        //~^ match_same_arms
+    };
+
+    match (1, 2, 3) {
+        (1, .., 3) | (.., 3) => 42,
+        //~^ match_same_arms
+        _ => 0,
+    };
+
+    let _ = match 42 {
+        //~^ match_same_arms
+        42 | 51 => 1,
+        41 | 52 => 2,
+        //~^ match_same_arms
+        _ => 0,
+    };
+
+    let _ = match 42 {
+        //~^ match_same_arms
+        1 | 2 | 3 => 2,
+        4 => 3,
+        _ => 0,
+    };
+}
+
+mod issue4244 {
+    #[derive(PartialEq, PartialOrd, Eq, Ord)]
+    pub enum CommandInfo {
+        BuiltIn { name: String, about: Option<String> },
+        External { name: String, path: std::path::PathBuf },
+    }
+
+    impl CommandInfo {
+        pub fn name(&self) -> String {
+            match self {
+                //~^ match_same_arms
+                CommandInfo::BuiltIn { name, .. } | CommandInfo::External { name, .. } => name.to_string(),
+            }
+        }
+    }
+}
+
+macro_rules! m {
+    (foo) => {};
+    (bar) => {};
+}
+macro_rules! foo {
+    () => {
+        1
+    };
+}
+macro_rules! bar {
+    () => {
+        1
+    };
+}
+
+fn main() {
+    let x = 0;
+    let _ = match 0 {
+        0 => {
+            m!(foo);
+            x
+        },
+        1 => {
+            m!(bar);
+            x
+        },
+        _ => 1,
+    };
+
+    let _ = match 0 {
+        0 => {
+            m!(foo);
+            0
+        },
+        1 => {
+            m!(bar);
+            0
+        },
+        _ => 1,
+    };
+
+    let _ = match 0 {
+        0 => {
+            let mut x = 0;
+            #[cfg(not_enabled)]
+            {
+                x = 5;
+            }
+            #[cfg(not(not_enabled))]
+            {
+                x = 6;
+            }
+            x
+        },
+        1 => {
+            let mut x = 0;
+            #[cfg(also_not_enabled)]
+            {
+                x = 5;
+            }
+            #[cfg(not(also_not_enabled))]
+            {
+                x = 6;
+            }
+            x
+        },
+        _ => 0,
+    };
+
+    let _ = match 0 {
+        0 => foo!(),
+        1 => bar!(),
+        _ => 1,
+    };
+
+    let _ = match 0 {
+        0 => cfg!(not_enabled),
+        1 => cfg!(also_not_enabled),
+        _ => false,
+    };
+}
diff --git a/tests/ui/match_same_arms.rs b/tests/ui/match_same_arms.rs
index 55441948e91b..39bee01bac22 100644
--- a/tests/ui/match_same_arms.rs
+++ b/tests/ui/match_same_arms.rs
@@ -1,4 +1,4 @@
-//@no-rustfix: overlapping suggestions
+#![allow(clippy::manual_range_patterns)]
 #![warn(clippy::match_same_arms)]
 
 pub enum Abc {
@@ -10,9 +10,17 @@ pub enum Abc {
 fn match_same_arms() {
     let _ = match Abc::A {
         Abc::A => 0,
-        //~^ match_same_arms
         Abc::B => 1,
         _ => 0,
+        //~^ match_same_arms
+    };
+
+    match 0 {
+        1 => 'a',
+        2 => 'b',
+        3 => 'b',
+        _ => 'b',
+        //~^ match_same_arms
     };
 
     match (1, 2, 3) {
@@ -24,8 +32,8 @@ fn match_same_arms() {
 
     let _ = match 42 {
         42 => 1,
-        51 => 1,
         //~^ match_same_arms
+        51 => 1,
         41 => 2,
         //~^ match_same_arms
         52 => 2,
@@ -34,11 +42,9 @@ fn match_same_arms() {
 
     let _ = match 42 {
         1 => 2,
-        2 => 2,
         //~^ match_same_arms
-        //~| match_same_arms
+        2 => 2,
         3 => 2,
-        //~^ match_same_arms
         4 => 3,
         _ => 0,
     };
@@ -55,8 +61,8 @@ mod issue4244 {
         pub fn name(&self) -> String {
             match self {
                 CommandInfo::BuiltIn { name, .. } => name.to_string(),
-                CommandInfo::External { name, .. } => name.to_string(),
                 //~^ match_same_arms
+                CommandInfo::External { name, .. } => name.to_string(),
             }
         }
     }
diff --git a/tests/ui/match_same_arms.stderr b/tests/ui/match_same_arms.stderr
index 3744b83d89cc..8aa60f835766 100644
--- a/tests/ui/match_same_arms.stderr
+++ b/tests/ui/match_same_arms.stderr
@@ -1,118 +1,121 @@
-error: this match arm has an identical body to the `_` wildcard arm
+error: these match arms have identical bodies
   --> tests/ui/match_same_arms.rs:12:9
    |
-LL | /         Abc::A => 0,
-LL | |
-   | |________^ help: try removing the arm
-   |
-   = help: or try changing either arm body
-note: `_` wildcard arm here
-  --> tests/ui/match_same_arms.rs:15:9
-   |
+LL |         Abc::A => 0,
+   |         ^^^^^^^^^^^
+LL |         Abc::B => 1,
 LL |         _ => 0,
-   |         ^^^^^^
+   |         ^^^^^^ the wildcard arm
+   |
+   = help: if this is unintentional make the arms return different values
    = note: `-D clippy::match-same-arms` implied by `-D warnings`
    = help: to override `-D warnings` add `#[allow(clippy::match_same_arms)]`
+help: otherwise remove the non-wildcard arm
+   |
+LL -         Abc::A => 0,
+   |
 
-error: this match arm has an identical body to another arm
-  --> tests/ui/match_same_arms.rs:19:9
+error: these match arms have identical bodies
+  --> tests/ui/match_same_arms.rs:20:9
+   |
+LL |         2 => 'b',
+   |         ^^^^^^^^
+LL |         3 => 'b',
+   |         ^^^^^^^^
+LL |         _ => 'b',
+   |         ^^^^^^^^ the wildcard arm
+   |
+   = help: if this is unintentional make the arms return different values
+help: otherwise remove the non-wildcard arms
+   |
+LL -         2 => 'b',
+LL -         3 => 'b',
+LL +         _ => 'b',
+   |
+
+error: these match arms have identical bodies
+  --> tests/ui/match_same_arms.rs:27:9
    |
 LL |         (1, .., 3) => 42,
    |         ^^^^^^^^^^^^^^^^
+LL |
+LL |         (.., 3) => 42,
+   |         ^^^^^^^^^^^^^
    |
-   = help: try changing either arm body
-help: or try merging the arm patterns and removing the obsolete arm
+   = help: if this is unintentional make the arms return different values
+help: otherwise merge the patterns into a single arm
    |
 LL ~         (1, .., 3) | (.., 3) => 42,
 LL |
 LL ~         _ => 0,
    |
 
-error: this match arm has an identical body to another arm
-  --> tests/ui/match_same_arms.rs:27:9
+error: these match arms have identical bodies
+  --> tests/ui/match_same_arms.rs:34:9
    |
+LL |         42 => 1,
+   |         ^^^^^^^
+LL |
 LL |         51 => 1,
    |         ^^^^^^^
    |
-   = help: try changing either arm body
-help: or try merging the arm patterns and removing the obsolete arm
+   = help: if this is unintentional make the arms return different values
+help: otherwise merge the patterns into a single arm
    |
-LL -         42 => 1,
-LL -         51 => 1,
-LL +         51 | 42 => 1,
+LL ~
+LL ~         42 | 51 => 1,
    |
 
-error: this match arm has an identical body to another arm
-  --> tests/ui/match_same_arms.rs:29:9
+error: these match arms have identical bodies
+  --> tests/ui/match_same_arms.rs:37:9
    |
 LL |         41 => 2,
    |         ^^^^^^^
+LL |
+LL |         52 => 2,
+   |         ^^^^^^^
    |
-   = help: try changing either arm body
-help: or try merging the arm patterns and removing the obsolete arm
+   = help: if this is unintentional make the arms return different values
+help: otherwise merge the patterns into a single arm
    |
 LL ~         41 | 52 => 2,
 LL |
 LL ~         _ => 0,
    |
 
-error: this match arm has an identical body to another arm
-  --> tests/ui/match_same_arms.rs:37:9
-   |
-LL |         2 => 2,
-   |         ^^^^^^
-   |
-   = help: try changing either arm body
-help: or try merging the arm patterns and removing the obsolete arm
-   |
-LL -         1 => 2,
-LL -         2 => 2,
-LL +         2 | 1 => 2,
-   |
-
-error: this match arm has an identical body to another arm
-  --> tests/ui/match_same_arms.rs:40:9
+error: these match arms have identical bodies
+  --> tests/ui/match_same_arms.rs:44:9
    |
-LL |         3 => 2,
+LL |         1 => 2,
    |         ^^^^^^
-   |
-   = help: try changing either arm body
-help: or try merging the arm patterns and removing the obsolete arm
-   |
-LL ~         2 => 2,
-LL |
 LL |
-LL ~         3 | 1 => 2,
-   |
-
-error: this match arm has an identical body to another arm
-  --> tests/ui/match_same_arms.rs:37:9
-   |
 LL |         2 => 2,
    |         ^^^^^^
+LL |         3 => 2,
+   |         ^^^^^^
    |
-   = help: try changing either arm body
-help: or try merging the arm patterns and removing the obsolete arm
+   = help: if this is unintentional make the arms return different values
+help: otherwise merge the patterns into a single arm
    |
-LL ~         2 | 3 => 2,
-LL |
-LL |
 LL ~
+LL ~         1 | 2 | 3 => 2,
    |
 
-error: this match arm has an identical body to another arm
-  --> tests/ui/match_same_arms.rs:58:17
+error: these match arms have identical bodies
+  --> tests/ui/match_same_arms.rs:63:17
    |
+LL |                 CommandInfo::BuiltIn { name, .. } => name.to_string(),
+   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |
 LL |                 CommandInfo::External { name, .. } => name.to_string(),
    |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
-   = help: try changing either arm body
-help: or try merging the arm patterns and removing the obsolete arm
+   = help: if this is unintentional make the arms return different values
+help: otherwise merge the patterns into a single arm
    |
-LL -                 CommandInfo::BuiltIn { name, .. } => name.to_string(),
-LL -                 CommandInfo::External { name, .. } => name.to_string(),
-LL +                 CommandInfo::External { name, .. } | CommandInfo::BuiltIn { name, .. } => name.to_string(),
+LL ~
+LL ~                 CommandInfo::BuiltIn { name, .. } | CommandInfo::External { name, .. } => name.to_string(),
    |
 
-error: aborting due to 8 previous errors
+error: aborting due to 7 previous errors
 
diff --git a/tests/ui/match_same_arms2.fixed b/tests/ui/match_same_arms2.fixed
index 0d93e2c728d7..cb860cef1e68 100644
--- a/tests/ui/match_same_arms2.fixed
+++ b/tests/ui/match_same_arms2.fixed
@@ -14,7 +14,7 @@ fn foo() -> bool {
 
 fn match_same_arms() {
     let _ = match 42 {
-        //~^^^^^^^^^ match_same_arms
+        //~v match_same_arms
         _ => {
             foo();
             let mut a = 42 + [23].len() as i32;
@@ -27,14 +27,14 @@ fn match_same_arms() {
     };
 
     let _ = match 42 {
-        51 | 42 => foo(),
         //~^ match_same_arms
+        42 | 51 => foo(),
         _ => true,
     };
 
     let _ = match Some(42) {
-        None | Some(_) => 24,
         //~^ match_same_arms
+        Some(_) | None => 24,
     };
 
     let _ = match Some(42) {
@@ -55,8 +55,8 @@ fn match_same_arms() {
     };
 
     match (Some(42), Some(42)) {
-        (None, Some(a)) | (Some(a), None) => bar(a),
         //~^ match_same_arms
+        (Some(a), None) | (None, Some(a)) => bar(a),
         _ => (),
     }
 
@@ -69,8 +69,8 @@ fn match_same_arms() {
     };
 
     let _ = match (Some(42), Some(42)) {
-        (None, Some(a)) | (Some(a), None) if a == 42 => a,
         //~^ match_same_arms
+        (Some(a), None) | (None, Some(a)) if a == 42 => a,
         _ => 0,
     };
 
@@ -124,8 +124,8 @@ fn match_same_arms() {
     // False negative #2251.
     match x {
         Ok(_tmp) => println!("ok"),
-        Ok(_) | Ok(3) => println!("ok"),
         //~^ match_same_arms
+        Ok(3) | Ok(_) => println!("ok"),
         Err(_) => {
             unreachable!();
         },
@@ -149,10 +149,10 @@ fn match_same_arms() {
 
     // still lint if the tokens are the same
     match 0 {
-        1 | 0 => {
+        //~^^^ match_same_arms
+        0 | 1 => {
             empty!(0);
         },
-        //~^^^ match_same_arms
         x => {
             empty!(x);
         },
@@ -208,9 +208,9 @@ fn main() {
 
     // Suggest moving `Foo::X(0)` down.
     let _ = match Foo::X(0) {
-        Foo::Y(_) | Foo::Z(0) => 2,
-        Foo::Z(_) | Foo::X(0) => 1,
         //~^ match_same_arms
+        Foo::Y(_) | Foo::Z(0) => 2,
+        Foo::X(0) | Foo::Z(_) => 1,
         _ => 0,
     };
 
@@ -230,10 +230,10 @@ fn main() {
 
     // Lint.
     let _ = match None {
+        //~^ match_same_arms
         Some(Bar { y: 10, z: 0, .. }) => 2,
         None => 50,
-        Some(Bar { y: 0, x: 5, .. }) | Some(Bar { x: 0, y: 5, .. }) => 1,
-        //~^ match_same_arms
+        Some(Bar { x: 0, y: 5, .. }) | Some(Bar { y: 0, x: 5, .. }) => 1,
         _ => 200,
     };
 
@@ -246,8 +246,8 @@ fn main() {
     };
 
     let _ = match 0 {
-        1 | 0 => cfg!(not_enable),
         //~^ match_same_arms
+        0 | 1 => cfg!(not_enable),
         _ => false,
     };
 }
@@ -262,9 +262,34 @@ mod with_lifetime {
     impl<'a> MaybeStaticStr<'a> {
         fn get(&self) -> &'a str {
             match *self {
-                MaybeStaticStr::Borrowed(s) | MaybeStaticStr::Static(s) => s,
                 //~^ match_same_arms
+                MaybeStaticStr::Static(s) | MaybeStaticStr::Borrowed(s) => s,
             }
         }
     }
 }
+
+fn lint_levels() {
+    match 1 {
+        0 => "a",
+        1 => "b",
+        #[expect(clippy::match_same_arms)]
+        _ => "b",
+    };
+
+    match 2 {
+        0 => "a",
+        1 | 2 => "b",
+        //~^ match_same_arms
+        #[allow(clippy::match_same_arms)]
+        _ => "b",
+    };
+
+    match 3 {
+        0 => "a",
+        1 | 2 => "b",
+        //~^ match_same_arms
+        #[expect(clippy::match_same_arms)]
+        _ => "b",
+    };
+}
diff --git a/tests/ui/match_same_arms2.rs b/tests/ui/match_same_arms2.rs
index b0ebc4784f33..0fd5d76e7d3e 100644
--- a/tests/ui/match_same_arms2.rs
+++ b/tests/ui/match_same_arms2.rs
@@ -23,7 +23,7 @@ fn match_same_arms() {
             a = -31 - a;
             a
         },
-        //~^^^^^^^^^ match_same_arms
+        //~v match_same_arms
         _ => {
             foo();
             let mut a = 42 + [23].len() as i32;
@@ -37,15 +37,15 @@ fn match_same_arms() {
 
     let _ = match 42 {
         42 => foo(),
-        51 => foo(),
         //~^ match_same_arms
+        51 => foo(),
         _ => true,
     };
 
     let _ = match Some(42) {
         Some(_) => 24,
-        None => 24,
         //~^ match_same_arms
+        None => 24,
     };
 
     let _ = match Some(42) {
@@ -67,8 +67,8 @@ fn match_same_arms() {
 
     match (Some(42), Some(42)) {
         (Some(a), None) => bar(a),
-        (None, Some(a)) => bar(a),
         //~^ match_same_arms
+        (None, Some(a)) => bar(a),
         _ => (),
     }
 
@@ -82,8 +82,8 @@ fn match_same_arms() {
 
     let _ = match (Some(42), Some(42)) {
         (Some(a), None) if a == 42 => a,
-        (None, Some(a)) if a == 42 => a,
         //~^ match_same_arms
+        (None, Some(a)) if a == 42 => a,
         _ => 0,
     };
 
@@ -140,8 +140,8 @@ fn match_same_arms() {
     match x {
         Ok(_tmp) => println!("ok"),
         Ok(3) => println!("ok"),
-        Ok(_) => println!("ok"),
         //~^ match_same_arms
+        Ok(_) => println!("ok"),
         Err(_) => {
             unreachable!();
         },
@@ -168,10 +168,10 @@ fn match_same_arms() {
         0 => {
             empty!(0);
         },
+        //~^^^ match_same_arms
         1 => {
             empty!(0);
         },
-        //~^^^ match_same_arms
         x => {
             empty!(x);
         },
@@ -229,9 +229,9 @@ fn main() {
     // Suggest moving `Foo::X(0)` down.
     let _ = match Foo::X(0) {
         Foo::X(0) => 1,
+        //~^ match_same_arms
         Foo::Y(_) | Foo::Z(0) => 2,
         Foo::Z(_) => 1,
-        //~^ match_same_arms
         _ => 0,
     };
 
@@ -252,10 +252,10 @@ fn main() {
     // Lint.
     let _ = match None {
         Some(Bar { x: 0, y: 5, .. }) => 1,
+        //~^ match_same_arms
         Some(Bar { y: 10, z: 0, .. }) => 2,
         None => 50,
         Some(Bar { y: 0, x: 5, .. }) => 1,
-        //~^ match_same_arms
         _ => 200,
     };
 
@@ -269,8 +269,8 @@ fn main() {
 
     let _ = match 0 {
         0 => cfg!(not_enable),
-        1 => cfg!(not_enable),
         //~^ match_same_arms
+        1 => cfg!(not_enable),
         _ => false,
     };
 }
@@ -286,9 +286,36 @@ mod with_lifetime {
         fn get(&self) -> &'a str {
             match *self {
                 MaybeStaticStr::Static(s) => s,
-                MaybeStaticStr::Borrowed(s) => s,
                 //~^ match_same_arms
+                MaybeStaticStr::Borrowed(s) => s,
             }
         }
     }
 }
+
+fn lint_levels() {
+    match 1 {
+        0 => "a",
+        1 => "b",
+        #[expect(clippy::match_same_arms)]
+        _ => "b",
+    };
+
+    match 2 {
+        0 => "a",
+        1 => "b",
+        //~^ match_same_arms
+        2 => "b",
+        #[allow(clippy::match_same_arms)]
+        _ => "b",
+    };
+
+    match 3 {
+        0 => "a",
+        1 => "b",
+        //~^ match_same_arms
+        2 => "b",
+        #[expect(clippy::match_same_arms)]
+        _ => "b",
+    };
+}
diff --git a/tests/ui/match_same_arms2.stderr b/tests/ui/match_same_arms2.stderr
index 21a8743cc324..f3031619cce5 100644
--- a/tests/ui/match_same_arms2.stderr
+++ b/tests/ui/match_same_arms2.stderr
@@ -1,4 +1,4 @@
-error: this match arm has an identical body to the `_` wildcard arm
+error: these match arms have identical bodies
   --> tests/ui/match_same_arms2.rs:17:9
    |
 LL | /         42 => {
@@ -6,14 +6,10 @@ LL | |             foo();
 LL | |             let mut a = 42 + [23].len() as i32;
 LL | |             if true {
 ...  |
+LL | |             a
 LL | |         },
-LL | |
-   | |________^ help: try removing the arm
-   |
-   = help: or try changing either arm body
-note: `_` wildcard arm here
-  --> tests/ui/match_same_arms2.rs:27:9
-   |
+   | |_________^
+LL |
 LL | /         _ => {
 LL | |             foo();
 LL | |             let mut a = 42 + [23].len() as i32;
@@ -21,134 +17,169 @@ LL | |             if true {
 ...  |
 LL | |             a
 LL | |         },
-   | |_________^
+   | |_________^ the wildcard arm
+   |
+   = help: if this is unintentional make the arms return different values
    = note: `-D clippy::match-same-arms` implied by `-D warnings`
    = help: to override `-D warnings` add `#[allow(clippy::match_same_arms)]`
+help: otherwise remove the non-wildcard arm
+   |
+LL -         42 => {
+LL -             foo();
+LL -             let mut a = 42 + [23].len() as i32;
+LL -             if true {
+LL -                 a += 7;
+LL -             }
+LL -             a = -31 - a;
+LL -             a
+LL -         },
+   |
 
-error: this match arm has an identical body to another arm
-  --> tests/ui/match_same_arms2.rs:40:9
+error: these match arms have identical bodies
+  --> tests/ui/match_same_arms2.rs:39:9
    |
+LL |         42 => foo(),
+   |         ^^^^^^^^^^^
+LL |
 LL |         51 => foo(),
    |         ^^^^^^^^^^^
    |
-   = help: try changing either arm body
-help: or try merging the arm patterns and removing the obsolete arm
+   = help: if this is unintentional make the arms return different values
+help: otherwise merge the patterns into a single arm
    |
-LL -         42 => foo(),
-LL -         51 => foo(),
-LL +         51 | 42 => foo(),
+LL ~
+LL ~         42 | 51 => foo(),
    |
 
-error: this match arm has an identical body to another arm
-  --> tests/ui/match_same_arms2.rs:47:9
+error: these match arms have identical bodies
+  --> tests/ui/match_same_arms2.rs:46:9
    |
+LL |         Some(_) => 24,
+   |         ^^^^^^^^^^^^^
+LL |
 LL |         None => 24,
    |         ^^^^^^^^^^
    |
-   = help: try changing either arm body
-help: or try merging the arm patterns and removing the obsolete arm
+   = help: if this is unintentional make the arms return different values
+help: otherwise merge the patterns into a single arm
    |
-LL -         Some(_) => 24,
-LL -         None => 24,
-LL +         None | Some(_) => 24,
+LL ~
+LL ~         Some(_) | None => 24,
    |
 
-error: this match arm has an identical body to another arm
-  --> tests/ui/match_same_arms2.rs:70:9
+error: these match arms have identical bodies
+  --> tests/ui/match_same_arms2.rs:69:9
    |
+LL |         (Some(a), None) => bar(a),
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |
 LL |         (None, Some(a)) => bar(a),
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^
    |
-   = help: try changing either arm body
-help: or try merging the arm patterns and removing the obsolete arm
+   = help: if this is unintentional make the arms return different values
+help: otherwise merge the patterns into a single arm
    |
-LL -         (Some(a), None) => bar(a),
-LL -         (None, Some(a)) => bar(a),
-LL +         (None, Some(a)) | (Some(a), None) => bar(a),
+LL ~
+LL ~         (Some(a), None) | (None, Some(a)) => bar(a),
    |
 
-error: this match arm has an identical body to another arm
-  --> tests/ui/match_same_arms2.rs:85:9
+error: these match arms have identical bodies
+  --> tests/ui/match_same_arms2.rs:84:9
    |
+LL |         (Some(a), None) if a == 42 => a,
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |
 LL |         (None, Some(a)) if a == 42 => a,
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
-   = help: try changing either arm body
-help: or try merging the arm patterns and removing the obsolete arm
+   = help: if this is unintentional make the arms return different values
+help: otherwise merge the patterns into a single arm
    |
-LL -         (Some(a), None) if a == 42 => a,
-LL -         (None, Some(a)) if a == 42 => a,
-LL +         (None, Some(a)) | (Some(a), None) if a == 42 => a,
+LL ~
+LL ~         (Some(a), None) | (None, Some(a)) if a == 42 => a,
    |
 
-error: this match arm has an identical body to another arm
+error: these match arms have identical bodies
   --> tests/ui/match_same_arms2.rs:91:9
    |
 LL |         (Some(a), ..) => bar(a),
    |         ^^^^^^^^^^^^^^^^^^^^^^^
+LL |
+LL |         (.., Some(a)) => bar(a),
+   |         ^^^^^^^^^^^^^^^^^^^^^^^
    |
-   = help: try changing either arm body
-help: or try merging the arm patterns and removing the obsolete arm
+   = help: if this is unintentional make the arms return different values
+help: otherwise merge the patterns into a single arm
    |
 LL ~         (Some(a), ..) | (.., Some(a)) => bar(a),
 LL |
 LL ~         _ => (),
    |
 
-error: this match arm has an identical body to another arm
+error: these match arms have identical bodies
   --> tests/ui/match_same_arms2.rs:126:9
    |
 LL |         (Ok(x), Some(_)) => println!("ok {}", x),
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |
+LL |         (Ok(_), Some(x)) => println!("ok {}", x),
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
-   = help: try changing either arm body
-help: or try merging the arm patterns and removing the obsolete arm
+   = help: if this is unintentional make the arms return different values
+help: otherwise merge the patterns into a single arm
    |
 LL ~         (Ok(x), Some(_)) | (Ok(_), Some(x)) => println!("ok {}", x),
 LL |
 LL ~         _ => println!("err"),
    |
 
-error: this match arm has an identical body to another arm
-  --> tests/ui/match_same_arms2.rs:143:9
+error: these match arms have identical bodies
+  --> tests/ui/match_same_arms2.rs:142:9
    |
+LL |         Ok(3) => println!("ok"),
+   |         ^^^^^^^^^^^^^^^^^^^^^^^
+LL |
 LL |         Ok(_) => println!("ok"),
    |         ^^^^^^^^^^^^^^^^^^^^^^^
    |
-   = help: try changing either arm body
-help: or try merging the arm patterns and removing the obsolete arm
+   = help: if this is unintentional make the arms return different values
+help: otherwise merge the patterns into a single arm
    |
-LL -         Ok(3) => println!("ok"),
-LL -         Ok(_) => println!("ok"),
-LL +         Ok(_) | Ok(3) => println!("ok"),
+LL ~
+LL ~         Ok(3) | Ok(_) => println!("ok"),
    |
 
-error: this match arm has an identical body to another arm
-  --> tests/ui/match_same_arms2.rs:171:9
+error: these match arms have identical bodies
+  --> tests/ui/match_same_arms2.rs:168:9
    |
+LL | /         0 => {
+LL | |             empty!(0);
+LL | |         },
+   | |_________^
+LL |
 LL | /         1 => {
 LL | |             empty!(0);
 LL | |         },
    | |_________^
    |
-   = help: try changing either arm body
-help: or try merging the arm patterns and removing the obsolete arm
+   = help: if this is unintentional make the arms return different values
+help: otherwise merge the patterns into a single arm
    |
-LL -         0 => {
-LL -             empty!(0);
-LL -         },
-LL -         1 => {
-LL +         1 | 0 => {
+LL ~
+LL ~         0 | 1 => {
    |
 
-error: this match arm has an identical body to another arm
+error: these match arms have identical bodies
   --> tests/ui/match_same_arms2.rs:222:9
    |
 LL |         Foo::X(0) => 1,
    |         ^^^^^^^^^^^^^^
+...
+LL |         Foo::Z(_) => 1,
+   |         ^^^^^^^^^^^^^^
    |
-   = help: try changing either arm body
-help: or try merging the arm patterns and removing the obsolete arm
+   = help: if this is unintentional make the arms return different values
+help: otherwise merge the patterns into a single arm
    |
 LL ~         Foo::X(0) | Foo::Z(_) => 1,
 LL |
@@ -156,60 +187,106 @@ LL |         Foo::X(_) | Foo::Y(_) => 2,
 LL ~         _ => 0,
    |
 
-error: this match arm has an identical body to another arm
-  --> tests/ui/match_same_arms2.rs:233:9
+error: these match arms have identical bodies
+  --> tests/ui/match_same_arms2.rs:231:9
    |
+LL |         Foo::X(0) => 1,
+   |         ^^^^^^^^^^^^^^
+...
 LL |         Foo::Z(_) => 1,
    |         ^^^^^^^^^^^^^^
    |
-   = help: try changing either arm body
-help: or try merging the arm patterns and removing the obsolete arm
+   = help: if this is unintentional make the arms return different values
+help: otherwise merge the patterns into a single arm
    |
-LL ~         Foo::Y(_) | Foo::Z(0) => 2,
-LL ~         Foo::Z(_) | Foo::X(0) => 1,
+LL ~
+LL |         Foo::Y(_) | Foo::Z(0) => 2,
+LL ~         Foo::X(0) | Foo::Z(_) => 1,
    |
 
-error: this match arm has an identical body to another arm
-  --> tests/ui/match_same_arms2.rs:257:9
+error: these match arms have identical bodies
+  --> tests/ui/match_same_arms2.rs:254:9
    |
+LL |         Some(Bar { x: 0, y: 5, .. }) => 1,
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+...
 LL |         Some(Bar { y: 0, x: 5, .. }) => 1,
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
-   = help: try changing either arm body
-help: or try merging the arm patterns and removing the obsolete arm
+   = help: if this is unintentional make the arms return different values
+help: otherwise merge the patterns into a single arm
    |
-LL ~         Some(Bar { y: 10, z: 0, .. }) => 2,
+LL ~
+LL |         Some(Bar { y: 10, z: 0, .. }) => 2,
 LL |         None => 50,
-LL ~         Some(Bar { y: 0, x: 5, .. }) | Some(Bar { x: 0, y: 5, .. }) => 1,
+LL ~         Some(Bar { x: 0, y: 5, .. }) | Some(Bar { y: 0, x: 5, .. }) => 1,
    |
 
-error: this match arm has an identical body to another arm
-  --> tests/ui/match_same_arms2.rs:272:9
+error: these match arms have identical bodies
+  --> tests/ui/match_same_arms2.rs:271:9
    |
+LL |         0 => cfg!(not_enable),
+   |         ^^^^^^^^^^^^^^^^^^^^^
+LL |
 LL |         1 => cfg!(not_enable),
    |         ^^^^^^^^^^^^^^^^^^^^^
    |
-   = help: try changing either arm body
-help: or try merging the arm patterns and removing the obsolete arm
+   = help: if this is unintentional make the arms return different values
+help: otherwise merge the patterns into a single arm
    |
-LL -         0 => cfg!(not_enable),
-LL -         1 => cfg!(not_enable),
-LL +         1 | 0 => cfg!(not_enable),
+LL ~
+LL ~         0 | 1 => cfg!(not_enable),
    |
 
-error: this match arm has an identical body to another arm
-  --> tests/ui/match_same_arms2.rs:289:17
+error: these match arms have identical bodies
+  --> tests/ui/match_same_arms2.rs:288:17
    |
+LL |                 MaybeStaticStr::Static(s) => s,
+   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |
 LL |                 MaybeStaticStr::Borrowed(s) => s,
    |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
-   = help: try changing either arm body
-help: or try merging the arm patterns and removing the obsolete arm
+   = help: if this is unintentional make the arms return different values
+help: otherwise merge the patterns into a single arm
+   |
+LL ~
+LL ~                 MaybeStaticStr::Static(s) | MaybeStaticStr::Borrowed(s) => s,
    |
-LL -                 MaybeStaticStr::Static(s) => s,
-LL -                 MaybeStaticStr::Borrowed(s) => s,
-LL +                 MaybeStaticStr::Borrowed(s) | MaybeStaticStr::Static(s) => s,
+
+error: these match arms have identical bodies
+  --> tests/ui/match_same_arms2.rs:306:9
+   |
+LL |         1 => "b",
+   |         ^^^^^^^^
+LL |
+LL |         2 => "b",
+   |         ^^^^^^^^
+   |
+   = help: if this is unintentional make the arms return different values
+help: otherwise merge the patterns into a single arm
+   |
+LL ~         1 | 2 => "b",
+LL |
+LL ~         #[allow(clippy::match_same_arms)]
+   |
+
+error: these match arms have identical bodies
+  --> tests/ui/match_same_arms2.rs:315:9
+   |
+LL |         1 => "b",
+   |         ^^^^^^^^
+LL |
+LL |         2 => "b",
+   |         ^^^^^^^^
+   |
+   = help: if this is unintentional make the arms return different values
+help: otherwise merge the patterns into a single arm
+   |
+LL ~         1 | 2 => "b",
+LL |
+LL ~         #[expect(clippy::match_same_arms)]
    |
 
-error: aborting due to 14 previous errors
+error: aborting due to 16 previous errors
 
diff --git a/tests/ui/match_same_arms_non_exhaustive.fixed b/tests/ui/match_same_arms_non_exhaustive.fixed
index 0c9398933b80..61a5bd0323a8 100644
--- a/tests/ui/match_same_arms_non_exhaustive.fixed
+++ b/tests/ui/match_same_arms_non_exhaustive.fixed
@@ -7,14 +7,22 @@ fn repeat() -> ! {
     panic!()
 }
 
+#[deny(non_exhaustive_omitted_patterns)]
 pub fn f(x: Ordering) {
-    #[deny(non_exhaustive_omitted_patterns)]
     match x {
         Ordering::Relaxed => println!("relaxed"),
         Ordering::Release => println!("release"),
         Ordering::Acquire => println!("acquire"),
-        Ordering::AcqRel | Ordering::SeqCst => repeat(),
-        _ => repeat(),
+        //~^ match_same_arms
+        Ordering::AcqRel | Ordering::SeqCst | _ => repeat(),
+    }
+
+    match x {
+        Ordering::Relaxed => println!("relaxed"),
+        Ordering::Release => println!("release"),
+        Ordering::Acquire => println!("acquire"),
+        //~^ match_same_arms
+        Ordering::AcqRel | Ordering::SeqCst | _ => repeat(),
     }
 }
 
@@ -28,21 +36,21 @@ mod f {
             Ordering::Relaxed => println!("relaxed"),
             Ordering::Release => println!("release"),
             Ordering::Acquire => println!("acquire"),
-            Ordering::AcqRel | Ordering::SeqCst => repeat(),
-            _ => repeat(),
+            //~^ match_same_arms
+            Ordering::AcqRel | Ordering::SeqCst | _ => repeat(),
         }
     }
 }
 
-// Below should still lint
+// Below can still suggest removing the other patterns
 
 pub fn g(x: Ordering) {
     match x {
         Ordering::Relaxed => println!("relaxed"),
         Ordering::Release => println!("release"),
         Ordering::Acquire => println!("acquire"),
-        //~^ match_same_arms
         _ => repeat(),
+        //~^ match_same_arms
     }
 }
 
@@ -54,8 +62,8 @@ mod g {
             Ordering::Relaxed => println!("relaxed"),
             Ordering::Release => println!("release"),
             Ordering::Acquire => println!("acquire"),
-            //~^ match_same_arms
             _ => repeat(),
+            //~^ match_same_arms
         }
     }
 }
diff --git a/tests/ui/match_same_arms_non_exhaustive.rs b/tests/ui/match_same_arms_non_exhaustive.rs
index 304a9e5c28e7..66f65eb39d0a 100644
--- a/tests/ui/match_same_arms_non_exhaustive.rs
+++ b/tests/ui/match_same_arms_non_exhaustive.rs
@@ -7,15 +7,25 @@ fn repeat() -> ! {
     panic!()
 }
 
+#[deny(non_exhaustive_omitted_patterns)]
 pub fn f(x: Ordering) {
-    #[deny(non_exhaustive_omitted_patterns)]
     match x {
         Ordering::Relaxed => println!("relaxed"),
         Ordering::Release => println!("release"),
         Ordering::Acquire => println!("acquire"),
         Ordering::AcqRel | Ordering::SeqCst => repeat(),
+        //~^ match_same_arms
         _ => repeat(),
     }
+
+    match x {
+        Ordering::Relaxed => println!("relaxed"),
+        Ordering::Release => println!("release"),
+        Ordering::Acquire => println!("acquire"),
+        Ordering::AcqRel => repeat(),
+        //~^ match_same_arms
+        Ordering::SeqCst | _ => repeat(),
+    }
 }
 
 mod f {
@@ -29,12 +39,13 @@ mod f {
             Ordering::Release => println!("release"),
             Ordering::Acquire => println!("acquire"),
             Ordering::AcqRel | Ordering::SeqCst => repeat(),
+            //~^ match_same_arms
             _ => repeat(),
         }
     }
 }
 
-// Below should still lint
+// Below can still suggest removing the other patterns
 
 pub fn g(x: Ordering) {
     match x {
@@ -42,8 +53,8 @@ pub fn g(x: Ordering) {
         Ordering::Release => println!("release"),
         Ordering::Acquire => println!("acquire"),
         Ordering::AcqRel | Ordering::SeqCst => repeat(),
-        //~^ match_same_arms
         _ => repeat(),
+        //~^ match_same_arms
     }
 }
 
@@ -56,8 +67,8 @@ mod g {
             Ordering::Release => println!("release"),
             Ordering::Acquire => println!("acquire"),
             Ordering::AcqRel | Ordering::SeqCst => repeat(),
-            //~^ match_same_arms
             _ => repeat(),
+            //~^ match_same_arms
         }
     }
 }
diff --git a/tests/ui/match_same_arms_non_exhaustive.stderr b/tests/ui/match_same_arms_non_exhaustive.stderr
index aa7f8c95dce8..03252f346c6c 100644
--- a/tests/ui/match_same_arms_non_exhaustive.stderr
+++ b/tests/ui/match_same_arms_non_exhaustive.stderr
@@ -1,32 +1,80 @@
-error: this match arm has an identical body to the `_` wildcard arm
-  --> tests/ui/match_same_arms_non_exhaustive.rs:44:9
-   |
-LL | /         Ordering::AcqRel | Ordering::SeqCst => repeat(),
-LL | |
-   | |________^ help: try removing the arm
-   |
-   = help: or try changing either arm body
-note: `_` wildcard arm here
-  --> tests/ui/match_same_arms_non_exhaustive.rs:46:9
+error: these match arms have identical bodies
+  --> tests/ui/match_same_arms_non_exhaustive.rs:16:9
    |
+LL |         Ordering::AcqRel | Ordering::SeqCst => repeat(),
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |
 LL |         _ => repeat(),
    |         ^^^^^^^^^^^^^
+   |
+   = help: if this is unintentional make the arms return different values
    = note: `-D clippy::match-same-arms` implied by `-D warnings`
    = help: to override `-D warnings` add `#[allow(clippy::match_same_arms)]`
+help: otherwise merge the patterns into a single arm
+   |
+LL ~
+LL ~         Ordering::AcqRel | Ordering::SeqCst | _ => repeat(),
+   |
 
-error: this match arm has an identical body to the `_` wildcard arm
-  --> tests/ui/match_same_arms_non_exhaustive.rs:58:13
+error: these match arms have identical bodies
+  --> tests/ui/match_same_arms_non_exhaustive.rs:25:9
+   |
+LL |         Ordering::AcqRel => repeat(),
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |
+LL |         Ordering::SeqCst | _ => repeat(),
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
-LL | /             Ordering::AcqRel | Ordering::SeqCst => repeat(),
-LL | |
-   | |____________^ help: try removing the arm
+   = help: if this is unintentional make the arms return different values
+help: otherwise merge the patterns into a single arm
    |
-   = help: or try changing either arm body
-note: `_` wildcard arm here
-  --> tests/ui/match_same_arms_non_exhaustive.rs:60:13
+LL ~
+LL ~         Ordering::AcqRel | Ordering::SeqCst | _ => repeat(),
    |
+
+error: these match arms have identical bodies
+  --> tests/ui/match_same_arms_non_exhaustive.rs:41:13
+   |
+LL |             Ordering::AcqRel | Ordering::SeqCst => repeat(),
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |
 LL |             _ => repeat(),
    |             ^^^^^^^^^^^^^
+   |
+   = help: if this is unintentional make the arms return different values
+help: otherwise merge the patterns into a single arm
+   |
+LL ~
+LL ~             Ordering::AcqRel | Ordering::SeqCst | _ => repeat(),
+   |
+
+error: these match arms have identical bodies
+  --> tests/ui/match_same_arms_non_exhaustive.rs:55:9
+   |
+LL |         Ordering::AcqRel | Ordering::SeqCst => repeat(),
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |         _ => repeat(),
+   |         ^^^^^^^^^^^^^ the wildcard arm
+   |
+   = help: if this is unintentional make the arms return different values
+help: otherwise remove the non-wildcard arm
+   |
+LL -         Ordering::AcqRel | Ordering::SeqCst => repeat(),
+   |
+
+error: these match arms have identical bodies
+  --> tests/ui/match_same_arms_non_exhaustive.rs:69:13
+   |
+LL |             Ordering::AcqRel | Ordering::SeqCst => repeat(),
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |             _ => repeat(),
+   |             ^^^^^^^^^^^^^ the wildcard arm
+   |
+   = help: if this is unintentional make the arms return different values
+help: otherwise remove the non-wildcard arm
+   |
+LL -             Ordering::AcqRel | Ordering::SeqCst => repeat(),
+   |
 
-error: aborting due to 2 previous errors
+error: aborting due to 5 previous errors
 
diff --git a/tests/ui/same_functions_in_if_condition.rs b/tests/ui/same_functions_in_if_condition.rs
index a98b73c9e1c8..3f205b322ab0 100644
--- a/tests/ui/same_functions_in_if_condition.rs
+++ b/tests/ui/same_functions_in_if_condition.rs
@@ -31,34 +31,34 @@ fn ifs_same_cond_fn() {
     let obj = Struct;
 
     if function() {
-    } else if function() {
         //~^ same_functions_in_if_condition
+    } else if function() {
     }
 
     if fn_arg(a) {
-    } else if fn_arg(a) {
         //~^ same_functions_in_if_condition
+    } else if fn_arg(a) {
     }
 
     if obj.method() {
-    } else if obj.method() {
         //~^ same_functions_in_if_condition
+    } else if obj.method() {
     }
 
     if obj.method_arg(a) {
-    } else if obj.method_arg(a) {
         //~^ same_functions_in_if_condition
+    } else if obj.method_arg(a) {
     }
 
     let mut v = vec![1];
     if v.pop().is_none() {
-    } else if v.pop().is_none() {
         //~^ same_functions_in_if_condition
+    } else if v.pop().is_none() {
     }
 
     if v.len() == 42 {
-    } else if v.len() == 42 {
         //~^ same_functions_in_if_condition
+    } else if v.len() == 42 {
     }
 
     if v.len() == 1 {
diff --git a/tests/ui/same_functions_in_if_condition.stderr b/tests/ui/same_functions_in_if_condition.stderr
index 35dcbadce595..59f4511757d6 100644
--- a/tests/ui/same_functions_in_if_condition.stderr
+++ b/tests/ui/same_functions_in_if_condition.stderr
@@ -1,79 +1,62 @@
-error: this `if` has the same function call as a previous `if`
-  --> tests/ui/same_functions_in_if_condition.rs:34:15
-   |
-LL |     } else if function() {
-   |               ^^^^^^^^^^
-   |
-note: same as this
+error: these `if` branches have the same function call
   --> tests/ui/same_functions_in_if_condition.rs:33:8
    |
 LL |     if function() {
    |        ^^^^^^^^^^
+LL |
+LL |     } else if function() {
+   |               ^^^^^^^^^^
+   |
 note: the lint level is defined here
   --> tests/ui/same_functions_in_if_condition.rs:2:9
    |
 LL | #![deny(clippy::same_functions_in_if_condition)]
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-error: this `if` has the same function call as a previous `if`
-  --> tests/ui/same_functions_in_if_condition.rs:39:15
-   |
-LL |     } else if fn_arg(a) {
-   |               ^^^^^^^^^
-   |
-note: same as this
+error: these `if` branches have the same function call
   --> tests/ui/same_functions_in_if_condition.rs:38:8
    |
 LL |     if fn_arg(a) {
    |        ^^^^^^^^^
+LL |
+LL |     } else if fn_arg(a) {
+   |               ^^^^^^^^^
 
-error: this `if` has the same function call as a previous `if`
-  --> tests/ui/same_functions_in_if_condition.rs:44:15
-   |
-LL |     } else if obj.method() {
-   |               ^^^^^^^^^^^^
-   |
-note: same as this
+error: these `if` branches have the same function call
   --> tests/ui/same_functions_in_if_condition.rs:43:8
    |
 LL |     if obj.method() {
    |        ^^^^^^^^^^^^
+LL |
+LL |     } else if obj.method() {
+   |               ^^^^^^^^^^^^
 
-error: this `if` has the same function call as a previous `if`
-  --> tests/ui/same_functions_in_if_condition.rs:49:15
-   |
-LL |     } else if obj.method_arg(a) {
-   |               ^^^^^^^^^^^^^^^^^
-   |
-note: same as this
+error: these `if` branches have the same function call
   --> tests/ui/same_functions_in_if_condition.rs:48:8
    |
 LL |     if obj.method_arg(a) {
    |        ^^^^^^^^^^^^^^^^^
-
-error: this `if` has the same function call as a previous `if`
-  --> tests/ui/same_functions_in_if_condition.rs:55:15
-   |
-LL |     } else if v.pop().is_none() {
+LL |
+LL |     } else if obj.method_arg(a) {
    |               ^^^^^^^^^^^^^^^^^
-   |
-note: same as this
+
+error: these `if` branches have the same function call
   --> tests/ui/same_functions_in_if_condition.rs:54:8
    |
 LL |     if v.pop().is_none() {
    |        ^^^^^^^^^^^^^^^^^
+LL |
+LL |     } else if v.pop().is_none() {
+   |               ^^^^^^^^^^^^^^^^^
 
-error: this `if` has the same function call as a previous `if`
-  --> tests/ui/same_functions_in_if_condition.rs:60:15
-   |
-LL |     } else if v.len() == 42 {
-   |               ^^^^^^^^^^^^^
-   |
-note: same as this
+error: these `if` branches have the same function call
   --> tests/ui/same_functions_in_if_condition.rs:59:8
    |
 LL |     if v.len() == 42 {
    |        ^^^^^^^^^^^^^
+LL |
+LL |     } else if v.len() == 42 {
+   |               ^^^^^^^^^^^^^
 
 error: aborting due to 6 previous errors
 


Reviews:
user: login: samueltardieu
body: Really nice improvement.
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-07T21:00:45Z
user: login: Alexendoo
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-16T12:57:17Z
user: login: Alexendoo
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-16T12:59:14Z

Review comments:
diff_hunk: @@ -2486,45 +2486,45 @@ pub fn is_slice_of_primitives(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<S
     None
 }
 
-/// Returns list of all pairs `(a, b)` where `eq(a, b) == true`
-/// and `a` is before `b` in `exprs` for all `a` and `b` in
-/// `exprs`
+/// Returns a list of groups where elements in each group are equal according to `eq`
+///
+/// Groups contain at least two elements and appear in the order they occur in `exprs`
path: clippy_utils/src/lib.rs
user: login: samueltardieu
body: ```suggestion
/// Groups contain at least two elements which appear in the order they occur in `exprs`
```

The groups themselves are not ordered, only elements within a group are.
created_at: 2025-05-07T19:00:47Z
author_association: MEMBER
diff_hunk: @@ -110,57 +111,68 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) {
             && check_same_body()
     };
 
-    let mut appl = Applicability::MaybeIncorrect;
     let indexed_arms: Vec<(usize, &Arm<'_>)> = arms.iter().enumerate().collect();
-    for (&(i, arm1), &(j, arm2)) in search_same(&indexed_arms, hash, eq) {
-        if matches!(arm2.pat.kind, PatKind::Wild) {
-            if !cx.tcx.features().non_exhaustive_omitted_patterns_lint()
-                || is_lint_allowed(cx, NON_EXHAUSTIVE_OMITTED_PATTERNS, arm2.hir_id)
-            {
-                let arm_span = adjusted_arm_span(cx, arm1.span);
-                span_lint_hir_and_then(
-                    cx,
-                    MATCH_SAME_ARMS,
-                    arm1.hir_id,
-                    arm_span,
-                    "this match arm has an identical body to the `_` wildcard arm",
-                    |diag| {
-                        diag.span_suggestion(arm_span, "try removing the arm", "", appl)
-                            .help("or try changing either arm body")
-                            .span_note(arm2.span, "`_` wildcard arm here");
-                    },
-                );
-            }
-        } else {
-            let back_block = backwards_blocking_idxs[j];
-            let (keep_arm, move_arm) = if back_block < i || (back_block == 0 && forwards_blocking_idxs[i] <= j) {
-                (arm1, arm2)
-            } else {
-                (arm2, arm1)
-            };
-
-            span_lint_hir_and_then(
-                cx,
-                MATCH_SAME_ARMS,
-                keep_arm.hir_id,
-                keep_arm.span,
-                "this match arm has an identical body to another arm",
-                |diag| {
-                    let move_pat_snip = snippet_with_applicability(cx, move_arm.pat.span, "<pat2>", &mut appl);
-                    let keep_pat_snip = snippet_with_applicability(cx, keep_arm.pat.span, "<pat1>", &mut appl);
+    for mut group in search_same(&indexed_arms, hash, eq) {
+        // Filter out (and fulfill) `#[allow]`ed and `#[expect]`ed arms
+        group.retain(|(_, arm)| !fulfill_or_allowed(cx, MATCH_SAME_ARMS, [arm.hir_id]));
 
-                    diag.multipart_suggestion(
-                        "or try merging the arm patterns and removing the obsolete arm",
-                        vec![
-                            (keep_arm.pat.span, format!("{keep_pat_snip} | {move_pat_snip}")),
-                            (adjusted_arm_span(cx, move_arm.span), String::new()),
-                        ],
-                        appl,
-                    )
-                    .help("try changing either arm body");
-                },
-            );
+        if group.len() < 2 {
+            continue;
         }
+
+        span_lint_and_then(
+            cx,
+            MATCH_SAME_ARMS,
+            group.iter().map(|(_, arm)| arm.span).collect_vec(),
+            "these match arms have identical bodies",
+            |diag| {
+                diag.help("make the arms return different values");
path: clippy_lints/src/matches/match_same_arms.rs
user: login: samueltardieu
body: I find this suggestion a bit strange. While it sounds logical to group the arms if possible, changing the body value means changing the semantics.
created_at: 2025-05-07T20:25:37Z
author_association: MEMBER
diff_hunk: @@ -2486,45 +2486,45 @@ pub fn is_slice_of_primitives(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<S
     None
 }
 
-/// Returns list of all pairs `(a, b)` where `eq(a, b) == true`
-/// and `a` is before `b` in `exprs` for all `a` and `b` in
-/// `exprs`
+/// Returns a list of groups where elements in each group are equal according to `eq`
+///
+/// Groups contain at least two elements and appear in the order they occur in `exprs`
path: clippy_utils/src/lib.rs
user: login: Alexendoo
body: The groups are also ordered, updated the comment to say that
created_at: 2025-05-16T12:57:17Z
author_association: MEMBER
diff_hunk: @@ -110,57 +111,68 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) {
             && check_same_body()
     };
 
-    let mut appl = Applicability::MaybeIncorrect;
     let indexed_arms: Vec<(usize, &Arm<'_>)> = arms.iter().enumerate().collect();
-    for (&(i, arm1), &(j, arm2)) in search_same(&indexed_arms, hash, eq) {
-        if matches!(arm2.pat.kind, PatKind::Wild) {
-            if !cx.tcx.features().non_exhaustive_omitted_patterns_lint()
-                || is_lint_allowed(cx, NON_EXHAUSTIVE_OMITTED_PATTERNS, arm2.hir_id)
-            {
-                let arm_span = adjusted_arm_span(cx, arm1.span);
-                span_lint_hir_and_then(
-                    cx,
-                    MATCH_SAME_ARMS,
-                    arm1.hir_id,
-                    arm_span,
-                    "this match arm has an identical body to the `_` wildcard arm",
-                    |diag| {
-                        diag.span_suggestion(arm_span, "try removing the arm", "", appl)
-                            .help("or try changing either arm body")
-                            .span_note(arm2.span, "`_` wildcard arm here");
-                    },
-                );
-            }
-        } else {
-            let back_block = backwards_blocking_idxs[j];
-            let (keep_arm, move_arm) = if back_block < i || (back_block == 0 && forwards_blocking_idxs[i] <= j) {
-                (arm1, arm2)
-            } else {
-                (arm2, arm1)
-            };
-
-            span_lint_hir_and_then(
-                cx,
-                MATCH_SAME_ARMS,
-                keep_arm.hir_id,
-                keep_arm.span,
-                "this match arm has an identical body to another arm",
-                |diag| {
-                    let move_pat_snip = snippet_with_applicability(cx, move_arm.pat.span, "<pat2>", &mut appl);
-                    let keep_pat_snip = snippet_with_applicability(cx, keep_arm.pat.span, "<pat1>", &mut appl);
+    for mut group in search_same(&indexed_arms, hash, eq) {
+        // Filter out (and fulfill) `#[allow]`ed and `#[expect]`ed arms
+        group.retain(|(_, arm)| !fulfill_or_allowed(cx, MATCH_SAME_ARMS, [arm.hir_id]));
 
-                    diag.multipart_suggestion(
-                        "or try merging the arm patterns and removing the obsolete arm",
-                        vec![
-                            (keep_arm.pat.span, format!("{keep_pat_snip} | {move_pat_snip}")),
-                            (adjusted_arm_span(cx, move_arm.span), String::new()),
-                        ],
-                        appl,
-                    )
-                    .help("try changing either arm body");
-                },
-            );
+        if group.len() < 2 {
+            continue;
         }
+
+        span_lint_and_then(
+            cx,
+            MATCH_SAME_ARMS,
+            group.iter().map(|(_, arm)| arm.span).collect_vec(),
+            "these match arms have identical bodies",
+            |diag| {
+                diag.help("make the arms return different values");
path: clippy_lints/src/matches/match_same_arms.rs
user: login: Alexendoo
body: It's from the basis that the same arms may not be intentional and so inherently require changing the semantics, reworded it to state that explicitly
created_at: 2025-05-16T12:59:14Z
author_association: MEMBER

PR comments:
user: login: rustbot
created_at: 2025-04-16T22:27:54Z
body: r? @llogiq

rustbot has assigned @llogiq.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use `r?` to explicitly pick a reviewer
author_association: COLLABORATOR
user: login: Alexendoo
created_at: 2025-04-16T22:41:59Z
body: > [110 added, 268700 removed, 172 changed](https://github.com/rust-lang/rust-clippy/actions/runs/14503803373#summary-40689669273)

😅
author_association: MEMBER
user: login: samueltardieu
created_at: 2025-05-07T21:08:27Z
body: @Alexendoo This looks really nice (see the two nitpicking comments). I like how the patterns stay in order compared to the previous implementation.

I prefer to wait until 1.88 is branched out of master (in two days) before merging this in case an unplanned Clippy sync happens in the meantime. Given the scope of the change, a full nightly + beta cycle will be useful to catch any oversight.

r? @samueltardieu 
author_association: MEMBER
user: login: samueltardieu
created_at: 2025-05-13T10:14:37Z
body: @rustbot author
author_association: MEMBER
user: login: samueltardieu
created_at: 2025-05-16T14:16:55Z
body: Thanks!
author_association: MEMBER