Skip to content

Commit 288e3c4

Browse files
committed
Improve map_entry suggestion. Will now suggest or_insert, insert_with or match _.entry(_) as appopriate.
Fix `map_entry` false positives where the entry api can't be used. e.g. when the map is used for multiple things.
1 parent 4d68619 commit 288e3c4

15 files changed

+1218
-299
lines changed

clippy_lints/src/entry.rs

Lines changed: 494 additions & 126 deletions
Large diffs are not rendered by default.

clippy_lints/src/manual_map.rs

Lines changed: 6 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
use crate::{map_unit_fn::OPTION_MAP_UNIT_FN, matches::MATCH_AS_REF};
22
use clippy_utils::diagnostics::span_lint_and_sugg;
33
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
4-
use clippy_utils::ty::{can_partially_move_ty, is_type_diagnostic_item, peel_mid_ty_refs_is_mutable};
5-
use clippy_utils::{is_allowed, is_else_clause_of_if_let_else, match_def_path, match_var, paths, peel_hir_expr_refs};
4+
use clippy_utils::ty::{is_type_diagnostic_item, peel_mid_ty_refs_is_mutable};
5+
use clippy_utils::{
6+
can_move_expr_to_closure, is_allowed, is_else_clause_of_if_let_else, match_def_path, match_var, paths,
7+
peel_hir_expr_refs,
8+
};
69
use rustc_ast::util::parser::PREC_POSTFIX;
710
use rustc_errors::Applicability;
8-
use rustc_hir::{
9-
def::Res,
10-
intravisit::{walk_expr, ErasedMap, NestedVisitorMap, Visitor},
11-
Arm, BindingAnnotation, Block, Expr, ExprKind, MatchSource, Mutability, Pat, PatKind, Path, QPath,
12-
};
11+
use rustc_hir::{Arm, BindingAnnotation, Block, Expr, ExprKind, MatchSource, Mutability, Pat, PatKind, QPath};
1312
use rustc_lint::{LateContext, LateLintPass, LintContext};
1413
use rustc_middle::lint::in_external_macro;
1514
use rustc_session::{declare_lint_pass, declare_tool_lint};
@@ -193,51 +192,6 @@ impl LateLintPass<'_> for ManualMap {
193192
}
194193
}
195194

196-
// Checks if the expression can be moved into a closure as is.
197-
fn can_move_expr_to_closure(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
198-
struct V<'cx, 'tcx> {
199-
cx: &'cx LateContext<'tcx>,
200-
make_closure: bool,
201-
}
202-
impl Visitor<'tcx> for V<'_, 'tcx> {
203-
type Map = ErasedMap<'tcx>;
204-
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
205-
NestedVisitorMap::None
206-
}
207-
208-
fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
209-
match e.kind {
210-
ExprKind::Break(..)
211-
| ExprKind::Continue(_)
212-
| ExprKind::Ret(_)
213-
| ExprKind::Yield(..)
214-
| ExprKind::InlineAsm(_)
215-
| ExprKind::LlvmInlineAsm(_) => {
216-
self.make_closure = false;
217-
},
218-
// Accessing a field of a local value can only be done if the type isn't
219-
// partially moved.
220-
ExprKind::Field(base_expr, _)
221-
if matches!(
222-
base_expr.kind,
223-
ExprKind::Path(QPath::Resolved(_, Path { res: Res::Local(_), .. }))
224-
) && can_partially_move_ty(self.cx, self.cx.typeck_results().expr_ty(base_expr)) =>
225-
{
226-
// TODO: check if the local has been partially moved. Assume it has for now.
227-
self.make_closure = false;
228-
return;
229-
}
230-
_ => (),
231-
};
232-
walk_expr(self, e);
233-
}
234-
}
235-
236-
let mut v = V { cx, make_closure: true };
237-
v.visit_expr(expr);
238-
v.make_closure
239-
}
240-
241195
// Checks whether the expression could be passed as a function, or whether a closure is needed.
242196
// Returns the function to be passed to `map` if it exists.
243197
fn can_pass_as_func(cx: &LateContext<'tcx>, binding: Ident, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {

clippy_utils/src/lib.rs

Lines changed: 144 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,12 @@ use rustc_data_structures::fx::FxHashMap;
5959
use rustc_hir as hir;
6060
use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
6161
use rustc_hir::def_id::{DefId, LOCAL_CRATE};
62-
use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
62+
use rustc_hir::intravisit::{self, walk_expr, ErasedMap, 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;
@@ -81,7 +81,7 @@ use rustc_span::{Span, DUMMY_SP};
8181
use rustc_target::abi::Integer;
8282

8383
use crate::consts::{constant, Constant};
84-
use crate::ty::is_recursively_primitive_type;
84+
use crate::ty::{can_partially_move_ty, is_recursively_primitive_type};
8585

8686
pub fn parse_msrv(msrv: &str, sess: Option<&Session>, span: Option<Span>) -> Option<RustcVersion> {
8787
if let Ok(version) = RustcVersion::parse(msrv) {
@@ -515,6 +515,73 @@ pub fn trait_ref_of_method<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Optio
515515
None
516516
}
517517

518+
/// Checks if the top level expression can be moved into a closure as is.
519+
pub fn can_move_expr_to_closure_no_visit(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, jump_targets: &[HirId]) -> bool {
520+
match expr.kind {
521+
ExprKind::Break(Destination { target_id: Ok(id), .. }, _)
522+
| ExprKind::Continue(Destination { target_id: Ok(id), .. })
523+
if jump_targets.contains(&id) =>
524+
{
525+
true
526+
},
527+
ExprKind::Break(..)
528+
| ExprKind::Continue(_)
529+
| ExprKind::Ret(_)
530+
| ExprKind::Yield(..)
531+
| ExprKind::InlineAsm(_)
532+
| ExprKind::LlvmInlineAsm(_) => false,
533+
// Accessing a field of a local value can only be done if the type isn't
534+
// partially moved.
535+
ExprKind::Field(base_expr, _)
536+
if matches!(
537+
base_expr.kind,
538+
ExprKind::Path(QPath::Resolved(_, Path { res: Res::Local(_), .. }))
539+
) && can_partially_move_ty(cx, cx.typeck_results().expr_ty(base_expr)) =>
540+
{
541+
// TODO: check if the local has been partially moved. Assume it has for now.
542+
false
543+
}
544+
_ => true,
545+
}
546+
}
547+
548+
/// Checks if the expression can be moved into a closure as is.
549+
pub fn can_move_expr_to_closure(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
550+
struct V<'cx, 'tcx> {
551+
cx: &'cx LateContext<'tcx>,
552+
loops: Vec<HirId>,
553+
allow_closure: bool,
554+
}
555+
impl Visitor<'tcx> for V<'_, 'tcx> {
556+
type Map = ErasedMap<'tcx>;
557+
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
558+
NestedVisitorMap::None
559+
}
560+
561+
fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
562+
if !self.allow_closure {
563+
return;
564+
}
565+
if let ExprKind::Loop(b, ..) = e.kind {
566+
self.loops.push(e.hir_id);
567+
self.visit_block(b);
568+
self.loops.pop();
569+
} else {
570+
self.allow_closure &= can_move_expr_to_closure_no_visit(self.cx, e, &self.loops);
571+
walk_expr(self, e);
572+
}
573+
}
574+
}
575+
576+
let mut v = V {
577+
cx,
578+
allow_closure: true,
579+
loops: Vec::new(),
580+
};
581+
v.visit_expr(expr);
582+
v.allow_closure
583+
}
584+
518585
/// Returns the method names and argument list of nested method call expressions that make up
519586
/// `expr`. method/span lists are sorted with the most recent call first.
520587
pub fn method_calls<'tcx>(
@@ -1254,6 +1321,48 @@ pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
12541321
did.map_or(false, |did| must_use_attr(&cx.tcx.get_attrs(did)).is_some())
12551322
}
12561323

1324+
pub fn get_expr_use_node(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option<Node<'tcx>> {
1325+
let map = tcx.hir();
1326+
let mut child_id = expr.hir_id;
1327+
let mut iter = map.parent_iter(child_id);
1328+
loop {
1329+
match iter.next() {
1330+
None => break None,
1331+
Some((id, Node::Block(_))) => child_id = id,
1332+
Some((id, Node::Arm(arm))) if arm.body.hir_id == child_id => child_id = id,
1333+
Some((_, Node::Expr(expr))) => match expr.kind {
1334+
ExprKind::Break(
1335+
Destination {
1336+
target_id: Ok(dest), ..
1337+
},
1338+
_,
1339+
) => {
1340+
iter = map.parent_iter(dest);
1341+
child_id = dest;
1342+
},
1343+
ExprKind::DropTemps(_) | ExprKind::Block(..) => child_id = expr.hir_id,
1344+
ExprKind::If(control_expr, ..) | ExprKind::Match(control_expr, ..)
1345+
if control_expr.hir_id != child_id =>
1346+
{
1347+
child_id = expr.hir_id
1348+
},
1349+
_ => break Some(Node::Expr(expr)),
1350+
},
1351+
Some((_, node)) => break Some(node),
1352+
}
1353+
}
1354+
}
1355+
1356+
pub fn is_expr_used(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
1357+
!matches!(
1358+
get_expr_use_node(tcx, expr),
1359+
Some(Node::Stmt(Stmt {
1360+
kind: StmtKind::Expr(_) | StmtKind::Semi(_),
1361+
..
1362+
}))
1363+
)
1364+
}
1365+
12571366
pub fn is_no_std_crate(cx: &LateContext<'_>) -> bool {
12581367
cx.tcx.hir().attrs(hir::CRATE_HIR_ID).iter().any(|attr| {
12591368
if let ast::AttrKind::Normal(ref attr, _) = attr.kind {
@@ -1413,28 +1522,43 @@ pub fn peel_hir_pat_refs(pat: &'a Pat<'a>) -> (&'a Pat<'a>, usize) {
14131522
peel(pat, 0)
14141523
}
14151524

1525+
/// Peels of expressions while the given closure returns `Some`.
1526+
pub fn peel_hir_expr_while<'tcx>(
1527+
mut expr: &'tcx Expr<'tcx>,
1528+
mut f: impl FnMut(&'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>>,
1529+
) -> &'tcx Expr<'tcx> {
1530+
while let Some(e) = f(expr) {
1531+
expr = e;
1532+
}
1533+
expr
1534+
}
1535+
14161536
/// Peels off up to the given number of references on the expression. Returns the underlying
14171537
/// expression and the number of references removed.
14181538
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)
1539+
let mut remaining = count;
1540+
let e = peel_hir_expr_while(expr, |e| match e.kind {
1541+
ExprKind::AddrOf(BorrowKind::Ref, _, e) if remaining != 0 => {
1542+
remaining -= 1;
1543+
Some(e)
1544+
},
1545+
_ => None,
1546+
});
1547+
(e, count - remaining)
14261548
}
14271549

14281550
/// Peels off all references on the expression. Returns the underlying expression and the number of
14291551
/// references removed.
14301552
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)
1553+
let mut count = 0;
1554+
let e = peel_hir_expr_while(expr, |e| match e.kind {
1555+
ExprKind::AddrOf(BorrowKind::Ref, _, e) => {
1556+
count += 1;
1557+
Some(e)
1558+
},
1559+
_ => None,
1560+
});
1561+
(e, count)
14381562
}
14391563

14401564
#[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"];

0 commit comments

Comments
 (0)