Skip to content

Commit 49ef8f7

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 86fb0e8 commit 49ef8f7

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
@@ -60,9 +60,9 @@ use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
6060
use rustc_hir::def_id::{DefId, LOCAL_CRATE};
6161
use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
6262
use rustc_hir::{
63-
def, Arm, BindingAnnotation, Block, Body, Constness, CrateItem, Expr, ExprKind, FieldDef, FnDecl, ForeignItem,
64-
GenericArgs, GenericParam, HirId, Impl, ImplItem, ImplItemKind, Item, ItemKind, LangItem, Lifetime, Local,
65-
MacroDef, MatchSource, Node, Param, Pat, PatKind, Path, PathSegment, QPath, Stmt, TraitItem, TraitItemKind,
63+
def, Arm, BindingAnnotation, Block, Body, Constness, CrateItem, Destination, Expr, ExprKind, FieldDef, FnDecl,
64+
ForeignItem, GenericArgs, GenericParam, HirId, Impl, ImplItem, ImplItemKind, Item, ItemKind, LangItem, Lifetime,
65+
Local, MacroDef, MatchSource, Node, Param, Pat, PatKind, Path, PathSegment, QPath, Stmt, TraitItem, TraitItemKind,
6666
TraitRef, TyKind, Variant, Visibility,
6767
};
6868
use rustc_lint::{LateContext, Level, Lint, LintContext};
@@ -1260,6 +1260,82 @@ pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
12601260
did.map_or(false, |did| must_use_attr(&cx.tcx.get_attrs(did)).is_some())
12611261
}
12621262

1263+
pub fn get_expr_use_node(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option<Node<'tcx>> {
1264+
let map = tcx.hir();
1265+
let mut child_id = expr.hir_id;
1266+
let mut iter = map.parent_iter(child_id);
1267+
loop {
1268+
match iter.next() {
1269+
None => break None,
1270+
Some((id, Node::Block(_))) => child_id = id,
1271+
Some((id, Node::Arm(arm))) if arm.body.hir_id == child_id => child_id = id,
1272+
Some((_, Node::Expr(expr))) => match expr.kind {
1273+
ExprKind::Break(
1274+
Destination {
1275+
target_id: Ok(dest), ..
1276+
},
1277+
_,
1278+
) => {
1279+
iter = map.parent_iter(dest);
1280+
child_id = dest;
1281+
},
1282+
ExprKind::DropTemps(_) | ExprKind::Block(..) => child_id = expr.hir_id,
1283+
ExprKind::If(control_expr, ..) | ExprKind::Match(control_expr, ..)
1284+
if control_expr.hir_id != child_id =>
1285+
{
1286+
child_id = expr.hir_id
1287+
},
1288+
_ => break Some(Node::Expr(expr)),
1289+
},
1290+
Some((_, node)) => break Some(node),
1291+
}
1292+
}
1293+
}
1294+
1295+
pub fn is_expr_used(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
1296+
!matches!(
1297+
get_expr_use_node(tcx, expr),
1298+
Some(Node::Stmt(Stmt {
1299+
kind: StmtKind::Expr(_) | StmtKind::Semi(_),
1300+
..
1301+
}))
1302+
)
1303+
}
1304+
1305+
pub fn get_expr_use_or_unification_node(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option<Node<'tcx>> {
1306+
let map = tcx.hir();
1307+
let mut child_id = expr.hir_id;
1308+
let mut iter = map.parent_iter(child_id);
1309+
loop {
1310+
match iter.next() {
1311+
None => break None,
1312+
Some((id, Node::Block(_))) => child_id = id,
1313+
Some((id, Node::Arm(arm))) if arm.body.hir_id == child_id => child_id = id,
1314+
Some((_, Node::Expr(expr))) => match expr.kind {
1315+
ExprKind::Match(_, [arm], _) if arm.hir_id == child_id => child_id = expr.hir_id,
1316+
ExprKind::Block(..) | ExprKind::DropTemps(_) => child_id = expr.hir_id,
1317+
ExprKind::If(_, then_expr, None) if then_expr.hir_id == child_id => break None,
1318+
_ => break Some(Node::Expr(expr)),
1319+
},
1320+
Some((_, node)) => break Some(node),
1321+
}
1322+
}
1323+
}
1324+
1325+
pub fn is_expr_used_or_unified(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
1326+
!matches!(
1327+
get_expr_use_or_unification_node(tcx, expr),
1328+
None | Some(Node::Stmt(Stmt {
1329+
kind: StmtKind::Expr(_) | StmtKind::Semi(_),
1330+
..
1331+
}))
1332+
)
1333+
}
1334+
1335+
pub fn is_expr_final_block_expr(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
1336+
matches!(get_parent_node(tcx, expr.hir_id), Some(Node::Block(..)))
1337+
}
1338+
12631339
pub fn is_no_std_crate(cx: &LateContext<'_>) -> bool {
12641340
cx.tcx.hir().attrs(hir::CRATE_HIR_ID).iter().any(|attr| {
12651341
if let ast::AttrKind::Normal(ref attr, _) = attr.kind {
@@ -1419,28 +1495,43 @@ pub fn peel_hir_pat_refs(pat: &'a Pat<'a>) -> (&'a Pat<'a>, usize) {
14191495
peel(pat, 0)
14201496
}
14211497

1498+
/// Peels of expressions while the given closure returns `Some`.
1499+
pub fn peel_hir_expr_while<'tcx>(
1500+
mut expr: &'tcx Expr<'tcx>,
1501+
mut f: impl FnMut(&'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>>,
1502+
) -> &'tcx Expr<'tcx> {
1503+
while let Some(e) = f(expr) {
1504+
expr = e;
1505+
}
1506+
expr
1507+
}
1508+
14221509
/// Peels off up to the given number of references on the expression. Returns the underlying
14231510
/// expression and the number of references removed.
14241511
pub fn peel_n_hir_expr_refs(expr: &'a Expr<'a>, count: usize) -> (&'a Expr<'a>, usize) {
1425-
fn f(expr: &'a Expr<'a>, count: usize, target: usize) -> (&'a Expr<'a>, usize) {
1426-
match expr.kind {
1427-
ExprKind::AddrOf(_, _, expr) if count != target => f(expr, count + 1, target),
1428-
_ => (expr, count),
1429-
}
1430-
}
1431-
f(expr, 0, count)
1512+
let mut remaining = count;
1513+
let e = peel_hir_expr_while(expr, |e| match e.kind {
1514+
ExprKind::AddrOf(BorrowKind::Ref, _, e) if remaining != 0 => {
1515+
remaining -= 1;
1516+
Some(e)
1517+
},
1518+
_ => None,
1519+
});
1520+
(e, count - remaining)
14321521
}
14331522

14341523
/// Peels off all references on the expression. Returns the underlying expression and the number of
14351524
/// references removed.
14361525
pub fn peel_hir_expr_refs(expr: &'a Expr<'a>) -> (&'a Expr<'a>, usize) {
1437-
fn f(expr: &'a Expr<'a>, count: usize) -> (&'a Expr<'a>, usize) {
1438-
match expr.kind {
1439-
ExprKind::AddrOf(BorrowKind::Ref, _, expr) => f(expr, count + 1),
1440-
_ => (expr, count),
1441-
}
1442-
}
1443-
f(expr, 0)
1526+
let mut count = 0;
1527+
let e = peel_hir_expr_while(expr, |e| match e.kind {
1528+
ExprKind::AddrOf(BorrowKind::Ref, _, e) => {
1529+
count += 1;
1530+
Some(e)
1531+
},
1532+
_ => None,
1533+
});
1534+
(e, count)
14441535
}
14451536

14461537
#[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"];
@@ -47,7 +49,9 @@ pub const FROM_ITERATOR: [&str; 5] = ["core", "iter", "traits", "collect", "From
4749
pub const FUTURE_FROM_GENERATOR: [&str; 3] = ["core", "future", "from_generator"];
4850
pub const HASH: [&str; 3] = ["core", "hash", "Hash"];
4951
pub const HASHMAP: [&str; 5] = ["std", "collections", "hash", "map", "HashMap"];
52+
pub const HASHMAP_CONTAINS_KEY: [&str; 6] = ["std", "collections", "hash", "map", "HashMap", "contains_key"];
5053
pub const HASHMAP_ENTRY: [&str; 5] = ["std", "collections", "hash", "map", "Entry"];
54+
pub const HASHMAP_INSERT: [&str; 6] = ["std", "collections", "hash", "map", "HashMap", "insert"];
5155
pub const HASHSET: [&str; 5] = ["std", "collections", "hash", "set", "HashSet"];
5256
#[cfg(feature = "internal-lints")]
5357
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)