Skip to content

Commit ce5e927

Browse files
committed
Improve map_entry lint
Fix false positives where the map is used before inserting into the map. Fix false positives where two insertions happen. Suggest using `if let Entry::Vacant(e) = _.entry(_)` when `or_insert` might be a semantic change
1 parent b1c675f commit ce5e927

File tree

12 files changed

+882
-280
lines changed

12 files changed

+882
-280
lines changed

clippy_lints/src/entry.rs

Lines changed: 368 additions & 121 deletions
Large diffs are not rendered by default.

clippy_utils/src/lib.rs

Lines changed: 108 additions & 17 deletions
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, NestedVisitorMap, Visitor};
6464
use rustc_hir::LangItem::{ResultErr, ResultOk};
6565
use rustc_hir::{
66-
def, Arm, BindingAnnotation, Block, Body, Constness, Expr, ExprKind, FnDecl, GenericArgs, HirId, Impl, ImplItem,
67-
ImplItemKind, Item, ItemKind, LangItem, MatchSource, Node, Param, Pat, PatKind, Path, PathSegment, QPath,
68-
TraitItem, TraitItemKind, TraitRef, TyKind,
66+
def, Arm, BindingAnnotation, Block, Body, Constness, Destination, Expr, ExprKind, FnDecl, GenericArgs, HirId, Impl,
67+
ImplItem, ImplItemKind, Item, ItemKind, LangItem, MatchSource, Node, Param, Pat, PatKind, Path, PathSegment, QPath,
68+
Stmt, StmtKind, TraitItem, TraitItemKind, TraitRef, TyKind,
6969
};
7070
use rustc_lint::{LateContext, Level, Lint, LintContext};
7171
use rustc_middle::hir::exports::Export;
@@ -1245,6 +1245,82 @@ pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
12451245
did.map_or(false, |did| must_use_attr(&cx.tcx.get_attrs(did)).is_some())
12461246
}
12471247

1248+
pub fn get_expr_use_node(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option<Node<'tcx>> {
1249+
let map = tcx.hir();
1250+
let mut child_id = expr.hir_id;
1251+
let mut iter = map.parent_iter(child_id);
1252+
loop {
1253+
match iter.next() {
1254+
None => break None,
1255+
Some((id, Node::Block(_))) => child_id = id,
1256+
Some((id, Node::Arm(arm))) if arm.body.hir_id == child_id => child_id = id,
1257+
Some((_, Node::Expr(expr))) => match expr.kind {
1258+
ExprKind::Break(
1259+
Destination {
1260+
target_id: Ok(dest), ..
1261+
},
1262+
_,
1263+
) => {
1264+
iter = map.parent_iter(dest);
1265+
child_id = dest;
1266+
},
1267+
ExprKind::DropTemps(_) | ExprKind::Block(..) => child_id = expr.hir_id,
1268+
ExprKind::If(control_expr, ..) | ExprKind::Match(control_expr, ..)
1269+
if control_expr.hir_id != child_id =>
1270+
{
1271+
child_id = expr.hir_id
1272+
},
1273+
_ => break Some(Node::Expr(expr)),
1274+
},
1275+
Some((_, node)) => break Some(node),
1276+
}
1277+
}
1278+
}
1279+
1280+
pub fn is_expr_used(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
1281+
!matches!(
1282+
get_expr_use_node(tcx, expr),
1283+
Some(Node::Stmt(Stmt {
1284+
kind: StmtKind::Expr(_) | StmtKind::Semi(_),
1285+
..
1286+
}))
1287+
)
1288+
}
1289+
1290+
pub fn get_expr_use_or_unification_node(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option<Node<'tcx>> {
1291+
let map = tcx.hir();
1292+
let mut child_id = expr.hir_id;
1293+
let mut iter = map.parent_iter(child_id);
1294+
loop {
1295+
match iter.next() {
1296+
None => break None,
1297+
Some((id, Node::Block(_))) => child_id = id,
1298+
Some((id, Node::Arm(arm))) if arm.body.hir_id == child_id => child_id = id,
1299+
Some((_, Node::Expr(expr))) => match expr.kind {
1300+
ExprKind::Match(_, [arm], _) if arm.hir_id == child_id => child_id = expr.hir_id,
1301+
ExprKind::Block(..) | ExprKind::DropTemps(_) => child_id = expr.hir_id,
1302+
ExprKind::If(_, then_expr, None) if then_expr.hir_id == child_id => break None,
1303+
_ => break Some(Node::Expr(expr)),
1304+
},
1305+
Some((_, node)) => break Some(node),
1306+
}
1307+
}
1308+
}
1309+
1310+
pub fn is_expr_used_or_unified(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
1311+
!matches!(
1312+
get_expr_use_or_unification_node(tcx, expr),
1313+
None | Some(Node::Stmt(Stmt {
1314+
kind: StmtKind::Expr(_) | StmtKind::Semi(_),
1315+
..
1316+
}))
1317+
)
1318+
}
1319+
1320+
pub fn is_expr_final_block_expr(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
1321+
matches!(get_parent_node(tcx, expr.hir_id), Some(Node::Block(..)))
1322+
}
1323+
12481324
pub fn is_no_std_crate(cx: &LateContext<'_>) -> bool {
12491325
cx.tcx.hir().attrs(hir::CRATE_HIR_ID).iter().any(|attr| {
12501326
if let ast::AttrKind::Normal(ref attr, _) = attr.kind {
@@ -1414,28 +1490,43 @@ pub fn peel_hir_pat_refs(pat: &'a Pat<'a>) -> (&'a Pat<'a>, usize) {
14141490
peel(pat, 0)
14151491
}
14161492

1493+
/// Peels of expressions while the given closure returns `Some`.
1494+
pub fn peel_hir_expr_while<'tcx>(
1495+
mut expr: &'tcx Expr<'tcx>,
1496+
mut f: impl FnMut(&'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>>,
1497+
) -> &'tcx Expr<'tcx> {
1498+
while let Some(e) = f(expr) {
1499+
expr = e;
1500+
}
1501+
expr
1502+
}
1503+
14171504
/// Peels off up to the given number of references on the expression. Returns the underlying
14181505
/// expression and the number of references removed.
14191506
pub fn peel_n_hir_expr_refs(expr: &'a Expr<'a>, count: usize) -> (&'a Expr<'a>, usize) {
1420-
fn f(expr: &'a Expr<'a>, count: usize, target: usize) -> (&'a Expr<'a>, usize) {
1421-
match expr.kind {
1422-
ExprKind::AddrOf(_, _, expr) if count != target => f(expr, count + 1, target),
1423-
_ => (expr, count),
1424-
}
1425-
}
1426-
f(expr, 0, count)
1507+
let mut remaining = count;
1508+
let e = peel_hir_expr_while(expr, |e| match e.kind {
1509+
ExprKind::AddrOf(BorrowKind::Ref, _, e) if remaining != 0 => {
1510+
remaining -= 1;
1511+
Some(e)
1512+
},
1513+
_ => None,
1514+
});
1515+
(e, count - remaining)
14271516
}
14281517

14291518
/// Peels off all references on the expression. Returns the underlying expression and the number of
14301519
/// references removed.
14311520
pub fn peel_hir_expr_refs(expr: &'a Expr<'a>) -> (&'a Expr<'a>, usize) {
1432-
fn f(expr: &'a Expr<'a>, count: usize) -> (&'a Expr<'a>, usize) {
1433-
match expr.kind {
1434-
ExprKind::AddrOf(BorrowKind::Ref, _, expr) => f(expr, count + 1),
1435-
_ => (expr, count),
1436-
}
1437-
}
1438-
f(expr, 0)
1521+
let mut count = 0;
1522+
let e = peel_hir_expr_while(expr, |e| match e.kind {
1523+
ExprKind::AddrOf(BorrowKind::Ref, _, e) => {
1524+
count += 1;
1525+
Some(e)
1526+
},
1527+
_ => None,
1528+
});
1529+
(e, count)
14391530
}
14401531

14411532
#[macro_export]

clippy_utils/src/paths.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ pub(super) const BEGIN_PANIC_FMT: [&str; 3] = ["std", "panicking", "begin_panic_
1313
pub const BINARY_HEAP: [&str; 4] = ["alloc", "collections", "binary_heap", "BinaryHeap"];
1414
pub const BORROW_TRAIT: [&str; 3] = ["core", "borrow", "Borrow"];
1515
pub const BTREEMAP: [&str; 5] = ["alloc", "collections", "btree", "map", "BTreeMap"];
16+
pub const BTREEMAP_CONTAINS_KEY: [&str; 6] = ["alloc", "collections", "btree", "map", "BTreeMap", "contains_key"];
1617
pub const BTREEMAP_ENTRY: [&str; 6] = ["alloc", "collections", "btree", "map", "entry", "Entry"];
18+
pub const BTREEMAP_INSERT: [&str; 6] = ["alloc", "collections", "btree", "map", "BTreeMap", "insert"];
1719
pub const BTREESET: [&str; 5] = ["alloc", "collections", "btree", "set", "BTreeSet"];
1820
pub const CLONE_TRAIT_METHOD: [&str; 4] = ["core", "clone", "Clone", "clone"];
1921
pub const CMP_MAX: [&str; 3] = ["core", "cmp", "max"];
@@ -45,7 +47,9 @@ pub const FROM_ITERATOR: [&str; 5] = ["core", "iter", "traits", "collect", "From
4547
pub const FUTURE_FROM_GENERATOR: [&str; 3] = ["core", "future", "from_generator"];
4648
pub const HASH: [&str; 3] = ["core", "hash", "Hash"];
4749
pub const HASHMAP: [&str; 5] = ["std", "collections", "hash", "map", "HashMap"];
50+
pub const HASHMAP_CONTAINS_KEY: [&str; 6] = ["std", "collections", "hash", "map", "HashMap", "contains_key"];
4851
pub const HASHMAP_ENTRY: [&str; 5] = ["std", "collections", "hash", "map", "Entry"];
52+
pub const HASHMAP_INSERT: [&str; 6] = ["std", "collections", "hash", "map", "HashMap", "insert"];
4953
pub const HASHSET: [&str; 5] = ["std", "collections", "hash", "set", "HashSet"];
5054
#[cfg(feature = "internal-lints")]
5155
pub const IDENT: [&str; 3] = ["rustc_span", "symbol", "Ident"];

clippy_utils/src/source.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,15 @@ pub fn indent_of<T: LintContext>(cx: &T, span: Span) -> Option<usize> {
6666
snippet_opt(cx, line_span(cx, span)).and_then(|snip| snip.find(|c: char| !c.is_whitespace()))
6767
}
6868

69+
/// Gets a snippet of the indentation of the line of a span
70+
pub fn snippet_indent<T: LintContext>(cx: &T, span: Span) -> Option<String> {
71+
snippet_opt(cx, line_span(cx, span)).map(|mut s| {
72+
let len = s.len() - s.trim_start().len();
73+
s.truncate(len);
74+
s
75+
})
76+
}
77+
6978
// If the snippet is empty, it's an attribute that was inserted during macro
7079
// expansion and we want to ignore those, because they could come from external
7180
// sources that the user has no control over.

tests/ui/entry.fixed

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// run-rustfix
2+
3+
#![allow(unused, clippy::needless_pass_by_value, clippy::collapsible_if)]
4+
#![warn(clippy::map_entry)]
5+
6+
use std::collections::{BTreeMap, HashMap};
7+
use std::hash::Hash;
8+
9+
macro_rules! m {
10+
($e:expr) => {{ $e }};
11+
}
12+
13+
fn foo() {}
14+
15+
fn hash_map<K: Eq + Hash + Copy, V: Copy>(m: &mut HashMap<K, V>, k: K, v: V, v2: V) {
16+
m.entry(k).or_insert(v);
17+
18+
if let std::collections::hash_map::Entry::Vacant(e) = m.entry(k) {
19+
if true {
20+
e.insert(v);
21+
} else {
22+
e.insert(v2);
23+
}
24+
}
25+
26+
if let std::collections::hash_map::Entry::Vacant(e) = m.entry(k) {
27+
if true {
28+
e.insert(v);
29+
} else {
30+
e.insert(v2);
31+
return;
32+
}
33+
}
34+
35+
if let std::collections::hash_map::Entry::Vacant(e) = m.entry(k) {
36+
foo();
37+
e.insert(v);
38+
}
39+
40+
if let std::collections::hash_map::Entry::Vacant(e) = m.entry(k) {
41+
match 0 {
42+
1 if true => {
43+
e.insert(v);
44+
},
45+
_ => {
46+
e.insert(v2);
47+
},
48+
};
49+
}
50+
51+
if let std::collections::hash_map::Entry::Vacant(e) = m.entry(k) {
52+
match 0 {
53+
0 => {},
54+
1 => {
55+
e.insert(v);
56+
},
57+
_ => {
58+
e.insert(v2);
59+
},
60+
};
61+
}
62+
63+
if let std::collections::hash_map::Entry::Vacant(e) = m.entry(k) {
64+
foo();
65+
match 0 {
66+
0 if false => {
67+
e.insert(v);
68+
},
69+
1 => {
70+
foo();
71+
e.insert(v);
72+
},
73+
2 | 3 => {
74+
for _ in 0..2 {
75+
foo();
76+
}
77+
if true {
78+
e.insert(v);
79+
} else {
80+
e.insert(v2);
81+
};
82+
},
83+
_ => {
84+
e.insert(v2);
85+
},
86+
}
87+
}
88+
89+
if let std::collections::hash_map::Entry::Vacant(e) = m.entry(m!(k)) {
90+
e.insert(m!(v));
91+
}
92+
}
93+
94+
fn btree_map<K: Eq + Ord + Copy, V: Copy>(m: &mut BTreeMap<K, V>, k: K, v: V, v2: V) {
95+
if let std::collections::btree_map::Entry::Vacant(e) = m.entry(k) {
96+
e.insert(v);
97+
foo();
98+
}
99+
}
100+
101+
fn main() {}

tests/ui/entry.rs

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// run-rustfix
2+
3+
#![allow(unused, clippy::needless_pass_by_value, clippy::collapsible_if)]
4+
#![warn(clippy::map_entry)]
5+
6+
use std::collections::{BTreeMap, HashMap};
7+
use std::hash::Hash;
8+
9+
macro_rules! m {
10+
($e:expr) => {{ $e }};
11+
}
12+
13+
fn foo() {}
14+
15+
fn hash_map<K: Eq + Hash + Copy, V: Copy>(m: &mut HashMap<K, V>, k: K, v: V, v2: V) {
16+
if !m.contains_key(&k) {
17+
m.insert(k, v);
18+
}
19+
20+
if !m.contains_key(&k) {
21+
if true {
22+
m.insert(k, v);
23+
} else {
24+
m.insert(k, v2);
25+
}
26+
}
27+
28+
if !m.contains_key(&k) {
29+
if true {
30+
m.insert(k, v);
31+
} else {
32+
m.insert(k, v2);
33+
return;
34+
}
35+
}
36+
37+
if !m.contains_key(&k) {
38+
foo();
39+
m.insert(k, v);
40+
}
41+
42+
if !m.contains_key(&k) {
43+
match 0 {
44+
1 if true => {
45+
m.insert(k, v);
46+
},
47+
_ => {
48+
m.insert(k, v2);
49+
},
50+
};
51+
}
52+
53+
if !m.contains_key(&k) {
54+
match 0 {
55+
0 => {},
56+
1 => {
57+
m.insert(k, v);
58+
},
59+
_ => {
60+
m.insert(k, v2);
61+
},
62+
};
63+
}
64+
65+
if !m.contains_key(&k) {
66+
foo();
67+
match 0 {
68+
0 if false => {
69+
m.insert(k, v);
70+
},
71+
1 => {
72+
foo();
73+
m.insert(k, v);
74+
},
75+
2 | 3 => {
76+
for _ in 0..2 {
77+
foo();
78+
}
79+
if true {
80+
m.insert(k, v);
81+
} else {
82+
m.insert(k, v2);
83+
};
84+
},
85+
_ => {
86+
m.insert(k, v2);
87+
},
88+
}
89+
}
90+
91+
if !m.contains_key(&m!(k)) {
92+
m.insert(m!(k), m!(v));
93+
}
94+
}
95+
96+
fn btree_map<K: Eq + Ord + Copy, V: Copy>(m: &mut BTreeMap<K, V>, k: K, v: V, v2: V) {
97+
if !m.contains_key(&k) {
98+
m.insert(k, v);
99+
foo();
100+
}
101+
}
102+
103+
fn main() {}

0 commit comments

Comments
 (0)