Skip to content

Commit 3efde9f

Browse files
fix: collapsible_match suggests ref/derefs when needed
Fixes: #14155
1 parent e0e2a93 commit 3efde9f

File tree

6 files changed

+184
-8
lines changed

6 files changed

+184
-8
lines changed

clippy_lints/src/matches/collapsible_match.rs

+44-5
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,22 @@ use clippy_utils::msrvs::Msrv;
44
use clippy_utils::source::snippet;
55
use clippy_utils::visitors::is_local_used;
66
use clippy_utils::{
7-
SpanlessEq, is_res_lang_ctor, is_unit_expr, path_to_local, peel_blocks_with_stmt, peel_ref_operators,
7+
SpanlessEq, get_ref_operators, is_res_lang_ctor, is_unit_expr, path_to_local, peel_blocks_with_stmt,
8+
peel_ref_operators,
89
};
10+
use rustc_ast::BorrowKind;
911
use rustc_errors::MultiSpan;
1012
use rustc_hir::LangItem::OptionNone;
11-
use rustc_hir::{Arm, Expr, HirId, Pat, PatExpr, PatExprKind, PatKind};
13+
use rustc_hir::{Arm, Expr, ExprKind, HirId, Pat, PatExpr, PatExprKind, PatKind};
1214
use rustc_lint::LateContext;
1315
use rustc_span::Span;
1416

1517
use super::{COLLAPSIBLE_MATCH, pat_contains_disallowed_or};
1618

17-
pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>], msrv: Msrv) {
19+
pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arms: &'tcx [Arm<'_>], msrv: Msrv) {
1820
if let Some(els_arm) = arms.iter().rfind(|arm| arm_is_wild_like(cx, arm)) {
1921
for arm in arms {
20-
check_arm(cx, true, arm.pat, arm.body, arm.guard, Some(els_arm.body), msrv);
22+
check_arm(cx, true, arm.pat, expr, arm.body, arm.guard, Some(els_arm.body), msrv);
2123
}
2224
}
2325
}
@@ -27,15 +29,18 @@ pub(super) fn check_if_let<'tcx>(
2729
pat: &'tcx Pat<'_>,
2830
body: &'tcx Expr<'_>,
2931
else_expr: Option<&'tcx Expr<'_>>,
32+
let_expr: &'tcx Expr<'_>,
3033
msrv: Msrv,
3134
) {
32-
check_arm(cx, false, pat, body, None, else_expr, msrv);
35+
check_arm(cx, false, pat, let_expr, body, None, else_expr, msrv);
3336
}
3437

38+
#[allow(clippy::too_many_arguments)]
3539
fn check_arm<'tcx>(
3640
cx: &LateContext<'tcx>,
3741
outer_is_match: bool,
3842
outer_pat: &'tcx Pat<'tcx>,
43+
outer_cond: &'tcx Expr<'tcx>,
3944
outer_then_body: &'tcx Expr<'tcx>,
4045
outer_guard: Option<&'tcx Expr<'tcx>>,
4146
outer_else_body: Option<&'tcx Expr<'tcx>>,
@@ -82,6 +87,9 @@ fn check_arm<'tcx>(
8287
},
8388
IfLetOrMatch::Match(_, arms, ..) => !arms.iter().any(|arm| is_local_used(cx, arm, binding_id)),
8489
}
90+
// Check if the inner expression contains any borrows/dereferences
91+
&& let ref_types = get_ref_operators(cx, inner_scrutinee)
92+
&& let Some(method) = build_ref_method_chain(ref_types)
8593
{
8694
let msg = format!(
8795
"this `{}` can be collapsed into the outer `{}`",
@@ -103,6 +111,10 @@ fn check_arm<'tcx>(
103111
let mut help_span = MultiSpan::from_spans(vec![binding_span, inner_then_pat.span]);
104112
help_span.push_span_label(binding_span, "replace this binding");
105113
help_span.push_span_label(inner_then_pat.span, format!("with this pattern{replace_msg}"));
114+
if !method.is_empty() {
115+
let outer_cond_msg = format!("use: `{}{}`", snippet(cx, outer_cond.span, ".."), method);
116+
help_span.push_span_label(outer_cond.span, outer_cond_msg);
117+
}
106118
diag.span_help(
107119
help_span,
108120
"the outer pattern can be modified to include the inner pattern",
@@ -148,3 +160,30 @@ fn find_pat_binding_and_is_innermost_parent_pat_struct(pat: &Pat<'_>, hir_id: Hi
148160
});
149161
(span, is_innermost_parent_pat_struct)
150162
}
163+
164+
/// Builds a chain of reference-manipulation method calls (e.g., `.as_ref()`, `.as_mut()`,
165+
/// `.copied()`) based on reference operators
166+
fn build_ref_method_chain(expr: Vec<&Expr<'_>>) -> Option<String> {
167+
let mut req_method_calls = String::new();
168+
169+
for ref_operator in expr {
170+
match ref_operator.kind {
171+
ExprKind::AddrOf(BorrowKind::Raw, _, _) => {
172+
return None;
173+
},
174+
ExprKind::AddrOf(_, m, _) if m.is_mut() => {
175+
req_method_calls.push_str(".as_mut()");
176+
},
177+
ExprKind::AddrOf(_, _, _) => {
178+
req_method_calls.push_str(".as_ref()");
179+
},
180+
// Deref operator is the only operator that this function should have received
181+
ExprKind::Unary(_, _) => {
182+
req_method_calls.push_str(".copied()");
183+
},
184+
_ => (),
185+
}
186+
}
187+
188+
Some(req_method_calls)
189+
}

clippy_lints/src/matches/mod.rs

+9-2
Original file line numberDiff line numberDiff line change
@@ -1111,7 +1111,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
11111111
significant_drop_in_scrutinee::check_match(cx, expr, ex, arms, source);
11121112
}
11131113

1114-
collapsible_match::check_match(cx, arms, self.msrv);
1114+
collapsible_match::check_match(cx, ex, arms, self.msrv);
11151115
if !from_expansion {
11161116
// These don't depend on a relationship between multiple arms
11171117
match_wild_err_arm::check(cx, ex, arms);
@@ -1176,7 +1176,14 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
11761176
match_ref_pats::check(cx, ex, arms.iter().map(|el| el.pat), expr);
11771177
}
11781178
} else if let Some(if_let) = higher::IfLet::hir(cx, expr) {
1179-
collapsible_match::check_if_let(cx, if_let.let_pat, if_let.if_then, if_let.if_else, self.msrv);
1179+
collapsible_match::check_if_let(
1180+
cx,
1181+
if_let.let_pat,
1182+
if_let.if_then,
1183+
if_let.if_else,
1184+
if_let.let_expr,
1185+
self.msrv,
1186+
);
11801187
significant_drop_in_scrutinee::check_if_let(cx, expr, if_let.let_expr, if_let.if_then, if_let.if_else);
11811188
if !from_expansion {
11821189
if let Some(else_expr) = if_let.if_else {

clippy_utils/src/lib.rs

+18
Original file line numberDiff line numberDiff line change
@@ -2620,6 +2620,24 @@ pub fn peel_ref_operators<'hir>(cx: &LateContext<'_>, mut expr: &'hir Expr<'hir>
26202620
expr
26212621
}
26222622

2623+
/// Returns a `Vec` of `Expr`s containing `AddrOf` operators (`&`) or deref operators (`*`) of a
2624+
/// given expression.
2625+
pub fn get_ref_operators<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>) -> Vec<&'hir Expr<'hir>> {
2626+
let mut operators = Vec::new();
2627+
peel_hir_expr_while(expr, |expr| match expr.kind {
2628+
ExprKind::AddrOf(_, _, e) => {
2629+
operators.push(expr);
2630+
Some(e)
2631+
},
2632+
ExprKind::Unary(UnOp::Deref, e) if cx.typeck_results().expr_ty(e).is_ref() => {
2633+
operators.push(expr);
2634+
Some(e)
2635+
},
2636+
_ => None,
2637+
});
2638+
operators
2639+
}
2640+
26232641
pub fn is_hir_ty_cfg_dependant(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool {
26242642
if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind
26252643
&& let Res::Def(_, def_id) = path.res

tests/ui/collapsible_match.rs

+41
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,47 @@ fn lint_emitted_at_right_node(opt: Option<Result<u64, String>>) {
315315
};
316316
}
317317

318+
pub fn issue_14155() {
319+
let mut arr = ["a", "b", "c"];
320+
if let Some(last) = arr.last() {
321+
match *last {
322+
//~^ collapsible_match
323+
"a" | "b" => {
324+
unimplemented!()
325+
},
326+
_ => (),
327+
}
328+
}
329+
330+
if let Some(last) = arr.last() {
331+
match &last {
332+
//~^ collapsible_match
333+
&&"a" | &&"b" => {
334+
unimplemented!()
335+
},
336+
_ => (),
337+
}
338+
}
339+
340+
if let Some(mut last) = arr.last_mut() {
341+
match &mut last {
342+
//~^ collapsible_match
343+
&mut &mut "a" | &mut &mut "b" => {
344+
unimplemented!()
345+
},
346+
_ => (),
347+
}
348+
}
349+
350+
const NULL_PTR: *const &'static str = std::ptr::null();
351+
if let Some(last) = arr.last() {
352+
match &raw const *last {
353+
NULL_PTR => unimplemented!(),
354+
_ => (),
355+
}
356+
}
357+
}
358+
318359
fn make<T>() -> T {
319360
unimplemented!()
320361
}

tests/ui/collapsible_match.stderr

+70-1
Original file line numberDiff line numberDiff line change
@@ -250,5 +250,74 @@ LL | if let Issue9647::A { a: Some(a), .. } = x {
250250
LL | if let Some(u) = a {
251251
| ^^^^^^^ with this pattern
252252

253-
error: aborting due to 13 previous errors
253+
error: this `match` can be collapsed into the outer `if let`
254+
--> tests/ui/collapsible_match.rs:321:9
255+
|
256+
LL | / match *last {
257+
LL | |
258+
LL | | "a" | "b" => {
259+
LL | | unimplemented!()
260+
LL | | },
261+
LL | | _ => (),
262+
LL | | }
263+
| |_________^
264+
|
265+
help: the outer pattern can be modified to include the inner pattern
266+
--> tests/ui/collapsible_match.rs:320:17
267+
|
268+
LL | if let Some(last) = arr.last() {
269+
| ^^^^ ---------- use: `arr.last().copied()`
270+
| |
271+
| replace this binding
272+
...
273+
LL | "a" | "b" => {
274+
| ^^^^^^^^^ with this pattern
275+
276+
error: this `match` can be collapsed into the outer `if let`
277+
--> tests/ui/collapsible_match.rs:331:9
278+
|
279+
LL | / match &last {
280+
LL | |
281+
LL | | &&"a" | &&"b" => {
282+
LL | | unimplemented!()
283+
LL | | },
284+
LL | | _ => (),
285+
LL | | }
286+
| |_________^
287+
|
288+
help: the outer pattern can be modified to include the inner pattern
289+
--> tests/ui/collapsible_match.rs:330:17
290+
|
291+
LL | if let Some(last) = arr.last() {
292+
| ^^^^ ---------- use: `arr.last().as_ref()`
293+
| |
294+
| replace this binding
295+
...
296+
LL | &&"a" | &&"b" => {
297+
| ^^^^^^^^^^^^^ with this pattern
298+
299+
error: this `match` can be collapsed into the outer `if let`
300+
--> tests/ui/collapsible_match.rs:341:9
301+
|
302+
LL | / match &mut last {
303+
LL | |
304+
LL | | &mut &mut "a" | &mut &mut "b" => {
305+
LL | | unimplemented!()
306+
LL | | },
307+
LL | | _ => (),
308+
LL | | }
309+
| |_________^
310+
|
311+
help: the outer pattern can be modified to include the inner pattern
312+
--> tests/ui/collapsible_match.rs:340:17
313+
|
314+
LL | if let Some(mut last) = arr.last_mut() {
315+
| ^^^^^^^^ -------------- use: `arr.last_mut().as_mut()`
316+
| |
317+
| replace this binding
318+
...
319+
LL | &mut &mut "a" | &mut &mut "b" => {
320+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ with this pattern
321+
322+
error: aborting due to 16 previous errors
254323

tests/ui/collapsible_match2.stderr

+2
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ LL | | },
7777
help: the outer pattern can be modified to include the inner pattern
7878
--> tests/ui/collapsible_match2.rs:54:14
7979
|
80+
LL | match Some(&[1]) {
81+
| ---------- use: `Some(&[1]).copied()`
8082
LL | Some(s) => match *s {
8183
| ^ replace this binding
8284
LL |

0 commit comments

Comments
 (0)