Skip to content

Commit 8618f60

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 4d68619 commit 8618f60

File tree

12 files changed

+883
-281
lines changed

12 files changed

+883
-281
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: 109 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,10 @@ use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
6161
use rustc_hir::def_id::{DefId, LOCAL_CRATE};
6262
use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
6363
use rustc_hir::{
64-
def, Arm, BindingAnnotation, Block, Body, Constness, CrateItem, Expr, ExprKind, FnDecl, ForeignItem, GenericArgs,
65-
GenericParam, HirId, Impl, ImplItem, ImplItemKind, Item, ItemKind, LangItem, Lifetime, Local, MacroDef,
66-
MatchSource, Node, Param, Pat, PatKind, Path, PathSegment, QPath, Stmt, StructField, TraitItem, TraitItemKind,
67-
TraitRef, TyKind, Variant, Visibility,
64+
def, Arm, BindingAnnotation, Block, Body, Constness, CrateItem, Destination, Expr, ExprKind, FnDecl, ForeignItem,
65+
GenericArgs, GenericParam, HirId, Impl, ImplItem, ImplItemKind, Item, ItemKind, LangItem, Lifetime, Local,
66+
MacroDef, MatchSource, Node, Param, Pat, PatKind, Path, PathSegment, QPath, Stmt, StmtKind, StructField, TraitItem,
67+
TraitItemKind, TraitRef, TyKind, Variant, Visibility,
6868
};
6969
use rustc_lint::{LateContext, Level, Lint, LintContext};
7070
use rustc_middle::hir::exports::Export;
@@ -1254,6 +1254,82 @@ pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
12541254
did.map_or(false, |did| must_use_attr(&cx.tcx.get_attrs(did)).is_some())
12551255
}
12561256

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

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

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

14401531
#[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"];
@@ -46,7 +48,9 @@ pub const FROM_ITERATOR: [&str; 5] = ["core", "iter", "traits", "collect", "From
4648
pub const FUTURE_FROM_GENERATOR: [&str; 3] = ["core", "future", "from_generator"];
4749
pub const HASH: [&str; 3] = ["core", "hash", "Hash"];
4850
pub const HASHMAP: [&str; 5] = ["std", "collections", "hash", "map", "HashMap"];
51+
pub const HASHMAP_CONTAINS_KEY: [&str; 6] = ["std", "collections", "hash", "map", "HashMap", "contains_key"];
4952
pub const HASHMAP_ENTRY: [&str; 5] = ["std", "collections", "hash", "map", "Entry"];
53+
pub const HASHMAP_INSERT: [&str; 6] = ["std", "collections", "hash", "map", "HashMap", "insert"];
5054
pub const HASHSET: [&str; 5] = ["std", "collections", "hash", "set", "HashSet"];
5155
#[cfg(feature = "internal-lints")]
5256
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)