Skip to content

Commit 5d837b5

Browse files
committed
Auto merge of #9573 - Alexendoo:needless-borrowed-ref-slice, r=dswij
lint nested patterns and slice patterns in `needless_borrowed_reference` Now lints in positions other than top level, e.g. `Some(&ref a)`. Or patterns are excluded as that can cause issues Slice patterns of `ref`s are now linted, e.g. `&[ref a, ref b]`. An easy one to walk into as you might expect that to match against a slice you should use `&[]`, then to get around a `cannot move out of type [T]` error you apply a `ref` changelog: [`needless_borrowed_reference`]: lint nested patterns and slice patterns
2 parents cb8da67 + 52a68dc commit 5d837b5

8 files changed

+266
-88
lines changed

clippy_lints/src/manual_clamp.rs

+2-4
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ fn is_call_max_min_pattern<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>)
324324
outer_arg: &'tcx Expr<'tcx>,
325325
span: Span,
326326
) -> Option<ClampSuggestion<'tcx>> {
327-
if let ExprKind::Call(inner_fn, &[ref first, ref second]) = &inner_call.kind
327+
if let ExprKind::Call(inner_fn, [first, second]) = &inner_call.kind
328328
&& let Some(inner_seg) = segment(cx, inner_fn)
329329
&& let Some(outer_seg) = segment(cx, outer_fn)
330330
{
@@ -377,9 +377,7 @@ fn is_call_max_min_pattern<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>)
377377
/// # ;
378378
/// ```
379379
fn is_match_pattern<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<ClampSuggestion<'tcx>> {
380-
if let ExprKind::Match(value, &[ref first_arm, ref second_arm, ref last_arm], rustc_hir::MatchSource::Normal) =
381-
&expr.kind
382-
{
380+
if let ExprKind::Match(value, [first_arm, second_arm, last_arm], rustc_hir::MatchSource::Normal) = &expr.kind {
383381
// Find possible min/max branches
384382
let minmax_values = |a: &'tcx Arm<'tcx>| {
385383
if let PatKind::Binding(_, var_hir_id, _, None) = &a.pat.kind

clippy_lints/src/map_unit_fn.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -131,12 +131,12 @@ fn reduce_unit_expression<'a>(cx: &LateContext<'_>, expr: &'a hir::Expr<'_>) ->
131131
},
132132
hir::ExprKind::Block(block, _) => {
133133
match (block.stmts, block.expr.as_ref()) {
134-
(&[], Some(inner_expr)) => {
134+
([], Some(inner_expr)) => {
135135
// If block only contains an expression,
136136
// reduce `{ X }` to `X`
137137
reduce_unit_expression(cx, inner_expr)
138138
},
139-
(&[ref inner_stmt], None) => {
139+
([inner_stmt], None) => {
140140
// If block only contains statements,
141141
// reduce `{ X; }` to `X` or `X;`
142142
match inner_stmt.kind {

clippy_lints/src/methods/manual_ok_or.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ fn is_ok_wrapping(cx: &LateContext<'_>, map_expr: &Expr<'_>) -> bool {
5555
if let ExprKind::Closure(&Closure { body, .. }) = map_expr.kind;
5656
let body = cx.tcx.hir().body(body);
5757
if let PatKind::Binding(_, param_id, ..) = body.params[0].pat.kind;
58-
if let ExprKind::Call(Expr { kind: ExprKind::Path(ok_path), .. }, &[ref ok_arg]) = body.value.kind;
58+
if let ExprKind::Call(Expr { kind: ExprKind::Path(ok_path), .. }, [ok_arg]) = body.value.kind;
5959
if is_lang_ctor(cx, ok_path, ResultOk);
6060
then { path_to_local_id(ok_arg, param_id) } else { false }
6161
}
+79-42
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,31 @@
11
use clippy_utils::diagnostics::span_lint_and_then;
2-
use clippy_utils::source::snippet_with_applicability;
3-
use if_chain::if_chain;
42
use rustc_errors::Applicability;
53
use rustc_hir::{BindingAnnotation, Mutability, Node, Pat, PatKind};
64
use rustc_lint::{LateContext, LateLintPass};
75
use rustc_session::{declare_lint_pass, declare_tool_lint};
86

97
declare_clippy_lint! {
108
/// ### What it does
11-
/// Checks for bindings that destructure a reference and borrow the inner
9+
/// Checks for bindings that needlessly destructure a reference and borrow the inner
1210
/// value with `&ref`.
1311
///
1412
/// ### Why is this bad?
1513
/// This pattern has no effect in almost all cases.
1614
///
17-
/// ### Known problems
18-
/// In some cases, `&ref` is needed to avoid a lifetime mismatch error.
19-
/// Example:
20-
/// ```rust
21-
/// fn foo(a: &Option<String>, b: &Option<String>) {
22-
/// match (a, b) {
23-
/// (None, &ref c) | (&ref c, None) => (),
24-
/// (&Some(ref c), _) => (),
25-
/// };
26-
/// }
27-
/// ```
28-
///
2915
/// ### Example
3016
/// ```rust
3117
/// let mut v = Vec::<String>::new();
32-
/// # #[allow(unused)]
3318
/// v.iter_mut().filter(|&ref a| a.is_empty());
19+
///
20+
/// if let &[ref first, ref second] = v.as_slice() {}
3421
/// ```
3522
///
3623
/// Use instead:
3724
/// ```rust
3825
/// let mut v = Vec::<String>::new();
39-
/// # #[allow(unused)]
4026
/// v.iter_mut().filter(|a| a.is_empty());
27+
///
28+
/// if let [first, second] = v.as_slice() {}
4129
/// ```
4230
#[clippy::version = "pre 1.29.0"]
4331
pub NEEDLESS_BORROWED_REFERENCE,
@@ -54,34 +42,83 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBorrowedRef {
5442
return;
5543
}
5644

57-
if_chain! {
58-
// Only lint immutable refs, because `&mut ref T` may be useful.
59-
if let PatKind::Ref(sub_pat, Mutability::Not) = pat.kind;
45+
// Do not lint patterns that are part of an OR `|` pattern, the binding mode must match in all arms
46+
for (_, node) in cx.tcx.hir().parent_iter(pat.hir_id) {
47+
let Node::Pat(pat) = node else { break };
48+
49+
if matches!(pat.kind, PatKind::Or(_)) {
50+
return;
51+
}
52+
}
53+
54+
// Only lint immutable refs, because `&mut ref T` may be useful.
55+
let PatKind::Ref(sub_pat, Mutability::Not) = pat.kind else { return };
6056

57+
match sub_pat.kind {
6158
// Check sub_pat got a `ref` keyword (excluding `ref mut`).
62-
if let PatKind::Binding(BindingAnnotation::REF, .., spanned_name, _) = sub_pat.kind;
63-
let parent_id = cx.tcx.hir().get_parent_node(pat.hir_id);
64-
if let Some(parent_node) = cx.tcx.hir().find(parent_id);
65-
then {
66-
// do not recurse within patterns, as they may have other references
67-
// XXXManishearth we can relax this constraint if we only check patterns
68-
// with a single ref pattern inside them
69-
if let Node::Pat(_) = parent_node {
70-
return;
59+
PatKind::Binding(BindingAnnotation::REF, _, ident, None) => {
60+
span_lint_and_then(
61+
cx,
62+
NEEDLESS_BORROWED_REFERENCE,
63+
pat.span,
64+
"this pattern takes a reference on something that is being dereferenced",
65+
|diag| {
66+
// `&ref ident`
67+
// ^^^^^
68+
let span = pat.span.until(ident.span);
69+
diag.span_suggestion_verbose(
70+
span,
71+
"try removing the `&ref` part",
72+
String::new(),
73+
Applicability::MachineApplicable,
74+
);
75+
},
76+
);
77+
},
78+
// Slices where each element is `ref`: `&[ref a, ref b, ..., ref z]`
79+
PatKind::Slice(
80+
before,
81+
None
82+
| Some(Pat {
83+
kind: PatKind::Wild, ..
84+
}),
85+
after,
86+
) => {
87+
let mut suggestions = Vec::new();
88+
89+
for element_pat in itertools::chain(before, after) {
90+
if let PatKind::Binding(BindingAnnotation::REF, _, ident, None) = element_pat.kind {
91+
// `&[..., ref ident, ...]`
92+
// ^^^^
93+
let span = element_pat.span.until(ident.span);
94+
suggestions.push((span, String::new()));
95+
} else {
96+
return;
97+
}
7198
}
72-
let mut applicability = Applicability::MachineApplicable;
73-
span_lint_and_then(cx, NEEDLESS_BORROWED_REFERENCE, pat.span,
74-
"this pattern takes a reference on something that is being de-referenced",
75-
|diag| {
76-
let hint = snippet_with_applicability(cx, spanned_name.span, "..", &mut applicability).into_owned();
77-
diag.span_suggestion(
78-
pat.span,
79-
"try removing the `&ref` part and just keep",
80-
hint,
81-
applicability,
82-
);
83-
});
84-
}
99+
100+
if !suggestions.is_empty() {
101+
span_lint_and_then(
102+
cx,
103+
NEEDLESS_BORROWED_REFERENCE,
104+
pat.span,
105+
"dereferencing a slice pattern where every element takes a reference",
106+
|diag| {
107+
// `&[...]`
108+
// ^
109+
let span = pat.span.until(sub_pat.span);
110+
suggestions.push((span, String::new()));
111+
112+
diag.multipart_suggestion(
113+
"try removing the `&` and `ref` parts",
114+
suggestions,
115+
Applicability::MachineApplicable,
116+
);
117+
},
118+
);
119+
}
120+
},
121+
_ => {},
85122
}
86123
}
87124
}
+5-13
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,22 @@
11
### What it does
2-
Checks for bindings that destructure a reference and borrow the inner
2+
Checks for bindings that needlessly destructure a reference and borrow the inner
33
value with `&ref`.
44

55
### Why is this bad?
66
This pattern has no effect in almost all cases.
77

8-
### Known problems
9-
In some cases, `&ref` is needed to avoid a lifetime mismatch error.
10-
Example:
11-
```
12-
fn foo(a: &Option<String>, b: &Option<String>) {
13-
match (a, b) {
14-
(None, &ref c) | (&ref c, None) => (),
15-
(&Some(ref c), _) => (),
16-
};
17-
}
18-
```
19-
208
### Example
219
```
2210
let mut v = Vec::<String>::new();
2311
v.iter_mut().filter(|&ref a| a.is_empty());
12+
13+
if let &[ref first, ref second] = v.as_slice() {}
2414
```
2515

2616
Use instead:
2717
```
2818
let mut v = Vec::<String>::new();
2919
v.iter_mut().filter(|a| a.is_empty());
20+
21+
if let [first, second] = v.as_slice() {}
3022
```

tests/ui/needless_borrowed_ref.fixed

+30-11
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,38 @@
11
// run-rustfix
22

3-
#[warn(clippy::needless_borrowed_reference)]
4-
#[allow(unused_variables)]
5-
fn main() {
3+
#![warn(clippy::needless_borrowed_reference)]
4+
#![allow(unused, clippy::needless_borrow)]
5+
6+
fn main() {}
7+
8+
fn should_lint(array: [u8; 4], slice: &[u8], slice_of_refs: &[&u8], vec: Vec<u8>) {
69
let mut v = Vec::<String>::new();
710
let _ = v.iter_mut().filter(|a| a.is_empty());
8-
// ^ should be linted
911

1012
let var = 3;
1113
let thingy = Some(&var);
12-
if let Some(&ref v) = thingy {
13-
// ^ should be linted
14-
}
14+
if let Some(v) = thingy {}
15+
16+
if let &[a, ref b] = slice_of_refs {}
17+
18+
let [a, ..] = &array;
19+
let [a, b, ..] = &array;
20+
21+
if let [a, b] = slice {}
22+
if let [a, b] = &vec[..] {}
23+
24+
if let [a, b, ..] = slice {}
25+
if let [a, .., b] = slice {}
26+
if let [.., a, b] = slice {}
27+
}
28+
29+
fn should_not_lint(array: [u8; 4], slice: &[u8], slice_of_refs: &[&u8], vec: Vec<u8>) {
30+
if let [ref a] = slice {}
31+
if let &[ref a, b] = slice {}
32+
if let &[ref a, .., b] = slice {}
33+
34+
// must not be removed as variables must be bound consistently across | patterns
35+
if let (&[ref a], _) | ([], ref a) = (slice_of_refs, &1u8) {}
1536

1637
let mut var2 = 5;
1738
let thingy2 = Some(&mut var2);
@@ -28,17 +49,15 @@ fn main() {
2849
}
2950
}
3051

31-
#[allow(dead_code)]
3252
enum Animal {
3353
Cat(u64),
3454
Dog(u64),
3555
}
3656

37-
#[allow(unused_variables)]
38-
#[allow(dead_code)]
3957
fn foo(a: &Animal, b: &Animal) {
4058
match (a, b) {
41-
(&Animal::Cat(v), &ref k) | (&ref k, &Animal::Cat(v)) => (), // lifetime mismatch error if there is no '&ref'
59+
// lifetime mismatch error if there is no '&ref' before `feature(nll)` stabilization in 1.63
60+
(&Animal::Cat(v), &ref k) | (&ref k, &Animal::Cat(v)) => (),
4261
// ^ and ^ should **not** be linted
4362
(&Animal::Dog(ref a), &Animal::Dog(_)) => (), // ^ should **not** be linted
4463
}

tests/ui/needless_borrowed_ref.rs

+30-11
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,38 @@
11
// run-rustfix
22

3-
#[warn(clippy::needless_borrowed_reference)]
4-
#[allow(unused_variables)]
5-
fn main() {
3+
#![warn(clippy::needless_borrowed_reference)]
4+
#![allow(unused, clippy::needless_borrow)]
5+
6+
fn main() {}
7+
8+
fn should_lint(array: [u8; 4], slice: &[u8], slice_of_refs: &[&u8], vec: Vec<u8>) {
69
let mut v = Vec::<String>::new();
710
let _ = v.iter_mut().filter(|&ref a| a.is_empty());
8-
// ^ should be linted
911

1012
let var = 3;
1113
let thingy = Some(&var);
12-
if let Some(&ref v) = thingy {
13-
// ^ should be linted
14-
}
14+
if let Some(&ref v) = thingy {}
15+
16+
if let &[&ref a, ref b] = slice_of_refs {}
17+
18+
let &[ref a, ..] = &array;
19+
let &[ref a, ref b, ..] = &array;
20+
21+
if let &[ref a, ref b] = slice {}
22+
if let &[ref a, ref b] = &vec[..] {}
23+
24+
if let &[ref a, ref b, ..] = slice {}
25+
if let &[ref a, .., ref b] = slice {}
26+
if let &[.., ref a, ref b] = slice {}
27+
}
28+
29+
fn should_not_lint(array: [u8; 4], slice: &[u8], slice_of_refs: &[&u8], vec: Vec<u8>) {
30+
if let [ref a] = slice {}
31+
if let &[ref a, b] = slice {}
32+
if let &[ref a, .., b] = slice {}
33+
34+
// must not be removed as variables must be bound consistently across | patterns
35+
if let (&[ref a], _) | ([], ref a) = (slice_of_refs, &1u8) {}
1536

1637
let mut var2 = 5;
1738
let thingy2 = Some(&mut var2);
@@ -28,17 +49,15 @@ fn main() {
2849
}
2950
}
3051

31-
#[allow(dead_code)]
3252
enum Animal {
3353
Cat(u64),
3454
Dog(u64),
3555
}
3656

37-
#[allow(unused_variables)]
38-
#[allow(dead_code)]
3957
fn foo(a: &Animal, b: &Animal) {
4058
match (a, b) {
41-
(&Animal::Cat(v), &ref k) | (&ref k, &Animal::Cat(v)) => (), // lifetime mismatch error if there is no '&ref'
59+
// lifetime mismatch error if there is no '&ref' before `feature(nll)` stabilization in 1.63
60+
(&Animal::Cat(v), &ref k) | (&ref k, &Animal::Cat(v)) => (),
4261
// ^ and ^ should **not** be linted
4362
(&Animal::Dog(ref a), &Animal::Dog(_)) => (), // ^ should **not** be linted
4463
}

0 commit comments

Comments
 (0)