Skip to content

Commit 1fbc3fb

Browse files
committed
Improve manual_map and map_entry
Locals which can be partially moved created within the to-be-created closure shouldn't block the use of a closure
1 parent f6a5889 commit 1fbc3fb

File tree

11 files changed

+181
-37
lines changed

11 files changed

+181
-37
lines changed

clippy_lints/src/entry.rs

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ use clippy_utils::{
77
};
88
use rustc_errors::Applicability;
99
use rustc_hir::{
10+
hir_id::HirIdSet,
1011
intravisit::{walk_expr, ErasedMap, NestedVisitorMap, Visitor},
11-
Block, Expr, ExprKind, Guard, HirId, Local, Stmt, StmtKind, UnOp,
12+
Block, Expr, ExprKind, Guard, HirId, Pat, Stmt, StmtKind, UnOp,
1213
};
1314
use rustc_lint::{LateContext, LateLintPass};
1415
use rustc_session::{declare_lint_pass, declare_tool_lint};
@@ -336,6 +337,8 @@ struct InsertSearcher<'cx, 'tcx> {
336337
edits: Vec<Edit<'tcx>>,
337338
/// A stack of loops the visitor is currently in.
338339
loops: Vec<HirId>,
340+
/// Local variables created in the expression. These don't need to be captured.
341+
locals: HirIdSet,
339342
}
340343
impl<'tcx> InsertSearcher<'_, 'tcx> {
341344
/// Visit the expression as a branch in control flow. Multiple insert calls can be used, but
@@ -383,13 +386,16 @@ impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, 'tcx> {
383386
}
384387
},
385388
StmtKind::Expr(e) => self.visit_expr(e),
386-
StmtKind::Local(Local { init: Some(e), .. }) => {
387-
self.allow_insert_closure &= !self.in_tail_pos;
388-
self.in_tail_pos = false;
389-
self.is_single_insert = false;
390-
self.visit_expr(e);
389+
StmtKind::Local(l) => {
390+
self.visit_pat(l.pat);
391+
if let Some(e) = l.init {
392+
self.allow_insert_closure &= !self.in_tail_pos;
393+
self.in_tail_pos = false;
394+
self.is_single_insert = false;
395+
self.visit_expr(e);
396+
}
391397
},
392-
_ => {
398+
StmtKind::Item(_) => {
393399
self.allow_insert_closure &= !self.in_tail_pos;
394400
self.is_single_insert = false;
395401
},
@@ -471,6 +477,7 @@ impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, 'tcx> {
471477
// Each branch may contain it's own insert expression.
472478
let mut is_map_used = self.is_map_used;
473479
for arm in arms {
480+
self.visit_pat(arm.pat);
474481
if let Some(Guard::If(guard) | Guard::IfLet(_, guard)) = arm.guard {
475482
self.visit_non_tail_expr(guard);
476483
}
@@ -496,7 +503,8 @@ impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, 'tcx> {
496503
},
497504
_ => {
498505
self.allow_insert_closure &= !self.in_tail_pos;
499-
self.allow_insert_closure &= can_move_expr_to_closure_no_visit(self.cx, expr, &self.loops);
506+
self.allow_insert_closure &=
507+
can_move_expr_to_closure_no_visit(self.cx, expr, &self.loops, &self.locals);
500508
// Sub expressions are no longer in the tail position.
501509
self.is_single_insert = false;
502510
self.in_tail_pos = false;
@@ -505,6 +513,12 @@ impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, 'tcx> {
505513
},
506514
}
507515
}
516+
517+
fn visit_pat(&mut self, p: &'tcx Pat<'tcx>) {
518+
p.each_binding_or_first(&mut |_, id, _, _| {
519+
self.locals.insert(id);
520+
});
521+
}
508522
}
509523

510524
struct InsertSearchResults<'tcx> {
@@ -630,6 +644,7 @@ fn find_insert_calls(
630644
in_tail_pos: true,
631645
is_single_insert: true,
632646
loops: Vec::new(),
647+
locals: HirIdSet::default(),
633648
};
634649
s.visit_expr(expr);
635650
let allow_insert_closure = s.allow_insert_closure;

clippy_utils/src/lib.rs

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ use rustc_data_structures::unhash::UnhashMap;
6767
use rustc_hir as hir;
6868
use rustc_hir::def::{DefKind, Res};
6969
use rustc_hir::def_id::DefId;
70+
use rustc_hir::hir_id::HirIdSet;
7071
use rustc_hir::intravisit::{self, walk_expr, ErasedMap, FnKind, NestedVisitorMap, Visitor};
7172
use rustc_hir::LangItem::{ResultErr, ResultOk};
7273
use rustc_hir::{
@@ -559,7 +560,12 @@ pub fn trait_ref_of_method<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Optio
559560
}
560561

561562
/// Checks if the top level expression can be moved into a closure as is.
562-
pub fn can_move_expr_to_closure_no_visit(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, jump_targets: &[HirId]) -> bool {
563+
pub fn can_move_expr_to_closure_no_visit(
564+
cx: &LateContext<'tcx>,
565+
expr: &'tcx Expr<'_>,
566+
jump_targets: &[HirId],
567+
ignore_locals: &HirIdSet,
568+
) -> bool {
563569
match expr.kind {
564570
ExprKind::Break(Destination { target_id: Ok(id), .. }, _)
565571
| ExprKind::Continue(Destination { target_id: Ok(id), .. })
@@ -575,15 +581,24 @@ pub fn can_move_expr_to_closure_no_visit(cx: &LateContext<'tcx>, expr: &'tcx Exp
575581
| ExprKind::LlvmInlineAsm(_) => false,
576582
// Accessing a field of a local value can only be done if the type isn't
577583
// partially moved.
578-
ExprKind::Field(base_expr, _)
579-
if matches!(
580-
base_expr.kind,
581-
ExprKind::Path(QPath::Resolved(_, Path { res: Res::Local(_), .. }))
582-
) && can_partially_move_ty(cx, cx.typeck_results().expr_ty(base_expr)) =>
583-
{
584+
ExprKind::Field(
585+
&Expr {
586+
hir_id,
587+
kind:
588+
ExprKind::Path(QPath::Resolved(
589+
_,
590+
Path {
591+
res: Res::Local(local_id),
592+
..
593+
},
594+
)),
595+
..
596+
},
597+
_,
598+
) if !ignore_locals.contains(local_id) && can_partially_move_ty(cx, cx.typeck_results().node_type(hir_id)) => {
584599
// TODO: check if the local has been partially moved. Assume it has for now.
585600
false
586-
}
601+
},
587602
_ => true,
588603
}
589604
}
@@ -592,7 +607,11 @@ pub fn can_move_expr_to_closure_no_visit(cx: &LateContext<'tcx>, expr: &'tcx Exp
592607
pub fn can_move_expr_to_closure(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
593608
struct V<'cx, 'tcx> {
594609
cx: &'cx LateContext<'tcx>,
610+
// Stack of potential break targets contained in the expression.
595611
loops: Vec<HirId>,
612+
/// Local variables created in the expression. These don't need to be captured.
613+
locals: HirIdSet,
614+
/// Whether this expression can be turned into a closure.
596615
allow_closure: bool,
597616
}
598617
impl Visitor<'tcx> for V<'_, 'tcx> {
@@ -610,16 +629,23 @@ pub fn can_move_expr_to_closure(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) ->
610629
self.visit_block(b);
611630
self.loops.pop();
612631
} else {
613-
self.allow_closure &= can_move_expr_to_closure_no_visit(self.cx, e, &self.loops);
632+
self.allow_closure &= can_move_expr_to_closure_no_visit(self.cx, e, &self.loops, &self.locals);
614633
walk_expr(self, e);
615634
}
616635
}
636+
637+
fn visit_pat(&mut self, p: &'tcx Pat<'tcx>) {
638+
p.each_binding_or_first(&mut |_, id, _, _| {
639+
self.locals.insert(id);
640+
});
641+
}
617642
}
618643

619644
let mut v = V {
620645
cx,
621646
allow_closure: true,
622647
loops: Vec::new(),
648+
locals: HirIdSet::default(),
623649
};
624650
v.visit_expr(expr);
625651
v.allow_closure

tests/ui/entry.fixed

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#![warn(clippy::map_entry)]
55
#![feature(asm)]
66

7-
use std::collections::{BTreeMap, HashMap};
7+
use std::collections::HashMap;
88
use std::hash::Hash;
99

1010
macro_rules! m {
@@ -142,14 +142,13 @@ fn hash_map<K: Eq + Hash + Copy, V: Copy>(m: &mut HashMap<K, V>, m2: &mut HashMa
142142
if !m.contains_key(&k) {
143143
insert!(m, k, v);
144144
}
145-
}
146145

147-
fn btree_map<K: Eq + Ord + Copy, V: Copy>(m: &mut BTreeMap<K, V>, k: K, v: V, v2: V) {
148-
// insert then do something, use if let
149-
if let std::collections::btree_map::Entry::Vacant(e) = m.entry(k) {
150-
e.insert(v);
151-
foo();
152-
}
146+
// or_insert_with. Partial move of a local declared in the closure is ok.
147+
m.entry(k).or_insert_with(|| {
148+
let x = (String::new(), String::new());
149+
let _ = x.0;
150+
v
151+
});
153152
}
154153

155154
fn main() {}

tests/ui/entry.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#![warn(clippy::map_entry)]
55
#![feature(asm)]
66

7-
use std::collections::{BTreeMap, HashMap};
7+
use std::collections::HashMap;
88
use std::hash::Hash;
99

1010
macro_rules! m {
@@ -146,13 +146,12 @@ fn hash_map<K: Eq + Hash + Copy, V: Copy>(m: &mut HashMap<K, V>, m2: &mut HashMa
146146
if !m.contains_key(&k) {
147147
insert!(m, k, v);
148148
}
149-
}
150149

151-
fn btree_map<K: Eq + Ord + Copy, V: Copy>(m: &mut BTreeMap<K, V>, k: K, v: V, v2: V) {
152-
// insert then do something, use if let
150+
// or_insert_with. Partial move of a local declared in the closure is ok.
153151
if !m.contains_key(&k) {
152+
let x = (String::new(), String::new());
153+
let _ = x.0;
154154
m.insert(k, v);
155-
foo();
156155
}
157156
}
158157

tests/ui/entry.stderr

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -165,21 +165,23 @@ LL | | m.insert(m!(k), m!(v));
165165
LL | | }
166166
| |_____^ help: try this: `m.entry(m!(k)).or_insert_with(|| m!(v));`
167167

168-
error: usage of `contains_key` followed by `insert` on a `BTreeMap`
169-
--> $DIR/entry.rs:153:5
168+
error: usage of `contains_key` followed by `insert` on a `HashMap`
169+
--> $DIR/entry.rs:151:5
170170
|
171171
LL | / if !m.contains_key(&k) {
172+
LL | | let x = (String::new(), String::new());
173+
LL | | let _ = x.0;
172174
LL | | m.insert(k, v);
173-
LL | | foo();
174175
LL | | }
175176
| |_____^
176177
|
177178
help: try this
178179
|
179-
LL | if let std::collections::btree_map::Entry::Vacant(e) = m.entry(k) {
180-
LL | e.insert(v);
181-
LL | foo();
182-
LL | }
180+
LL | m.entry(k).or_insert_with(|| {
181+
LL | let x = (String::new(), String::new());
182+
LL | let _ = x.0;
183+
LL | v
184+
LL | });
183185
|
184186

185187
error: aborting due to 10 previous errors

tests/ui/entry_btree.fixed

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// run-rustfix
2+
3+
#![warn(clippy::map_entry)]
4+
#![allow(dead_code)]
5+
6+
use std::collections::BTreeMap;
7+
8+
fn foo() {}
9+
10+
fn btree_map<K: Eq + Ord + Copy, V: Copy>(m: &mut BTreeMap<K, V>, k: K, v: V) {
11+
// insert then do something, use if let
12+
if let std::collections::btree_map::Entry::Vacant(e) = m.entry(k) {
13+
e.insert(v);
14+
foo();
15+
}
16+
}
17+
18+
fn main() {}

tests/ui/entry_btree.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// run-rustfix
2+
3+
#![warn(clippy::map_entry)]
4+
#![allow(dead_code)]
5+
6+
use std::collections::BTreeMap;
7+
8+
fn foo() {}
9+
10+
fn btree_map<K: Eq + Ord + Copy, V: Copy>(m: &mut BTreeMap<K, V>, k: K, v: V) {
11+
// insert then do something, use if let
12+
if !m.contains_key(&k) {
13+
m.insert(k, v);
14+
foo();
15+
}
16+
}
17+
18+
fn main() {}

tests/ui/entry_btree.stderr

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error: usage of `contains_key` followed by `insert` on a `BTreeMap`
2+
--> $DIR/entry_btree.rs:12:5
3+
|
4+
LL | / if !m.contains_key(&k) {
5+
LL | | m.insert(k, v);
6+
LL | | foo();
7+
LL | | }
8+
| |_____^
9+
|
10+
= note: `-D clippy::map-entry` implied by `-D warnings`
11+
help: try this
12+
|
13+
LL | if let std::collections::btree_map::Entry::Vacant(e) = m.entry(k) {
14+
LL | e.insert(v);
15+
LL | foo();
16+
LL | }
17+
|
18+
19+
error: aborting due to previous error
20+

tests/ui/manual_map_option_2.fixed

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// run-rustfix
2+
3+
#![warn(clippy::manual_map)]
4+
5+
fn main() {
6+
let _ = Some(0).map(|x| {
7+
let y = (String::new(), String::new());
8+
(x, y.0)
9+
});
10+
}

tests/ui/manual_map_option_2.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// run-rustfix
2+
3+
#![warn(clippy::manual_map)]
4+
5+
fn main() {
6+
let _ = match Some(0) {
7+
Some(x) => Some({
8+
let y = (String::new(), String::new());
9+
(x, y.0)
10+
}),
11+
None => None,
12+
};
13+
}

0 commit comments

Comments
 (0)