Skip to content

Commit 182007c

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 624e8aa commit 182007c

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, Expr, ExprKind, FnDecl, GenericArgs, HirId, Impl, ImplItem,
64-
ImplItemKind, Item, ItemKind, LangItem, MatchSource, Node, Param, Pat, PatKind, Path, PathSegment, QPath,
65-
TraitItem, TraitItemKind, TraitRef, TyKind,
63+
def, Arm, BindingAnnotation, Block, Body, Constness, Destination, Expr, ExprKind, FnDecl, GenericArgs, HirId, Impl,
64+
ImplItem, ImplItemKind, Item, ItemKind, LangItem, MatchSource, Node, Param, Pat, PatKind, Path, PathSegment, QPath,
65+
Stmt, StmtKind, TraitItem, TraitItemKind, TraitRef, TyKind,
6666
};
6767
use rustc_lint::{LateContext, Level, Lint, LintContext};
6868
use rustc_middle::hir::exports::Export;
@@ -1234,6 +1234,82 @@ pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
12341234
did.map_or(false, |did| must_use_attr(&cx.tcx.get_attrs(did)).is_some())
12351235
}
12361236

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

1482+
/// Peels of expressions while the given closure returns `Some`.
1483+
pub fn peel_hir_expr_while<'tcx>(
1484+
mut expr: &'tcx Expr<'tcx>,
1485+
mut f: impl FnMut(&'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>>,
1486+
) -> &'tcx Expr<'tcx> {
1487+
while let Some(e) = f(expr) {
1488+
expr = e;
1489+
}
1490+
expr
1491+
}
1492+
14061493
/// Peels off up to the given number of references on the expression. Returns the underlying
14071494
/// expression and the number of references removed.
14081495
pub fn peel_n_hir_expr_refs(expr: &'a Expr<'a>, count: usize) -> (&'a Expr<'a>, usize) {
1409-
fn f(expr: &'a Expr<'a>, count: usize, target: usize) -> (&'a Expr<'a>, usize) {
1410-
match expr.kind {
1411-
ExprKind::AddrOf(_, _, expr) if count != target => f(expr, count + 1, target),
1412-
_ => (expr, count),
1413-
}
1414-
}
1415-
f(expr, 0, count)
1496+
let mut remaining = count;
1497+
let e = peel_hir_expr_while(expr, |e| match e.kind {
1498+
ExprKind::AddrOf(BorrowKind::Ref, _, e) if remaining != 0 => {
1499+
remaining -= 1;
1500+
Some(e)
1501+
},
1502+
_ => None,
1503+
});
1504+
(e, count - remaining)
14161505
}
14171506

14181507
/// Peels off all references on the expression. Returns the underlying expression and the number of
14191508
/// references removed.
14201509
pub fn peel_hir_expr_refs(expr: &'a Expr<'a>) -> (&'a Expr<'a>, usize) {
1421-
fn f(expr: &'a Expr<'a>, count: usize) -> (&'a Expr<'a>, usize) {
1422-
match expr.kind {
1423-
ExprKind::AddrOf(BorrowKind::Ref, _, expr) => f(expr, count + 1),
1424-
_ => (expr, count),
1425-
}
1426-
}
1427-
f(expr, 0)
1510+
let mut count = 0;
1511+
let e = peel_hir_expr_while(expr, |e| match e.kind {
1512+
ExprKind::AddrOf(BorrowKind::Ref, _, e) => {
1513+
count += 1;
1514+
Some(e)
1515+
},
1516+
_ => None,
1517+
});
1518+
(e, count)
14281519
}
14291520

14301521
#[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)