Skip to content

Commit 9e9110e

Browse files
committed
Remove final reference on fields and method calls in needless_borrow
1 parent 15c068e commit 9e9110e

32 files changed

+485
-363
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -981,7 +981,7 @@ Released 2021-03-25
981981
[#6532](https://github.com/rust-lang/rust-clippy/pull/6532)
982982
* [`single_match`] Suggest `if` over `if let` when possible
983983
[#6574](https://github.com/rust-lang/rust-clippy/pull/6574)
984-
* [`ref_in_deref`] Use parentheses correctly in suggestion
984+
* `ref_in_deref` Use parentheses correctly in suggestion
985985
[#6609](https://github.com/rust-lang/rust-clippy/pull/6609)
986986
* [`stable_sort_primitive`] Clarify error message
987987
[#6611](https://github.com/rust-lang/rust-clippy/pull/6611)

clippy_lints/src/dereference.rs

Lines changed: 135 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
22
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
3+
use clippy_utils::sugg::has_enclosing_paren;
34
use clippy_utils::ty::peel_mid_ty_refs;
45
use clippy_utils::{get_parent_expr, get_parent_node, is_lint_allowed, path_to_local};
56
use rustc_ast::util::parser::{PREC_POSTFIX, PREC_PREFIX};
@@ -130,8 +131,6 @@ pub struct Dereferencing {
130131
struct StateData {
131132
/// Span of the top level expression
132133
span: Span,
133-
/// The required mutability
134-
target_mut: Mutability,
135134
}
136135

137136
enum State {
@@ -140,9 +139,13 @@ enum State {
140139
// The number of calls in a sequence which changed the referenced type
141140
ty_changed_count: usize,
142141
is_final_ufcs: bool,
142+
/// The required mutability
143+
target_mut: Mutability,
143144
},
144145
DerefedBorrow {
145-
count: u32,
146+
count: usize,
147+
required_precedence: i8,
148+
msg: &'static str,
146149
},
147150
}
148151

@@ -213,77 +216,98 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing {
213216
1
214217
},
215218
is_final_ufcs: matches!(expr.kind, ExprKind::Call(..)),
216-
},
217-
StateData {
218-
span: expr.span,
219219
target_mut,
220220
},
221+
StateData { span: expr.span },
221222
));
222223
},
223224
RefOp::AddrOf => {
224225
// Find the number of times the borrow is auto-derefed.
225226
let mut iter = find_adjustments(cx.tcx, typeck, expr).iter();
226-
if let Some((i, adjust)) = iter.by_ref().enumerate().find_map(|(i, adjust)| {
227-
if !matches!(adjust.kind, Adjust::Deref(_)) {
228-
Some((i, Some(adjust)))
229-
} else if !adjust.target.is_ref() {
230-
// Include the current deref.
231-
Some((i + 1, None))
232-
} else {
233-
None
234-
}
235-
}) {
236-
if i > 1 {
237-
// If the next adjustment is a mutable borrow, then check to see if the compiler will
238-
// insert a re-borrow here. If not, leave an extra borrow here to avoid attempting to
239-
// move the a mutable reference.
240-
let (i, target_mut) = if let Some(&Adjust::Borrow(AutoBorrow::Ref(_, mutability))) =
241-
adjust.or_else(|| iter.next()).map(|a| &a.kind)
242-
{
243-
if matches!(mutability, AutoBorrowMutability::Mut { .. })
244-
&& !is_auto_reborrow_position(parent, expr.hir_id)
245-
{
246-
(i - 1, Mutability::Mut)
247-
} else {
248-
(i, mutability.into())
227+
let mut deref_count = 0usize;
228+
let next_adjust = loop {
229+
match iter.next() {
230+
Some(adjust) => {
231+
if !matches!(adjust.kind, Adjust::Deref(_)) {
232+
break Some(adjust);
233+
} else if !adjust.target.is_ref() {
234+
deref_count += 1;
235+
break iter.next();
249236
}
250-
} else {
251-
(
252-
i,
253-
iter.find_map(|adjust| match adjust.kind {
254-
Adjust::Borrow(AutoBorrow::Ref(_, m)) => Some(m.into()),
255-
_ => None,
256-
})
257-
// This default should never happen. Auto-deref always reborrows.
258-
.unwrap_or(Mutability::Not),
259-
)
260-
};
261-
262-
if i > 1 {
263-
self.state = Some((
264-
// Subtract one for the current borrow expression, and one to cover the last
265-
// reference which can't be removed (it's either reborrowed, or needed for
266-
// auto-deref to happen).
267-
State::DerefedBorrow {
268-
count:
269-
// Truncation here would require more than a `u32::MAX` level reference. The compiler
270-
// does not support this.
271-
#[allow(clippy::cast_possible_truncation)]
272-
{ i as u32 - 2 }
273-
},
274-
StateData {
275-
span: expr.span,
276-
target_mut,
277-
},
278-
));
279-
}
237+
deref_count += 1;
238+
},
239+
None => break None,
240+
};
241+
};
242+
243+
// Determine the required number of references before any can be removed. In all cases the
244+
// reference made by the current expression will be removed. After that there are four cases to
245+
// handle.
246+
//
247+
// 1. Auto-borrow will trigger in the current position, so no further references are required.
248+
// 2. Auto-deref ends at a reference, or the underlying type, so one extra needs to be left to
249+
// handle the automatically inserted re-borrow.
250+
// 3. Auto-deref hits a user-defined `Deref` impl, so at least one reference needs to exist to
251+
// start auto-deref.
252+
// 4. If the chain of non-user-defined derefs ends with a mutable re-borrow, and re-borrow
253+
// adjustments will not be inserted automatically, then leave one further reference to avoid
254+
// moving a mutable borrow.
255+
// e.g.
256+
// fn foo<T>(x: &mut Option<&mut T>, y: &mut T) {
257+
// let x = match x {
258+
// // Removing the borrow will cause `x` to be moved
259+
// Some(x) => &mut *x,
260+
// None => y
261+
// };
262+
// }
263+
let deref_msg =
264+
"this expression creates a reference which is immediately dereferenced by the compiler";
265+
let borrow_msg = "this expression borrows a value the compiler would automatically borrow";
266+
267+
let (required_refs, required_precedence, msg) = if is_auto_borrow_position(parent, expr.hir_id)
268+
{
269+
(1, PREC_POSTFIX, if deref_count == 1 { borrow_msg } else { deref_msg })
270+
} else if let Some(&Adjust::Borrow(AutoBorrow::Ref(_, mutability))) =
271+
next_adjust.map(|a| &a.kind)
272+
{
273+
if matches!(mutability, AutoBorrowMutability::Mut { .. })
274+
&& !is_auto_reborrow_position(parent)
275+
{
276+
(3, 0, deref_msg)
277+
} else {
278+
(2, 0, deref_msg)
280279
}
280+
} else {
281+
(2, 0, deref_msg)
282+
};
283+
284+
if deref_count >= required_refs {
285+
self.state = Some((
286+
State::DerefedBorrow {
287+
// One of the required refs is for the current borrow expression, the remaining ones
288+
// can't be removed without breaking the code. See earlier comment.
289+
count: deref_count - required_refs,
290+
required_precedence,
291+
msg,
292+
},
293+
StateData { span: expr.span },
294+
));
281295
}
282296
},
283297
_ => (),
284298
}
285299
},
286-
(Some((State::DerefMethod { ty_changed_count, .. }, data)), RefOp::Method(_)) => {
300+
(
301+
Some((
302+
State::DerefMethod {
303+
target_mut,
304+
ty_changed_count,
305+
..
306+
},
307+
data,
308+
)),
309+
RefOp::Method(_),
310+
) => {
287311
self.state = Some((
288312
State::DerefMethod {
289313
ty_changed_count: if deref_method_same_type(typeck.expr_ty(expr), typeck.expr_ty(sub_expr)) {
@@ -292,12 +316,30 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing {
292316
ty_changed_count + 1
293317
},
294318
is_final_ufcs: matches!(expr.kind, ExprKind::Call(..)),
319+
target_mut,
295320
},
296321
data,
297322
));
298323
},
299-
(Some((State::DerefedBorrow { count }, data)), RefOp::AddrOf) if count != 0 => {
300-
self.state = Some((State::DerefedBorrow { count: count - 1 }, data));
324+
(
325+
Some((
326+
State::DerefedBorrow {
327+
count,
328+
required_precedence,
329+
msg,
330+
},
331+
data,
332+
)),
333+
RefOp::AddrOf,
334+
) if count != 0 => {
335+
self.state = Some((
336+
State::DerefedBorrow {
337+
count: count - 1,
338+
required_precedence,
339+
msg,
340+
},
341+
data,
342+
));
301343
},
302344

303345
(Some((state, data)), _) => report(cx, expr, state, data),
@@ -475,18 +517,28 @@ fn is_linted_explicit_deref_position(parent: Option<Node<'_>>, child_id: HirId,
475517

476518
/// Checks if the given expression is in a position which can be auto-reborrowed.
477519
/// Note: This is only correct assuming auto-deref is already occurring.
478-
fn is_auto_reborrow_position(parent: Option<Node<'_>>, child_id: HirId) -> bool {
520+
fn is_auto_reborrow_position(parent: Option<Node<'_>>) -> bool {
479521
match parent {
480-
Some(Node::Expr(parent)) => match parent.kind {
481-
ExprKind::MethodCall(..) => true,
482-
ExprKind::Call(callee, _) => callee.hir_id != child_id,
483-
_ => false,
484-
},
522+
Some(Node::Expr(parent)) => matches!(parent.kind, ExprKind::MethodCall(..) | ExprKind::Call(..)),
485523
Some(Node::Local(_)) => true,
486524
_ => false,
487525
}
488526
}
489527

528+
/// Checks if the given expression is a position which can auto-borrow.
529+
fn is_auto_borrow_position(parent: Option<Node<'_>>, child_id: HirId) -> bool {
530+
if let Some(Node::Expr(parent)) = parent {
531+
match parent.kind {
532+
ExprKind::MethodCall(_, _, [self_arg, ..], _) => self_arg.hir_id == child_id,
533+
ExprKind::Field(..) => true,
534+
ExprKind::Call(f, _) => f.hir_id == child_id,
535+
_ => false,
536+
}
537+
} else {
538+
false
539+
}
540+
}
541+
490542
/// Adjustments are sometimes made in the parent block rather than the expression itself.
491543
fn find_adjustments<'tcx>(
492544
tcx: TyCtxt<'tcx>,
@@ -535,6 +587,7 @@ fn report(cx: &LateContext<'_>, expr: &Expr<'_>, state: State, data: StateData)
535587
State::DerefMethod {
536588
ty_changed_count,
537589
is_final_ufcs,
590+
target_mut,
538591
} => {
539592
let mut app = Applicability::MachineApplicable;
540593
let (expr_str, expr_is_macro_call) = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app);
@@ -549,12 +602,12 @@ fn report(cx: &LateContext<'_>, expr: &Expr<'_>, state: State, data: StateData)
549602
};
550603
let addr_of_str = if ty_changed_count < ref_count {
551604
// Check if a reborrow from &mut T -> &T is required.
552-
if data.target_mut == Mutability::Not && matches!(ty.kind(), ty::Ref(_, _, Mutability::Mut)) {
605+
if target_mut == Mutability::Not && matches!(ty.kind(), ty::Ref(_, _, Mutability::Mut)) {
553606
"&*"
554607
} else {
555608
""
556609
}
557-
} else if data.target_mut == Mutability::Mut {
610+
} else if target_mut == Mutability::Mut {
558611
"&mut "
559612
} else {
560613
"&"
@@ -570,7 +623,7 @@ fn report(cx: &LateContext<'_>, expr: &Expr<'_>, state: State, data: StateData)
570623
cx,
571624
EXPLICIT_DEREF_METHODS,
572625
data.span,
573-
match data.target_mut {
626+
match target_mut {
574627
Mutability::Not => "explicit `deref` method call",
575628
Mutability::Mut => "explicit `deref_mut` method call",
576629
},
@@ -579,19 +632,24 @@ fn report(cx: &LateContext<'_>, expr: &Expr<'_>, state: State, data: StateData)
579632
app,
580633
);
581634
},
582-
State::DerefedBorrow { .. } => {
635+
State::DerefedBorrow {
636+
required_precedence,
637+
msg,
638+
..
639+
} => {
583640
let mut app = Applicability::MachineApplicable;
584641
let snip = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app).0;
585642
span_lint_and_sugg(
586643
cx,
587644
NEEDLESS_BORROW,
588645
data.span,
589-
&format!(
590-
"this expression borrows a reference (`{}`) that is immediately dereferenced by the compiler",
591-
cx.typeck_results().expr_ty(expr),
592-
),
646+
msg,
593647
"change this to",
594-
snip.into(),
648+
if required_precedence > expr.precedence().order() && !has_enclosing_paren(&snip) {
649+
format!("({})", snip)
650+
} else {
651+
snip.into()
652+
},
595653
app,
596654
);
597655
},

clippy_lints/src/implicit_hasher.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitHasher {
179179
)
180180
.and_then(|snip| {
181181
let i = snip.find("fn")?;
182-
Some(item.span.lo() + BytePos((i + (&snip[i..]).find('(')?) as u32))
182+
Some(item.span.lo() + BytePos((i + snip[i..].find('(')?) as u32))
183183
})
184184
.expect("failed to create span for type parameters");
185185
Span::new(pos, pos, item.span.ctxt(), item.span.parent())

clippy_lints/src/octal_escapes.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ fn check_lit(cx: &EarlyContext<'_>, lit: &Lit, span: Span, is_string: bool) {
101101
// construct a replacement escape
102102
// the maximum value is \077, or \x3f, so u8 is sufficient here
103103
if let Ok(n) = u8::from_str_radix(&contents[from + 1..to], 8) {
104-
write!(&mut suggest_1, "\\x{:02x}", n).unwrap();
104+
write!(suggest_1, "\\x{:02x}", n).unwrap();
105105
}
106106

107107
// append the null byte as \x00 and the following digits literally

clippy_utils/src/sugg.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ fn binop_to_string(op: AssocOp, lhs: &str, rhs: &str) -> String {
388388
}
389389

390390
/// Return `true` if `sugg` is enclosed in parenthesis.
391-
fn has_enclosing_paren(sugg: impl AsRef<str>) -> bool {
391+
pub fn has_enclosing_paren(sugg: impl AsRef<str>) -> bool {
392392
let mut chars = sugg.as_ref().chars();
393393
if chars.next() == Some('(') {
394394
let mut depth = 1;

tests/ui/bytecount.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#![allow(clippy::needless_borrow)]
2+
13
#[deny(clippy::naive_bytecount)]
24
fn main() {
35
let x = vec![0_u8; 16];

tests/ui/bytecount.stderr

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
error: you appear to be counting bytes the naive way
2-
--> $DIR/bytecount.rs:5:13
2+
--> $DIR/bytecount.rs:7:13
33
|
44
LL | let _ = x.iter().filter(|&&a| a == 0).count(); // naive byte count
55
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using the bytecount crate: `bytecount::count(x, 0)`
66
|
77
note: the lint level is defined here
8-
--> $DIR/bytecount.rs:1:8
8+
--> $DIR/bytecount.rs:3:8
99
|
1010
LL | #[deny(clippy::naive_bytecount)]
1111
| ^^^^^^^^^^^^^^^^^^^^^^^
1212

1313
error: you appear to be counting bytes the naive way
14-
--> $DIR/bytecount.rs:7:13
14+
--> $DIR/bytecount.rs:9:13
1515
|
1616
LL | let _ = (&x[..]).iter().filter(|&a| *a == 0).count(); // naive byte count
1717
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using the bytecount crate: `bytecount::count((&x[..]), 0)`
1818

1919
error: you appear to be counting bytes the naive way
20-
--> $DIR/bytecount.rs:19:13
20+
--> $DIR/bytecount.rs:21:13
2121
|
2222
LL | let _ = x.iter().filter(|a| b + 1 == **a).count(); // naive byte count
2323
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using the bytecount crate: `bytecount::count(x, b + 1)`

tests/ui/clone_on_copy.fixed

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
clippy::no_effect,
88
clippy::unnecessary_operation,
99
clippy::vec_init_then_push,
10-
clippy::toplevel_ref_arg
10+
clippy::toplevel_ref_arg,
11+
clippy::needless_borrow
1112
)]
1213

1314
use std::cell::RefCell;

0 commit comments

Comments
 (0)