Skip to content

Commit ea54123

Browse files
authored
fix(collapsible_match): exclude binding modes from struct field pattern suggestions (#15608)
Fixes #13287 changelog: [`collapsible_match`]: exclude binding modes from struct field pattern suggestions
2 parents c1f6124 + fba062b commit ea54123

File tree

3 files changed

+78
-39
lines changed

3 files changed

+78
-39
lines changed

clippy_lints/src/matches/collapsible_match.rs

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use rustc_hir::LangItem::OptionNone;
1313
use rustc_hir::{Arm, Expr, ExprKind, HirId, Pat, PatExpr, PatExprKind, PatKind};
1414
use rustc_lint::LateContext;
1515
use rustc_span::Span;
16+
use rustc_span::symbol::Ident;
1617

1718
use super::{COLLAPSIBLE_MATCH, pat_contains_disallowed_or};
1819

@@ -50,15 +51,17 @@ fn check_arm<'tcx>(
5051
if let Some(inner) = IfLetOrMatch::parse(cx, inner_expr)
5152
&& let Some((inner_scrutinee, inner_then_pat, inner_else_body)) = match inner {
5253
IfLetOrMatch::IfLet(scrutinee, pat, _, els, _) => Some((scrutinee, pat, els)),
53-
IfLetOrMatch::Match(scrutinee, arms, ..) => if arms.len() == 2 && arms.iter().all(|a| a.guard.is_none())
54-
// if there are more than two arms, collapsing would be non-trivial
55-
// one of the arms must be "wild-like"
56-
&& let Some(wild_idx) = arms.iter().rposition(|a| arm_is_wild_like(cx, a))
57-
{
58-
let (then, els) = (&arms[1 - wild_idx], &arms[wild_idx]);
59-
Some((scrutinee, then.pat, Some(els.body)))
60-
} else {
61-
None
54+
IfLetOrMatch::Match(scrutinee, arms, ..) => {
55+
if arms.len() == 2 && arms.iter().all(|a| a.guard.is_none())
56+
// if there are more than two arms, collapsing would be non-trivial
57+
// one of the arms must be "wild-like"
58+
&& let Some(wild_idx) = arms.iter().rposition(|a| arm_is_wild_like(cx, a))
59+
{
60+
let (then, els) = (&arms[1 - wild_idx], &arms[wild_idx]);
61+
Some((scrutinee, then.pat, Some(els.body)))
62+
} else {
63+
None
64+
}
6265
},
6366
}
6467
&& outer_pat.span.eq_ctxt(inner_scrutinee.span)
@@ -68,18 +71,16 @@ fn check_arm<'tcx>(
6871
&& !pat_contains_disallowed_or(cx, inner_then_pat, msrv)
6972
// the binding must come from the pattern of the containing match arm
7073
// ..<local>.. => match <local> { .. }
71-
&& let (Some(binding_span), is_innermost_parent_pat_struct)
72-
= find_pat_binding_and_is_innermost_parent_pat_struct(outer_pat, binding_id)
74+
&& let (Some((binding_ident, binding_span)), is_innermost_parent_pat_struct) =
75+
find_pat_binding_and_is_innermost_parent_pat_struct(outer_pat, binding_id)
7376
// the "else" branches must be equal
7477
&& match (outer_else_body, inner_else_body) {
7578
(None, None) => true,
7679
(None, Some(e)) | (Some(e), None) => is_unit_expr(e),
7780
(Some(a), Some(b)) => SpanlessEq::new(cx).eq_expr(a, b),
7881
}
7982
// the binding must not be used in the if guard
80-
&& outer_guard.is_none_or(
81-
|e| !is_local_used(cx, e, binding_id)
82-
)
83+
&& outer_guard.is_none_or(|e| !is_local_used(cx, e, binding_id))
8384
// ...or anywhere in the inner expression
8485
&& match inner {
8586
IfLetOrMatch::IfLet(_, _, body, els, _) => {
@@ -103,7 +104,7 @@ fn check_arm<'tcx>(
103104
// collapsing patterns need an explicit field name in struct pattern matching
104105
// ex: Struct {x: Some(1)}
105106
let replace_msg = if is_innermost_parent_pat_struct {
106-
format!(", prefixed by `{}`:", snippet(cx, binding_span, "their field name"))
107+
format!(", prefixed by `{binding_ident}: `")
107108
} else {
108109
String::new()
109110
};
@@ -140,16 +141,16 @@ fn arm_is_wild_like(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool {
140141
}
141142
}
142143

143-
fn find_pat_binding_and_is_innermost_parent_pat_struct(pat: &Pat<'_>, hir_id: HirId) -> (Option<Span>, bool) {
144-
let mut span = None;
144+
fn find_pat_binding_and_is_innermost_parent_pat_struct(pat: &Pat<'_>, hir_id: HirId) -> (Option<(Ident, Span)>, bool) {
145+
let mut binding = None;
145146
let mut is_innermost_parent_pat_struct = false;
146-
pat.walk_short(|p| match &p.kind {
147+
pat.walk_short(|p| match p.kind {
147148
// ignore OR patterns
148149
PatKind::Or(_) => false,
149-
PatKind::Binding(_bm, _, _ident, _) => {
150+
PatKind::Binding(_bm, _, ident, _) => {
150151
let found = p.hir_id == hir_id;
151152
if found {
152-
span = Some(p.span);
153+
binding = Some((ident, p.span));
153154
}
154155
!found
155156
},
@@ -158,7 +159,7 @@ fn find_pat_binding_and_is_innermost_parent_pat_struct(pat: &Pat<'_>, hir_id: Hi
158159
true
159160
},
160161
});
161-
(span, is_innermost_parent_pat_struct)
162+
(binding, is_innermost_parent_pat_struct)
162163
}
163164

164165
/// Builds a chain of reference-manipulation method calls (e.g., `.as_ref()`, `.as_mut()`,

tests/ui/collapsible_match.rs

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -304,16 +304,25 @@ pub fn test_2(x: Issue9647) {
304304
}
305305
}
306306

307-
// https://github.com/rust-lang/rust-clippy/issues/14281
308-
fn lint_emitted_at_right_node(opt: Option<Result<u64, String>>) {
309-
let n = match opt {
310-
#[expect(clippy::collapsible_match)]
311-
Some(n) => match n {
312-
Ok(n) => n,
313-
_ => return,
314-
},
315-
None => return,
316-
};
307+
mod issue_13287 {
308+
enum Token {
309+
Name,
310+
Other,
311+
}
312+
313+
struct Error {
314+
location: u32,
315+
token: Option<Token>,
316+
}
317+
318+
fn struct_field_pat_with_binding_mode(err: Option<Error>) {
319+
if let Some(Error { ref token, .. }) = err {
320+
if let Some(Token::Name) = token {
321+
//~^ collapsible_match
322+
println!("token used as a ref");
323+
}
324+
}
325+
}
317326
}
318327

319328
pub fn issue_14155() {
@@ -357,6 +366,18 @@ pub fn issue_14155() {
357366
}
358367
}
359368

369+
// https://github.com/rust-lang/rust-clippy/issues/14281
370+
fn lint_emitted_at_right_node(opt: Option<Result<u64, String>>) {
371+
let n = match opt {
372+
#[expect(clippy::collapsible_match)]
373+
Some(n) => match n {
374+
Ok(n) => n,
375+
_ => return,
376+
},
377+
None => return,
378+
};
379+
}
380+
360381
fn make<T>() -> T {
361382
unimplemented!()
362383
}

tests/ui/collapsible_match.stderr

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ help: the outer pattern can be modified to include the inner pattern
230230
LL | if let Issue9647::A { a, .. } = x {
231231
| ^ replace this binding
232232
LL | if let Some(u) = a {
233-
| ^^^^^^^ with this pattern, prefixed by `a`:
233+
| ^^^^^^^ with this pattern, prefixed by `a: `
234234

235235
error: this `if let` can be collapsed into the outer `if let`
236236
--> tests/ui/collapsible_match.rs:299:9
@@ -250,8 +250,25 @@ LL | if let Issue9647::A { a: Some(a), .. } = x {
250250
LL | if let Some(u) = a {
251251
| ^^^^^^^ with this pattern
252252

253+
error: this `if let` can be collapsed into the outer `if let`
254+
--> tests/ui/collapsible_match.rs:320:13
255+
|
256+
LL | / if let Some(Token::Name) = token {
257+
LL | |
258+
LL | | println!("token used as a ref");
259+
LL | | }
260+
| |_____________^
261+
|
262+
help: the outer pattern can be modified to include the inner pattern
263+
--> tests/ui/collapsible_match.rs:319:29
264+
|
265+
LL | if let Some(Error { ref token, .. }) = err {
266+
| ^^^^^^^^^ replace this binding
267+
LL | if let Some(Token::Name) = token {
268+
| ^^^^^^^^^^^^^^^^^ with this pattern, prefixed by `token: `
269+
253270
error: this `match` can be collapsed into the outer `if let`
254-
--> tests/ui/collapsible_match.rs:322:9
271+
--> tests/ui/collapsible_match.rs:331:9
255272
|
256273
LL | / match *last {
257274
LL | |
@@ -263,7 +280,7 @@ LL | | }
263280
| |_________^
264281
|
265282
help: the outer pattern can be modified to include the inner pattern
266-
--> tests/ui/collapsible_match.rs:321:17
283+
--> tests/ui/collapsible_match.rs:330:17
267284
|
268285
LL | if let Some(last) = arr.last() {
269286
| ^^^^ ---------- use: `arr.last().copied()`
@@ -274,7 +291,7 @@ LL | "a" | "b" => {
274291
| ^^^^^^^^^ with this pattern
275292

276293
error: this `match` can be collapsed into the outer `if let`
277-
--> tests/ui/collapsible_match.rs:332:9
294+
--> tests/ui/collapsible_match.rs:341:9
278295
|
279296
LL | / match &last {
280297
LL | |
@@ -286,7 +303,7 @@ LL | | }
286303
| |_________^
287304
|
288305
help: the outer pattern can be modified to include the inner pattern
289-
--> tests/ui/collapsible_match.rs:331:17
306+
--> tests/ui/collapsible_match.rs:340:17
290307
|
291308
LL | if let Some(last) = arr.last() {
292309
| ^^^^ ---------- use: `arr.last().as_ref()`
@@ -297,7 +314,7 @@ LL | &&"a" | &&"b" => {
297314
| ^^^^^^^^^^^^^ with this pattern
298315

299316
error: this `match` can be collapsed into the outer `if let`
300-
--> tests/ui/collapsible_match.rs:342:9
317+
--> tests/ui/collapsible_match.rs:351:9
301318
|
302319
LL | / match &mut last {
303320
LL | |
@@ -309,7 +326,7 @@ LL | | }
309326
| |_________^
310327
|
311328
help: the outer pattern can be modified to include the inner pattern
312-
--> tests/ui/collapsible_match.rs:341:17
329+
--> tests/ui/collapsible_match.rs:350:17
313330
|
314331
LL | if let Some(mut last) = arr.last_mut() {
315332
| ^^^^^^^^ -------------- use: `arr.last_mut().as_mut()`
@@ -319,5 +336,5 @@ LL | if let Some(mut last) = arr.last_mut() {
319336
LL | &mut &mut "a" | &mut &mut "b" => {
320337
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ with this pattern
321338

322-
error: aborting due to 16 previous errors
339+
error: aborting due to 17 previous errors
323340

0 commit comments

Comments
 (0)