Skip to content

Commit b2e5a71

Browse files
committed
Auto merge of #9685 - kraktus:collapsible_match, r=llogiq
[`collapsible_match`] specify field name when destructuring structs changelog: [`collapsible_match`] specify field name when destructuring structs fix rust-lang/rust-clippy#9647 I wasn't the sure about the best way to convey the message in the lint message since it does not use suggestion. Because I liked the former output highlighting both spans, I've left it as before, only modifying the span label.
2 parents 967f172 + 487c6fc commit b2e5a71

File tree

3 files changed

+72
-6
lines changed

3 files changed

+72
-6
lines changed

clippy_lints/src/matches/collapsible_match.rs

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use clippy_utils::diagnostics::span_lint_and_then;
22
use clippy_utils::higher::IfLetOrMatch;
3+
use clippy_utils::source::snippet;
34
use clippy_utils::visitors::is_local_used;
45
use clippy_utils::{
56
is_res_lang_ctor, is_unit_expr, path_to_local, peel_blocks_with_stmt, peel_ref_operators, SpanlessEq,
@@ -63,7 +64,8 @@ fn check_arm<'tcx>(
6364
if !pat_contains_or(inner_then_pat);
6465
// the binding must come from the pattern of the containing match arm
6566
// ..<local>.. => match <local> { .. }
66-
if let Some(binding_span) = find_pat_binding(outer_pat, binding_id);
67+
if let (Some(binding_span), is_innermost_parent_pat_struct)
68+
= find_pat_binding_and_is_innermost_parent_pat_struct(outer_pat, binding_id);
6769
// the "else" branches must be equal
6870
if match (outer_else_body, inner_else_body) {
6971
(None, None) => true,
@@ -88,6 +90,13 @@ fn check_arm<'tcx>(
8890
if matches!(inner, IfLetOrMatch::Match(..)) { "match" } else { "if let" },
8991
if outer_is_match { "match" } else { "if let" },
9092
);
93+
// collapsing patterns need an explicit field name in struct pattern matching
94+
// ex: Struct {x: Some(1)}
95+
let replace_msg = if is_innermost_parent_pat_struct {
96+
format!(", prefixed by {}:", snippet(cx, binding_span, "their field name"))
97+
} else {
98+
String::new()
99+
};
91100
span_lint_and_then(
92101
cx,
93102
COLLAPSIBLE_MATCH,
@@ -96,7 +105,7 @@ fn check_arm<'tcx>(
96105
|diag| {
97106
let mut help_span = MultiSpan::from_spans(vec![binding_span, inner_then_pat.span]);
98107
help_span.push_span_label(binding_span, "replace this binding");
99-
help_span.push_span_label(inner_then_pat.span, "with this pattern");
108+
help_span.push_span_label(inner_then_pat.span, format!("with this pattern{replace_msg}"));
100109
diag.span_help(help_span, "the outer pattern can be modified to include the inner pattern");
101110
},
102111
);
@@ -117,8 +126,9 @@ fn arm_is_wild_like(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool {
117126
}
118127
}
119128

120-
fn find_pat_binding(pat: &Pat<'_>, hir_id: HirId) -> Option<Span> {
129+
fn find_pat_binding_and_is_innermost_parent_pat_struct(pat: &Pat<'_>, hir_id: HirId) -> (Option<Span>, bool) {
121130
let mut span = None;
131+
let mut is_innermost_parent_pat_struct = false;
122132
pat.walk_short(|p| match &p.kind {
123133
// ignore OR patterns
124134
PatKind::Or(_) => false,
@@ -129,9 +139,12 @@ fn find_pat_binding(pat: &Pat<'_>, hir_id: HirId) -> Option<Span> {
129139
}
130140
!found
131141
},
132-
_ => true,
142+
_ => {
143+
is_innermost_parent_pat_struct = matches!(p.kind, PatKind::Struct(..));
144+
true
145+
},
133146
});
134-
span
147+
(span, is_innermost_parent_pat_struct)
135148
}
136149

137150
fn pat_contains_or(pat: &Pat<'_>) -> bool {

tests/ui/collapsible_match.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,27 @@ fn negative_cases(res_opt: Result<Option<u32>, String>, res_res: Result<Result<u
253253
};
254254
}
255255

256+
pub enum Issue9647 {
257+
A { a: Option<Option<u8>>, b: () },
258+
B,
259+
}
260+
261+
pub fn test_1(x: Issue9647) {
262+
if let Issue9647::A { a, .. } = x {
263+
if let Some(u) = a {
264+
println!("{u:?}")
265+
}
266+
}
267+
}
268+
269+
pub fn test_2(x: Issue9647) {
270+
if let Issue9647::A { a: Some(a), .. } = x {
271+
if let Some(u) = a {
272+
println!("{u}")
273+
}
274+
}
275+
}
276+
256277
fn make<T>() -> T {
257278
unimplemented!()
258279
}

tests/ui/collapsible_match.stderr

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,5 +175,37 @@ LL | Some(val) => match val {
175175
LL | Some(n) => foo(n),
176176
| ^^^^^^^ with this pattern
177177

178-
error: aborting due to 10 previous errors
178+
error: this `if let` can be collapsed into the outer `if let`
179+
--> $DIR/collapsible_match.rs:263:9
180+
|
181+
LL | / if let Some(u) = a {
182+
LL | | println!("{u:?}")
183+
LL | | }
184+
| |_________^
185+
|
186+
help: the outer pattern can be modified to include the inner pattern
187+
--> $DIR/collapsible_match.rs:262:27
188+
|
189+
LL | if let Issue9647::A { a, .. } = x {
190+
| ^ replace this binding
191+
LL | if let Some(u) = a {
192+
| ^^^^^^^ with this pattern, prefixed by a:
193+
194+
error: this `if let` can be collapsed into the outer `if let`
195+
--> $DIR/collapsible_match.rs:271:9
196+
|
197+
LL | / if let Some(u) = a {
198+
LL | | println!("{u}")
199+
LL | | }
200+
| |_________^
201+
|
202+
help: the outer pattern can be modified to include the inner pattern
203+
--> $DIR/collapsible_match.rs:270:35
204+
|
205+
LL | if let Issue9647::A { a: Some(a), .. } = x {
206+
| ^ replace this binding
207+
LL | if let Some(u) = a {
208+
| ^^^^^^^ with this pattern
209+
210+
error: aborting due to 12 previous errors
179211

0 commit comments

Comments
 (0)