Skip to content

Commit 5b3c00f

Browse files
committed
Successfully generalize prevention of suggestions causing multiple mutable borrows.
Also add some more tests to check that it's working.
1 parent 3e20e30 commit 5b3c00f

File tree

2 files changed

+84
-19
lines changed

2 files changed

+84
-19
lines changed

clippy_lints/src/loops/needless_collect.rs

+64-18
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
33
use clippy_utils::source::{snippet, snippet_with_applicability};
44
use clippy_utils::sugg::Sugg;
55
use clippy_utils::ty::is_type_diagnostic_item;
6-
use clippy_utils::{can_move_expr_to_closure, is_trait_method, path_to_local_id, CaptureKind};
6+
use clippy_utils::{can_move_expr_to_closure, is_trait_method, path_to_local, path_to_local_id, CaptureKind};
77
use if_chain::if_chain;
8+
use rustc_data_structures::fx::FxHashMap;
89
use rustc_errors::Applicability;
910
use rustc_hir::intravisit::{walk_block, walk_expr, NestedVisitorMap, Visitor};
1011
use rustc_hir::{Block, Expr, ExprKind, HirId, HirIdSet, Local, Mutability, Node, PatKind, Stmt, StmtKind};
@@ -174,15 +175,23 @@ struct IterFunctionVisitor<'b, 'a> {
174175
illegal_mutable_capture_ids: HirIdSet,
175176
current_mutably_captured_ids: HirIdSet,
176177
cx: &'a LateContext<'b>,
177-
uses: Vec<IterFunction>,
178+
uses: Vec<Option<IterFunction>>,
179+
hir_id_uses_map: FxHashMap<HirId, usize>,
180+
current_statement_hir_id: Option<HirId>,
178181
seen_other: bool,
179182
target: HirId,
180183
}
181184
impl<'tcx> Visitor<'tcx> for IterFunctionVisitor<'_, 'tcx> {
182185
fn visit_block(&mut self, block: &'txc Block<'tcx>) {
183-
for elem in block.stmts.iter().filter_map(get_expr_from_stmt).chain(block.expr) {
184-
self.current_mutably_captured_ids = HirIdSet::default();
185-
self.visit_expr(elem);
186+
for (expr, hir_id) in block
187+
.stmts
188+
.iter()
189+
.filter_map(get_expr_and_hir_id_from_stmt)
190+
.chain(block.expr.map(|expr| (expr, None)))
191+
{
192+
self.current_statement_hir_id = hir_id;
193+
self.current_mutably_captured_ids = get_captured_ids(self.cx, self.cx.typeck_results().expr_ty(expr));
194+
self.visit_expr(expr);
186195
}
187196
}
188197

@@ -202,28 +211,53 @@ impl<'tcx> Visitor<'tcx> for IterFunctionVisitor<'_, 'tcx> {
202211
.next()
203212
.is_none()
204213
{
214+
if let Some(hir_id) = self.current_statement_hir_id {
215+
self.hir_id_uses_map.insert(hir_id, self.uses.len());
216+
}
205217
match &*method_name.ident.name.as_str() {
206-
"into_iter" => self.uses.push(IterFunction {
218+
"into_iter" => self.uses.push(Some(IterFunction {
207219
func: IterFunctionKind::IntoIter,
208220
span: expr.span,
209-
}),
210-
"len" => self.uses.push(IterFunction {
221+
})),
222+
"len" => self.uses.push(Some(IterFunction {
211223
func: IterFunctionKind::Len,
212224
span: expr.span,
213-
}),
214-
"is_empty" => self.uses.push(IterFunction {
225+
})),
226+
"is_empty" => self.uses.push(Some(IterFunction {
215227
func: IterFunctionKind::IsEmpty,
216228
span: expr.span,
217-
}),
218-
"contains" => self.uses.push(IterFunction {
229+
})),
230+
"contains" => self.uses.push(Some(IterFunction {
219231
func: IterFunctionKind::Contains(args[0].span),
220232
span: expr.span,
221-
}),
222-
_ => self.seen_other = true,
233+
})),
234+
_ => {
235+
self.seen_other = true;
236+
if let Some(hir_id) = self.current_statement_hir_id {
237+
self.hir_id_uses_map.remove(&hir_id);
238+
}
239+
},
223240
}
224241
}
225242
return;
226243
}
244+
245+
if let Some(hir_id) = path_to_local(recv) {
246+
if let Some(index) = self.hir_id_uses_map.remove(&hir_id) {
247+
if self
248+
.illegal_mutable_capture_ids
249+
.intersection(&self.current_mutably_captured_ids)
250+
.next()
251+
.is_none()
252+
{
253+
if let Some(hir_id) = self.current_statement_hir_id {
254+
self.hir_id_uses_map.insert(hir_id, index);
255+
}
256+
} else {
257+
self.uses[index] = None;
258+
}
259+
}
260+
}
227261
}
228262
// Check if the collection is used for anything else
229263
if path_to_local_id(expr, self.target) {
@@ -239,11 +273,17 @@ impl<'tcx> Visitor<'tcx> for IterFunctionVisitor<'_, 'tcx> {
239273
}
240274
}
241275

242-
fn get_expr_from_stmt<'v>(stmt: &'v Stmt<'v>) -> Option<&'v Expr<'v>> {
276+
fn get_expr_and_hir_id_from_stmt<'v>(stmt: &'v Stmt<'v>) -> Option<(&'v Expr<'v>, Option<HirId>)> {
243277
match stmt.kind {
244-
StmtKind::Expr(expr) | StmtKind::Semi(expr) => Some(expr),
278+
StmtKind::Expr(expr) | StmtKind::Semi(expr) => Some((expr, None)),
245279
StmtKind::Item(..) => None,
246-
StmtKind::Local(Local { init, .. }) => *init,
280+
StmtKind::Local(Local { init, pat, .. }) => {
281+
if let PatKind::Binding(_, hir_id, ..) = pat.kind {
282+
init.map(|init_expr| (init_expr, Some(hir_id)))
283+
} else {
284+
init.map(|init_expr| (init_expr, None))
285+
}
286+
},
247287
}
248288
}
249289

@@ -284,9 +324,15 @@ fn detect_iter_and_into_iters<'tcx: 'a, 'a>(
284324
cx,
285325
current_mutably_captured_ids: HirIdSet::default(),
286326
illegal_mutable_capture_ids: captured_ids,
327+
hir_id_uses_map: FxHashMap::default(),
328+
current_statement_hir_id: None,
287329
};
288330
visitor.visit_block(block);
289-
if visitor.seen_other { None } else { Some(visitor.uses) }
331+
if visitor.seen_other {
332+
None
333+
} else {
334+
Some(visitor.uses.into_iter().flatten().collect())
335+
}
290336
}
291337

292338
#[allow(rustc::usage_of_ty_tykind)]

tests/ui/needless_collect_indirect.rs

+20-1
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,32 @@ mod issue7110 {
7979
mod issue7975 {
8080
use super::*;
8181

82-
fn shouldnt_lint() -> Vec<()> {
82+
fn direct_mapping_with_used_mutable_reference() -> Vec<()> {
8383
let test_vec: Vec<()> = vec![];
8484
let mut vec_2: Vec<()> = vec![];
8585
let mut_ref = &mut vec_2;
8686
let collected_vec: Vec<_> = test_vec.into_iter().map(|_| mut_ref.push(())).collect();
8787
collected_vec.into_iter().map(|_| mut_ref.push(())).collect()
8888
}
89+
90+
fn indirectly_mapping_with_used_mutable_reference() -> Vec<()> {
91+
let test_vec: Vec<()> = vec![];
92+
let mut vec_2: Vec<()> = vec![];
93+
let mut_ref = &mut vec_2;
94+
let collected_vec: Vec<_> = test_vec.into_iter().map(|_| mut_ref.push(())).collect();
95+
let iter = collected_vec.into_iter();
96+
iter.map(|_| mut_ref.push(())).collect()
97+
}
98+
99+
fn indirect_collect_after_indirect_mapping_with_used_mutable_reference() -> Vec<()> {
100+
let test_vec: Vec<()> = vec![];
101+
let mut vec_2: Vec<()> = vec![];
102+
let mut_ref = &mut vec_2;
103+
let collected_vec: Vec<_> = test_vec.into_iter().map(|_| mut_ref.push(())).collect();
104+
let iter = collected_vec.into_iter();
105+
let mapped_iter = iter.map(|_| mut_ref.push(()));
106+
mapped_iter.collect()
107+
}
89108
}
90109

91110
fn allow_test() {

0 commit comments

Comments
 (0)