Skip to content

Commit 9ac0f8c

Browse files
committed
chore: use multipart_suggestions for match_same_arms
1 parent c4aeb32 commit 9ac0f8c

File tree

4 files changed

+323
-99
lines changed

4 files changed

+323
-99
lines changed

clippy_lints/src/matches/match_same_arms.rs

+8-12
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) {
7474
// check if using the same bindings as before
7575
HirIdMapEntry::Occupied(entry) => return *entry.get() == b_id,
7676
}
77-
// the names technically don't have to match; this makes the lint more conservative
78-
&& cx.tcx.hir().name(a_id) == cx.tcx.hir().name(b_id)
77+
// the names technically don't have to match; this makes the lint more conservative
78+
&& cx.tcx.hir().name(a_id) == cx.tcx.hir().name(b_id)
7979
&& cx.typeck_results().expr_ty(a) == cx.typeck_results().expr_ty(b)
8080
&& pat_contains_local(lhs.pat, a_id)
8181
&& pat_contains_local(rhs.pat, b_id)
@@ -149,16 +149,12 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) {
149149
let move_pat_snip = snippet_with_applicability(cx, move_arm.pat.span, "<pat2>", &mut appl);
150150
let keep_pat_snip = snippet_with_applicability(cx, keep_arm.pat.span, "<pat1>", &mut appl);
151151

152-
diag.span_suggestion(
153-
keep_arm.pat.span,
154-
"or try merging the arm patterns",
155-
format!("{keep_pat_snip} | {move_pat_snip}"),
156-
appl,
157-
)
158-
.span_suggestion(
159-
adjusted_arm_span(cx, move_arm.span),
160-
"and remove this obsolete arm",
161-
"",
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+
],
162158
appl,
163159
)
164160
.help("try changing either arm body");

tests/ui/match_same_arms2.fixed

+259
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
#![warn(clippy::match_same_arms)]
2+
#![allow(
3+
clippy::disallowed_names,
4+
clippy::diverging_sub_expression,
5+
clippy::uninlined_format_args,
6+
clippy::match_single_binding,
7+
clippy::match_like_matches_macro
8+
)]
9+
10+
fn bar<T>(_: T) {}
11+
fn foo() -> bool {
12+
unimplemented!()
13+
}
14+
15+
fn match_same_arms() {
16+
let _ = match 42 {
17+
_ => {
18+
foo();
19+
let mut a = 42 + [23].len() as i32;
20+
if true {
21+
a += 7;
22+
}
23+
a = -31 - a;
24+
a
25+
},
26+
};
27+
//~^^^^^^^^^^^^^^^^^^^ ERROR: this match arm has an identical body to the `_` wildcard arm
28+
29+
let _ = match 42 {
30+
51 | 42 => foo(), //~ ERROR: this match arm has an identical body to another arm
31+
_ => true,
32+
};
33+
34+
let _ = match Some(42) {
35+
None | Some(_) => 24, //~ ERROR: this match arm has an identical body to another arm
36+
};
37+
38+
let _ = match Some(42) {
39+
Some(foo) => 24,
40+
None => 24,
41+
};
42+
43+
let _ = match Some(42) {
44+
Some(42) => 24,
45+
Some(a) => 24, // bindings are different
46+
None => 0,
47+
};
48+
49+
let _ = match Some(42) {
50+
Some(a) if a > 0 => 24,
51+
Some(a) => 24, // one arm has a guard
52+
None => 0,
53+
};
54+
55+
match (Some(42), Some(42)) {
56+
(None, Some(a)) | (Some(a), None) => bar(a), //~ ERROR: this match arm has an identical body to another arm
57+
_ => (),
58+
}
59+
60+
// No warning because guards are different
61+
let _ = match Some(42) {
62+
Some(a) if a == 42 => a,
63+
Some(a) if a == 24 => a,
64+
Some(_) => 24,
65+
None => 0,
66+
};
67+
68+
let _ = match (Some(42), Some(42)) {
69+
(None, Some(a)) | (Some(a), None) if a == 42 => a, //~ ERROR: this match arm has an identical body to another arm
70+
_ => 0,
71+
};
72+
73+
match (Some(42), Some(42)) {
74+
(Some(a), ..) | (.., Some(a)) => bar(a), //~ ERROR: this match arm has an identical body to another arm
75+
_ => (),
76+
}
77+
78+
let _ = match Some(()) {
79+
Some(()) => 0.0,
80+
None => -0.0,
81+
};
82+
83+
match (Some(42), Some("")) {
84+
(Some(a), None) => bar(a),
85+
(None, Some(a)) => bar(a), // bindings have different types
86+
_ => (),
87+
}
88+
89+
let x: Result<i32, &str> = Ok(3);
90+
91+
// No warning because of the guard.
92+
match x {
93+
Ok(x) if x * x == 64 => println!("ok"),
94+
Ok(_) => println!("ok"),
95+
Err(_) => println!("err"),
96+
}
97+
98+
// This used to be a false positive; see issue #1996.
99+
match x {
100+
Ok(3) => println!("ok"),
101+
Ok(x) if x * x == 64 => println!("ok 64"),
102+
Ok(_) => println!("ok"),
103+
Err(_) => println!("err"),
104+
}
105+
106+
match (x, Some(1i32)) {
107+
(Ok(x), Some(_)) | (Ok(_), Some(x)) => println!("ok {}", x), //~ ERROR: this match arm has an identical body to another arm
108+
_ => println!("err"),
109+
}
110+
111+
// No warning; different types for `x`.
112+
match (x, Some(1.0f64)) {
113+
(Ok(x), Some(_)) => println!("ok {}", x),
114+
(Ok(_), Some(x)) => println!("ok {}", x),
115+
_ => println!("err"),
116+
}
117+
118+
// False negative #2251.
119+
match x {
120+
Ok(_tmp) => println!("ok"),
121+
Ok(_) | Ok(3) => println!("ok"), //~ ERROR: this match arm has an identical body to another arm
122+
Err(_) => {
123+
unreachable!();
124+
},
125+
}
126+
127+
// False positive #1390
128+
macro_rules! empty {
129+
($e:expr) => {};
130+
}
131+
match 0 {
132+
0 => {
133+
empty!(0);
134+
},
135+
1 => {
136+
empty!(1);
137+
},
138+
x => {
139+
empty!(x);
140+
},
141+
};
142+
143+
// still lint if the tokens are the same
144+
match 0 {
145+
1 | 0 => {
146+
empty!(0);
147+
},
148+
x => {
149+
empty!(x);
150+
},
151+
}
152+
//~^^^^^^^ ERROR: this match arm has an identical body to another arm
153+
154+
match_expr_like_matches_macro_priority();
155+
}
156+
157+
fn match_expr_like_matches_macro_priority() {
158+
enum E {
159+
A,
160+
B,
161+
C,
162+
}
163+
let x = E::A;
164+
let _ans = match x {
165+
E::A => false,
166+
E::B => false,
167+
_ => true,
168+
};
169+
}
170+
171+
fn main() {
172+
let _ = match Some(0) {
173+
Some(0) => 0,
174+
Some(1) => 1,
175+
#[cfg(feature = "foo")]
176+
Some(2) => 2,
177+
_ => 1,
178+
};
179+
180+
enum Foo {
181+
X(u32),
182+
Y(u32),
183+
Z(u32),
184+
}
185+
186+
// Don't lint. `Foo::X(0)` and `Foo::Z(_)` overlap with the arm in between.
187+
let _ = match Foo::X(0) {
188+
Foo::X(0) => 1,
189+
Foo::X(_) | Foo::Y(_) | Foo::Z(0) => 2,
190+
Foo::Z(_) => 1,
191+
_ => 0,
192+
};
193+
194+
// Suggest moving `Foo::Z(_)` up.
195+
let _ = match Foo::X(0) {
196+
Foo::X(0) | Foo::Z(_) => 1, //~ ERROR: this match arm has an identical body to another arm
197+
Foo::X(_) | Foo::Y(_) => 2,
198+
_ => 0,
199+
};
200+
201+
// Suggest moving `Foo::X(0)` down.
202+
let _ = match Foo::X(0) {
203+
Foo::Y(_) | Foo::Z(0) => 2,
204+
Foo::Z(_) | Foo::X(0) => 1, //~ ERROR: this match arm has an identical body to another arm
205+
_ => 0,
206+
};
207+
208+
// Don't lint.
209+
let _ = match 0 {
210+
-2 => 1,
211+
-5..=50 => 2,
212+
-150..=88 => 1,
213+
_ => 3,
214+
};
215+
216+
struct Bar {
217+
x: u32,
218+
y: u32,
219+
z: u32,
220+
}
221+
222+
// Lint.
223+
let _ = match None {
224+
Some(Bar { y: 10, z: 0, .. }) => 2,
225+
None => 50,
226+
Some(Bar { y: 0, x: 5, .. }) | Some(Bar { x: 0, y: 5, .. }) => 1, //~ ERROR: this match arm has an identical body to another arm
227+
_ => 200,
228+
};
229+
230+
let _ = match 0 {
231+
0 => todo!(),
232+
1 => todo!(),
233+
2 => core::convert::identity::<u32>(todo!()),
234+
3 => core::convert::identity::<u32>(todo!()),
235+
_ => 5,
236+
};
237+
238+
let _ = match 0 {
239+
1 | 0 => cfg!(not_enable),
240+
_ => false,
241+
};
242+
}
243+
244+
// issue #8919, fixed on https://github.com/rust-lang/rust/pull/97312
245+
mod with_lifetime {
246+
enum MaybeStaticStr<'a> {
247+
Static(&'static str),
248+
Borrowed(&'a str),
249+
}
250+
251+
impl<'a> MaybeStaticStr<'a> {
252+
fn get(&self) -> &'a str {
253+
match *self {
254+
MaybeStaticStr::Borrowed(s) | MaybeStaticStr::Static(s) => s,
255+
//~^ ERROR: this match arm has an identical body to another arm
256+
}
257+
}
258+
}
259+
}

tests/ui/match_same_arms2.rs

-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77
clippy::match_like_matches_macro
88
)]
99

10-
//@no-rustfix: need to change the suggestion to a multipart suggestion
11-
1210
fn bar<T>(_: T) {}
1311
fn foo() -> bool {
1412
unimplemented!()

0 commit comments

Comments
 (0)