|
1 |
| -use clippy_utils::diagnostics::span_lint_hir_and_then; |
2 |
| -use clippy_utils::source::snippet_with_applicability; |
3 |
| -use clippy_utils::{SpanlessEq, SpanlessHash, is_lint_allowed, path_to_local, search_same}; |
| 1 | +use clippy_utils::diagnostics::span_lint_and_then; |
| 2 | +use clippy_utils::source::SpanRangeExt; |
| 3 | +use clippy_utils::{SpanlessEq, SpanlessHash, fulfill_or_allowed, is_lint_allowed, path_to_local, search_same}; |
4 | 4 | use core::cmp::Ordering;
|
5 | 5 | use core::{iter, slice};
|
| 6 | +use itertools::Itertools; |
6 | 7 | use rustc_arena::DroplessArena;
|
7 | 8 | use rustc_ast::ast::LitKind;
|
8 | 9 | use rustc_errors::Applicability;
|
@@ -110,57 +111,68 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) {
|
110 | 111 | && check_same_body()
|
111 | 112 | };
|
112 | 113 |
|
113 |
| - let mut appl = Applicability::MaybeIncorrect; |
114 | 114 | let indexed_arms: Vec<(usize, &Arm<'_>)> = arms.iter().enumerate().collect();
|
115 |
| - for (&(i, arm1), &(j, arm2)) in search_same(&indexed_arms, hash, eq) { |
116 |
| - if matches!(arm2.pat.kind, PatKind::Wild) { |
117 |
| - if !cx.tcx.features().non_exhaustive_omitted_patterns_lint() |
118 |
| - || is_lint_allowed(cx, NON_EXHAUSTIVE_OMITTED_PATTERNS, arm2.hir_id) |
119 |
| - { |
120 |
| - let arm_span = adjusted_arm_span(cx, arm1.span); |
121 |
| - span_lint_hir_and_then( |
122 |
| - cx, |
123 |
| - MATCH_SAME_ARMS, |
124 |
| - arm1.hir_id, |
125 |
| - arm_span, |
126 |
| - "this match arm has an identical body to the `_` wildcard arm", |
127 |
| - |diag| { |
128 |
| - diag.span_suggestion(arm_span, "try removing the arm", "", appl) |
129 |
| - .help("or try changing either arm body") |
130 |
| - .span_note(arm2.span, "`_` wildcard arm here"); |
131 |
| - }, |
132 |
| - ); |
133 |
| - } |
134 |
| - } else { |
135 |
| - let back_block = backwards_blocking_idxs[j]; |
136 |
| - let (keep_arm, move_arm) = if back_block < i || (back_block == 0 && forwards_blocking_idxs[i] <= j) { |
137 |
| - (arm1, arm2) |
138 |
| - } else { |
139 |
| - (arm2, arm1) |
140 |
| - }; |
141 |
| - |
142 |
| - span_lint_hir_and_then( |
143 |
| - cx, |
144 |
| - MATCH_SAME_ARMS, |
145 |
| - keep_arm.hir_id, |
146 |
| - keep_arm.span, |
147 |
| - "this match arm has an identical body to another arm", |
148 |
| - |diag| { |
149 |
| - let move_pat_snip = snippet_with_applicability(cx, move_arm.pat.span, "<pat2>", &mut appl); |
150 |
| - let keep_pat_snip = snippet_with_applicability(cx, keep_arm.pat.span, "<pat1>", &mut appl); |
| 115 | + for mut group in search_same(&indexed_arms, hash, eq) { |
| 116 | + // Filter out (and fulfill) `#[allow]`ed and `#[expect]`ed arms |
| 117 | + group.retain(|(_, arm)| !fulfill_or_allowed(cx, MATCH_SAME_ARMS, [arm.hir_id])); |
151 | 118 |
|
152 |
| - diag.multipart_suggestion( |
153 |
| - "or try merging the arm patterns and removing the obsolete arm", |
154 |
| - vec![ |
155 |
| - (keep_arm.pat.span, format!("{keep_pat_snip} | {move_pat_snip}")), |
156 |
| - (adjusted_arm_span(cx, move_arm.span), String::new()), |
157 |
| - ], |
158 |
| - appl, |
159 |
| - ) |
160 |
| - .help("try changing either arm body"); |
161 |
| - }, |
162 |
| - ); |
| 119 | + if group.len() < 2 { |
| 120 | + continue; |
163 | 121 | }
|
| 122 | + |
| 123 | + span_lint_and_then( |
| 124 | + cx, |
| 125 | + MATCH_SAME_ARMS, |
| 126 | + group.iter().map(|(_, arm)| arm.span).collect_vec(), |
| 127 | + "these match arms have identical bodies", |
| 128 | + |diag| { |
| 129 | + diag.help("if this is unintentional make the arms return different values"); |
| 130 | + |
| 131 | + if let [prev @ .., (_, last)] = group.as_slice() |
| 132 | + && is_wildcard_arm(last.pat) |
| 133 | + && is_lint_allowed(cx, NON_EXHAUSTIVE_OMITTED_PATTERNS, last.hir_id) |
| 134 | + { |
| 135 | + diag.span_label(last.span, "the wildcard arm"); |
| 136 | + |
| 137 | + let s = if prev.len() > 1 { "s" } else { "" }; |
| 138 | + diag.multipart_suggestion_verbose( |
| 139 | + format!("otherwise remove the non-wildcard arm{s}"), |
| 140 | + prev.iter() |
| 141 | + .map(|(_, arm)| (adjusted_arm_span(cx, arm.span), String::new())) |
| 142 | + .collect(), |
| 143 | + Applicability::MaybeIncorrect, |
| 144 | + ); |
| 145 | + } else if let &[&(first_idx, _), .., &(last_idx, _)] = group.as_slice() { |
| 146 | + let back_block = backwards_blocking_idxs[last_idx]; |
| 147 | + let split = if back_block < first_idx |
| 148 | + || (back_block == 0 && forwards_blocking_idxs[first_idx] <= last_idx) |
| 149 | + { |
| 150 | + group.split_first() |
| 151 | + } else { |
| 152 | + group.split_last() |
| 153 | + }; |
| 154 | + |
| 155 | + if let Some(((_, dest), src)) = split |
| 156 | + && let Some(pat_snippets) = group |
| 157 | + .iter() |
| 158 | + .map(|(_, arm)| arm.pat.span.get_source_text(cx)) |
| 159 | + .collect::<Option<Vec<_>>>() |
| 160 | + { |
| 161 | + let mut suggs = src |
| 162 | + .iter() |
| 163 | + .map(|(_, arm)| (adjusted_arm_span(cx, arm.span), String::new())) |
| 164 | + .collect_vec(); |
| 165 | + |
| 166 | + suggs.push((dest.pat.span, pat_snippets.iter().join(" | "))); |
| 167 | + diag.multipart_suggestion_verbose( |
| 168 | + "otherwise merge the patterns into a single arm", |
| 169 | + suggs, |
| 170 | + Applicability::MaybeIncorrect, |
| 171 | + ); |
| 172 | + } |
| 173 | + } |
| 174 | + }, |
| 175 | + ); |
164 | 176 | }
|
165 | 177 | }
|
166 | 178 |
|
@@ -449,3 +461,11 @@ fn bindings_eq(pat: &Pat<'_>, mut ids: HirIdSet) -> bool {
|
449 | 461 | pat.each_binding_or_first(&mut |_, id, _, _| result &= ids.swap_remove(&id));
|
450 | 462 | result && ids.is_empty()
|
451 | 463 | }
|
| 464 | + |
| 465 | +fn is_wildcard_arm(pat: &Pat<'_>) -> bool { |
| 466 | + match pat.kind { |
| 467 | + PatKind::Wild => true, |
| 468 | + PatKind::Or([.., last]) => matches!(last.kind, PatKind::Wild), |
| 469 | + _ => false, |
| 470 | + } |
| 471 | +} |
0 commit comments