Skip to content

Commit 8f84628

Browse files
committed
Improve needless_borrow
Use the traversal done by the linting framework instead of a visitor to find binding usages. Only lint once when a binding appears multiple times in an `or` pattern. Don't suggest adding a reference before accessing a field.
1 parent d6ab6f5 commit 8f84628

File tree

6 files changed

+213
-206
lines changed

6 files changed

+213
-206
lines changed

clippy_lints/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1088,7 +1088,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
10881088
store.register_late_pass(|| box zero_div_zero::ZeroDiv);
10891089
store.register_late_pass(|| box mutex_atomic::Mutex);
10901090
store.register_late_pass(|| box needless_update::NeedlessUpdate);
1091-
store.register_late_pass(|| box needless_borrow::NeedlessBorrow);
1091+
store.register_late_pass(|| box needless_borrow::NeedlessBorrow::default());
10921092
store.register_late_pass(|| box needless_borrowed_ref::NeedlessBorrowedRef);
10931093
store.register_late_pass(|| box no_effect::NoEffect);
10941094
store.register_late_pass(|| box temporary_assignment::TemporaryAssignment);

clippy_lints/src/needless_borrow.rs

+171-56
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,21 @@
44
55
use clippy_utils::diagnostics::span_lint_and_then;
66
use clippy_utils::source::{snippet_opt, snippet_with_applicability, snippet_with_context};
7-
use clippy_utils::visitors::foreach_local_usage;
8-
use clippy_utils::{get_parent_expr, get_pat_usable_location};
7+
use clippy_utils::{get_parent_expr, path_to_local};
98
use if_chain::if_chain;
9+
use rustc_ast::util::parser::PREC_POSTFIX;
10+
use rustc_data_structures::fx::FxIndexMap;
1011
use rustc_errors::Applicability;
11-
use rustc_hir::{BindingAnnotation, BorrowKind, Expr, ExprKind, Mutability, Pat, PatKind, UnOp};
12-
use rustc_lint::{LateContext, LateLintPass, LintContext};
12+
use rustc_hir::{
13+
BindingAnnotation, Block, Body, BodyId, BorrowKind, Expr, ExprKind, HirId, ImplItem, Item, Mutability, Node, Pat,
14+
PatKind, TraitItem, UnOp,
15+
};
16+
use rustc_lint::{LateContext, LateLintPass, Lint, LintContext};
1317
use rustc_middle::lint::in_external_macro;
1418
use rustc_middle::ty;
1519
use rustc_middle::ty::adjustment::{Adjust, Adjustment};
16-
use rustc_session::{declare_lint_pass, declare_tool_lint};
20+
use rustc_session::{declare_tool_lint, impl_lint_pass};
21+
use rustc_span::{Span, SyntaxContext};
1722

1823
declare_clippy_lint! {
1924
/// **What it does:** Checks for address of operations (`&`) that are going to
@@ -63,10 +68,36 @@ declare_clippy_lint! {
6368
"`ref` binding to a reference"
6469
}
6570

66-
declare_lint_pass!(NeedlessBorrow => [NEEDLESS_BORROW, REF_BINDING_TO_REFERENCE]);
71+
impl_lint_pass!(NeedlessBorrow => [NEEDLESS_BORROW, REF_BINDING_TO_REFERENCE]);
72+
#[derive(Default)]
73+
pub struct NeedlessBorrow {
74+
/// The body the first local was found in. Used to emit lints when the traversal of the body has
75+
/// been finished. Note we can't lint at the end of every body as they can be nested within each
76+
/// other.
77+
current_body: Option<BodyId>,
78+
/// The list of locals currently being checked by the lint.
79+
ref_locals: FxIndexMap<HirId, Option<RefPat>>,
80+
}
81+
82+
struct RefPat {
83+
/// The lint to use for this suggestion. Either `needless_borrow` or `ref_binding_to_reference`.
84+
lint: &'static Lint,
85+
/// The spans of all the ref bindings for this local.
86+
spans: Vec<Span>,
87+
/// The context replacements are to be made in.
88+
ctxt: SyntaxContext,
89+
/// The applicability of this suggestion.
90+
app: Applicability,
91+
/// All the replacements which need to be made.
92+
replacements: Vec<(Span, String)>,
93+
}
6794

6895
impl<'tcx> LateLintPass<'tcx> for NeedlessBorrow {
6996
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
97+
if let Some(local) = path_to_local(e) {
98+
self.check_local_usage(cx, e, local);
99+
}
100+
70101
if e.span.from_expansion() {
71102
return;
72103
}
@@ -107,70 +138,154 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBorrow {
107138
}
108139
}
109140
}
141+
110142
fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) {
111-
if_chain! {
112-
if let PatKind::Binding(BindingAnnotation::Ref, id, name, _) = pat.kind;
113-
if !in_external_macro(cx.sess(), pat.span);
114-
if let ty::Ref(_, tam, _) = *cx.typeck_results().pat_ty(pat).kind();
115-
// only lint immutable refs, because borrowed `&mut T` cannot be moved out
116-
if let ty::Ref(_, _, Mutability::Not) = *tam.kind();
117-
then {
118-
let (span, search_loc) = match get_pat_usable_location(cx.tcx, pat) {
119-
Some(x) => x,
120-
None => return,
121-
};
122-
let ctxt = span.ctxt();
123-
if pat.span.ctxt() != ctxt {
124-
// The context of the pattern is different than the context using the binding.
125-
// Changing the pattern might affect other code which needs the ref binding.
126-
return;
143+
if let PatKind::Binding(BindingAnnotation::Ref, id, name, _) = pat.kind {
144+
if let Some(opt_prev_pat) = self.ref_locals.get_mut(&id) {
145+
// This binding id has been seen before. Add this pattern to the list of changes.
146+
if let Some(prev_pat) = opt_prev_pat {
147+
if prev_pat.ctxt == pat.span.ctxt() {
148+
prev_pat.spans.push(pat.span);
149+
prev_pat.replacements.push((
150+
pat.span,
151+
snippet_with_context(cx, name.span, prev_pat.ctxt, "..", &mut prev_pat.app)
152+
.0
153+
.into(),
154+
));
155+
} else {
156+
// Doesn't match the context of the previous pattern. Can't lint here.
157+
*opt_prev_pat = None;
158+
}
127159
}
160+
return;
161+
}
128162

129-
let mut app = Applicability::MachineApplicable;
130-
let mut can_fix = true;
131-
let mut lint = NEEDLESS_BORROW;
132-
let mut changes = vec![
133-
(pat.span, snippet_with_context(cx, name.span, ctxt, "..", &mut app).0.into_owned()),
134-
];
135-
136-
foreach_local_usage(cx, id, search_loc, |e| {
137-
// Check for auto-deref
138-
if !matches!(
139-
cx.typeck_results().expr_adjustments(e),
140-
[Adjustment { kind: Adjust::Deref(_), .. }, Adjustment { kind: Adjust::Deref(_), .. }, ..]
141-
) {
142-
match get_parent_expr(cx, e) {
143-
Some(&Expr { span, kind: ExprKind::Unary(UnOp::Deref, _), .. })
144-
if span.ctxt() == ctxt => {
145-
// Remove explicit deref.
146-
let snip = snippet_with_context(cx, e.span, ctxt, "..", &mut app).0;
147-
changes.push((span, snip.into()));
148-
}
149-
_ if e.span.ctxt() == ctxt => {
150-
// Double reference might be needed at this point.
151-
lint = REF_BINDING_TO_REFERENCE;
152-
let snip = snippet_with_applicability(cx, e.span, "..", &mut app);
153-
changes.push((e.span, format!("&{}", snip)));
163+
if_chain! {
164+
if !in_external_macro(cx.sess(), pat.span);
165+
if let ty::Ref(_, tam, _) = *cx.typeck_results().pat_ty(pat).kind();
166+
// only lint immutable refs, because borrowed `&mut T` cannot be moved out
167+
if let ty::Ref(_, _, Mutability::Not) = *tam.kind();
168+
then {
169+
let map = cx.tcx.hir();
170+
let mut iter = map.parent_iter(pat.hir_id);
171+
let expr_ctxt = loop {
172+
// Find the context shared by the pattern, and it's scope use site.
173+
match iter.next() {
174+
None => {
175+
// This shouldn't happen, but mark this as unfixable if it does.
176+
self.ref_locals.insert(id, None);
177+
return;
154178
}
155-
_ => can_fix = false,
179+
Some((_, Node::ImplItem(ImplItem { span, .. })
180+
| Node::TraitItem(TraitItem { span, .. })
181+
| Node::Item(Item { span, .. })
182+
| Node::Block( Block { span, ..})
183+
| Node::Expr( Expr { span, .. }))) => break span.ctxt(),
184+
_ => (),
156185
}
157-
}
158-
});
186+
};
159187

160-
if !can_fix {
161-
return;
188+
if pat.span.ctxt() == expr_ctxt {
189+
let mut app = Applicability::MachineApplicable;
190+
let snip = snippet_with_context(cx, name.span, expr_ctxt, "..", &mut app).0;
191+
self.current_body = self.current_body.or(cx.enclosing_body);
192+
self.ref_locals.insert(
193+
id,
194+
Some(RefPat {
195+
lint: NEEDLESS_BORROW,
196+
spans: vec![pat.span],
197+
ctxt: expr_ctxt,
198+
app,
199+
replacements: vec![(pat.span, snip.into())],
200+
}),
201+
);
202+
} else {
203+
// The context of the pattern is different than the context using the binding.
204+
// Changing the pattern might affect other code which needs the ref binding.
205+
// Mark the local as having been seen, but not fixable.
206+
self.ref_locals.insert(id, None);
207+
}
162208
}
209+
}
210+
}
211+
}
163212

213+
fn check_body_post(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) {
214+
if Some(body.id()) == self.current_body {
215+
for pat in self.ref_locals.drain(..).filter_map(|(_, x)| x) {
216+
let replacements = pat.replacements;
217+
let app = pat.app;
164218
span_lint_and_then(
165219
cx,
166-
lint,
167-
pat.span,
220+
pat.lint,
221+
pat.spans,
168222
"this pattern creates a reference to a reference",
169223
|diag| {
170-
diag.multipart_suggestion("try this", changes, app);
171-
}
224+
diag.multipart_suggestion("try this", replacements, app);
225+
},
172226
)
173227
}
228+
self.current_body = None;
229+
}
230+
}
231+
}
232+
impl NeedlessBorrow {
233+
fn check_local_usage(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, local: HirId) {
234+
if let Some(outer_pat) = self.ref_locals.get_mut(&local) {
235+
if let Some(pat) = outer_pat {
236+
// Check for auto-deref
237+
if !matches!(
238+
cx.typeck_results().expr_adjustments(e),
239+
[
240+
Adjustment {
241+
kind: Adjust::Deref(_),
242+
..
243+
},
244+
Adjustment {
245+
kind: Adjust::Deref(_),
246+
..
247+
},
248+
..
249+
]
250+
) {
251+
match get_parent_expr(cx, e) {
252+
// Field accesses are the same no matter the number of references.
253+
Some(Expr {
254+
kind: ExprKind::Field(..),
255+
..
256+
}) => (),
257+
Some(&Expr {
258+
span,
259+
kind: ExprKind::Unary(UnOp::Deref, _),
260+
..
261+
}) if span.ctxt() == pat.ctxt => {
262+
// Remove explicit deref.
263+
let snip = snippet_with_context(cx, e.span, pat.ctxt, "..", &mut pat.app).0;
264+
pat.replacements.push((span, snip.into()));
265+
},
266+
Some(parent) if parent.span.ctxt() == pat.ctxt => {
267+
// Double reference might be needed at this point.
268+
if parent.precedence().order() == PREC_POSTFIX {
269+
// Parenthesis would be needed here, don't lint.
270+
*outer_pat = None;
271+
} else {
272+
let snip = snippet_with_context(cx, e.span, pat.ctxt, "..", &mut pat.app).0;
273+
pat.replacements.push((e.span, format!("&{}", snip)));
274+
}
275+
},
276+
_ if e.span.ctxt() == pat.ctxt => {
277+
// Double reference might be needed at this point.
278+
pat.lint = REF_BINDING_TO_REFERENCE;
279+
let snip = snippet_with_applicability(cx, e.span, "..", &mut pat.app);
280+
pat.replacements.push((e.span, format!("&{}", snip)));
281+
},
282+
// Edge case for macros. The span of the identifier will usually match the context of the
283+
// binding, but not if the identifier was created in a macro. e.g. `concat_idents` and proc
284+
// macros
285+
_ => *outer_pat = None,
286+
}
287+
}
288+
}
174289
}
175290
}
176291
}

clippy_utils/src/lib.rs

+3-74
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,9 @@ use rustc_hir::def_id::{DefId, LOCAL_CRATE};
6363
use rustc_hir::intravisit::{self, walk_expr, ErasedMap, NestedVisitorMap, Visitor};
6464
use rustc_hir::LangItem::{ResultErr, ResultOk};
6565
use rustc_hir::{
66-
def, Arm, BindingAnnotation, Block, Body, Constness, Destination, Expr, ExprKind, FnDecl, GenericArgs, Guard,
67-
HirId, Impl, ImplItem, ImplItemKind, Item, ItemKind, LangItem, Local, MatchSource, Node, Param, Pat, PatKind, Path,
68-
PathSegment, QPath, Stmt, StmtKind, TraitFn, TraitItem, TraitItemKind, TraitRef, TyKind,
66+
def, Arm, BindingAnnotation, Block, Body, Constness, Destination, Expr, ExprKind, FnDecl, GenericArgs, HirId, Impl,
67+
ImplItem, ImplItemKind, Item, ItemKind, LangItem, Local, MatchSource, Node, Param, Pat, PatKind, Path, PathSegment,
68+
QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitRef, TyKind,
6969
};
7070
use rustc_lint::{LateContext, Level, Lint, LintContext};
7171
use rustc_middle::hir::exports::Export;
@@ -1469,77 +1469,6 @@ pub fn run_lints(cx: &LateContext<'_>, lints: &[&'static Lint], id: HirId) -> bo
14691469
})
14701470
}
14711471

1472-
pub enum PatUsableLocation<'tcx> {
1473-
Decl,
1474-
Body(&'tcx Expr<'tcx>),
1475-
Local(&'tcx [Stmt<'tcx>], Option<&'tcx Expr<'tcx>>),
1476-
Arm(Option<&'tcx Expr<'tcx>>, &'tcx Expr<'tcx>),
1477-
}
1478-
1479-
/// Gets all the expressions a pattern binding is visible to, and the span encompassing all the
1480-
/// expression as well as the pattern. Note name shadowing is not considered here.
1481-
pub fn get_pat_usable_location(tcx: TyCtxt<'tcx>, pat: &Pat<'_>) -> Option<(Span, PatUsableLocation<'tcx>)> {
1482-
let map = tcx.hir();
1483-
let mut parents = map.parent_iter(pat.hir_id);
1484-
loop {
1485-
match parents.next() {
1486-
Some((_, Node::Pat(_) | Node::Local(_) | Node::Param(_))) => (),
1487-
Some((_, Node::Arm(arm))) => {
1488-
break Some((
1489-
arm.span,
1490-
PatUsableLocation::Arm(
1491-
arm.guard.as_ref().map(|&(Guard::If(e) | Guard::IfLet(_, e))| e),
1492-
arm.body,
1493-
),
1494-
));
1495-
},
1496-
Some((id, Node::Stmt(_))) => {
1497-
if let Some((_, Node::Block(block))) = parents.next() {
1498-
let mut stmts = block.stmts.iter();
1499-
stmts.find(|s| s.hir_id == id);
1500-
break Some((block.span, PatUsableLocation::Local(stmts.as_slice(), block.expr)));
1501-
}
1502-
// Should be impossible. Statements can only have blocks as their parent.
1503-
break None;
1504-
},
1505-
Some((
1506-
_,
1507-
Node::Item(&Item {
1508-
span,
1509-
kind: ItemKind::Fn(.., body),
1510-
..
1511-
})
1512-
| Node::ImplItem(&ImplItem {
1513-
span,
1514-
kind: ImplItemKind::Fn(_, body),
1515-
..
1516-
})
1517-
| Node::Expr(&Expr {
1518-
span,
1519-
kind: ExprKind::Closure(_, _, body, _, _),
1520-
..
1521-
}),
1522-
)) => {
1523-
break Some((span, PatUsableLocation::Body(&map.body(body).value)));
1524-
},
1525-
Some((
1526-
_,
1527-
Node::TraitItem(&TraitItem {
1528-
span,
1529-
kind: TraitItemKind::Fn(_, ref kind),
1530-
..
1531-
}),
1532-
)) => {
1533-
break match *kind {
1534-
TraitFn::Required(_) => Some((span, PatUsableLocation::Decl)),
1535-
TraitFn::Provided(body) => Some((span, PatUsableLocation::Body(&map.body(body).value))),
1536-
};
1537-
},
1538-
_ => break None,
1539-
}
1540-
}
1541-
}
1542-
15431472
/// Returns Option<String> where String is a textual representation of the type encapsulated in the
15441473
/// slice iff the given expression is a slice of primitives (as defined in the
15451474
/// `is_recursively_primitive_type` function) and None otherwise.

0 commit comments

Comments
 (0)