Skip to content

Commit 1e0a3ff

Browse files
committed
Auto merge of rust-lang#6937 - Jarcho:map_entry_suggestion, r=giraffate
Improve `map_entry` suggestion fixes: rust-lang#5176 fixes: rust-lang#4674 fixes: rust-lang#4664 fixes: rust-lang#1450 Still need to handle the value returned by `insert` correctly. changelog: Improve `map_entry` suggestion. Will now suggest `or_insert`, `insert_with` or `match _.entry(_)` as appopriate. changelog: Fix `map_entry` false positives where the entry api can't be used. e.g. when the map is used for multiple things.
2 parents ddc2598 + bcf3488 commit 1e0a3ff

19 files changed

+1518
-371
lines changed

clippy_lints/src/entry.rs

+575-122
Large diffs are not rendered by default.

clippy_lints/src/manual_map.rs

+5-52
Original file line numberDiff line numberDiff line change
@@ -1,16 +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::{in_constant, is_allowed, is_else_clause, is_lang_ctor, match_var, 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, in_constant, is_allowed, is_else_clause, is_lang_ctor, match_var, peel_hir_expr_refs,
7+
};
68
use rustc_ast::util::parser::PREC_POSTFIX;
79
use rustc_errors::Applicability;
810
use rustc_hir::LangItem::{OptionNone, OptionSome};
9-
use rustc_hir::{
10-
def::Res,
11-
intravisit::{walk_expr, ErasedMap, NestedVisitorMap, Visitor},
12-
Arm, BindingAnnotation, Block, Expr, ExprKind, MatchSource, Mutability, Pat, PatKind, Path, QPath,
13-
};
11+
use rustc_hir::{Arm, BindingAnnotation, Block, Expr, ExprKind, MatchSource, Mutability, Pat, PatKind};
1412
use rustc_lint::{LateContext, LateLintPass, LintContext};
1513
use rustc_middle::lint::in_external_macro;
1614
use rustc_session::{declare_lint_pass, declare_tool_lint};
@@ -199,51 +197,6 @@ impl LateLintPass<'_> for ManualMap {
199197
}
200198
}
201199

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

clippy_utils/src/lib.rs

+146-19
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,12 @@ use rustc_data_structures::fx::FxHashMap;
6060
use rustc_hir as hir;
6161
use rustc_hir::def::{DefKind, Res};
6262
use rustc_hir::def_id::{DefId, LOCAL_CRATE};
63-
use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
63+
use rustc_hir::intravisit::{self, walk_expr, ErasedMap, 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, Local, MatchSource, Node, Param, Pat, PatKind, Path, PathSegment,
68+
QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitRef, TyKind,
6969
};
7070
use rustc_lint::{LateContext, Level, Lint, LintContext};
7171
use rustc_middle::hir::exports::Export;
@@ -82,7 +82,7 @@ use rustc_span::{Span, DUMMY_SP};
8282
use rustc_target::abi::Integer;
8383

8484
use crate::consts::{constant, Constant};
85-
use crate::ty::is_recursively_primitive_type;
85+
use crate::ty::{can_partially_move_ty, is_recursively_primitive_type};
8686

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

551+
/// Checks if the top level expression can be moved into a closure as is.
552+
pub fn can_move_expr_to_closure_no_visit(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, jump_targets: &[HirId]) -> bool {
553+
match expr.kind {
554+
ExprKind::Break(Destination { target_id: Ok(id), .. }, _)
555+
| ExprKind::Continue(Destination { target_id: Ok(id), .. })
556+
if jump_targets.contains(&id) =>
557+
{
558+
true
559+
},
560+
ExprKind::Break(..)
561+
| ExprKind::Continue(_)
562+
| ExprKind::Ret(_)
563+
| ExprKind::Yield(..)
564+
| ExprKind::InlineAsm(_)
565+
| ExprKind::LlvmInlineAsm(_) => false,
566+
// Accessing a field of a local value can only be done if the type isn't
567+
// partially moved.
568+
ExprKind::Field(base_expr, _)
569+
if matches!(
570+
base_expr.kind,
571+
ExprKind::Path(QPath::Resolved(_, Path { res: Res::Local(_), .. }))
572+
) && can_partially_move_ty(cx, cx.typeck_results().expr_ty(base_expr)) =>
573+
{
574+
// TODO: check if the local has been partially moved. Assume it has for now.
575+
false
576+
}
577+
_ => true,
578+
}
579+
}
580+
581+
/// Checks if the expression can be moved into a closure as is.
582+
pub fn can_move_expr_to_closure(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
583+
struct V<'cx, 'tcx> {
584+
cx: &'cx LateContext<'tcx>,
585+
loops: Vec<HirId>,
586+
allow_closure: bool,
587+
}
588+
impl Visitor<'tcx> for V<'_, 'tcx> {
589+
type Map = ErasedMap<'tcx>;
590+
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
591+
NestedVisitorMap::None
592+
}
593+
594+
fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
595+
if !self.allow_closure {
596+
return;
597+
}
598+
if let ExprKind::Loop(b, ..) = e.kind {
599+
self.loops.push(e.hir_id);
600+
self.visit_block(b);
601+
self.loops.pop();
602+
} else {
603+
self.allow_closure &= can_move_expr_to_closure_no_visit(self.cx, e, &self.loops);
604+
walk_expr(self, e);
605+
}
606+
}
607+
}
608+
609+
let mut v = V {
610+
cx,
611+
allow_closure: true,
612+
loops: Vec::new(),
613+
};
614+
v.visit_expr(expr);
615+
v.allow_closure
616+
}
617+
551618
/// Returns the method names and argument list of nested method call expressions that make up
552619
/// `expr`. method/span lists are sorted with the most recent call first.
553620
pub fn method_calls<'tcx>(
@@ -1272,6 +1339,51 @@ pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
12721339
did.map_or(false, |did| must_use_attr(&cx.tcx.get_attrs(did)).is_some())
12731340
}
12741341

1342+
/// Gets the node where an expression is either used, or it's type is unified with another branch.
1343+
pub fn get_expr_use_or_unification_node(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option<Node<'tcx>> {
1344+
let map = tcx.hir();
1345+
let mut child_id = expr.hir_id;
1346+
let mut iter = map.parent_iter(child_id);
1347+
loop {
1348+
match iter.next() {
1349+
None => break None,
1350+
Some((id, Node::Block(_))) => child_id = id,
1351+
Some((id, Node::Arm(arm))) if arm.body.hir_id == child_id => child_id = id,
1352+
Some((_, Node::Expr(expr))) => match expr.kind {
1353+
ExprKind::Match(_, [arm], _) if arm.hir_id == child_id => child_id = expr.hir_id,
1354+
ExprKind::Block(..) | ExprKind::DropTemps(_) => child_id = expr.hir_id,
1355+
ExprKind::If(_, then_expr, None) if then_expr.hir_id == child_id => break None,
1356+
_ => break Some(Node::Expr(expr)),
1357+
},
1358+
Some((_, node)) => break Some(node),
1359+
}
1360+
}
1361+
}
1362+
1363+
/// Checks if the result of an expression is used, or it's type is unified with another branch.
1364+
pub fn is_expr_used_or_unified(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
1365+
!matches!(
1366+
get_expr_use_or_unification_node(tcx, expr),
1367+
None | Some(Node::Stmt(Stmt {
1368+
kind: StmtKind::Expr(_)
1369+
| StmtKind::Semi(_)
1370+
| StmtKind::Local(Local {
1371+
pat: Pat {
1372+
kind: PatKind::Wild,
1373+
..
1374+
},
1375+
..
1376+
}),
1377+
..
1378+
}))
1379+
)
1380+
}
1381+
1382+
/// Checks if the expression is the final expression returned from a block.
1383+
pub fn is_expr_final_block_expr(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
1384+
matches!(get_parent_node(tcx, expr.hir_id), Some(Node::Block(..)))
1385+
}
1386+
12751387
pub fn is_no_std_crate(cx: &LateContext<'_>) -> bool {
12761388
cx.tcx.hir().attrs(hir::CRATE_HIR_ID).iter().any(|attr| {
12771389
if let ast::AttrKind::Normal(ref attr, _) = attr.kind {
@@ -1441,28 +1553,43 @@ pub fn peel_hir_pat_refs(pat: &'a Pat<'a>) -> (&'a Pat<'a>, usize) {
14411553
peel(pat, 0)
14421554
}
14431555

1556+
/// Peels of expressions while the given closure returns `Some`.
1557+
pub fn peel_hir_expr_while<'tcx>(
1558+
mut expr: &'tcx Expr<'tcx>,
1559+
mut f: impl FnMut(&'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>>,
1560+
) -> &'tcx Expr<'tcx> {
1561+
while let Some(e) = f(expr) {
1562+
expr = e;
1563+
}
1564+
expr
1565+
}
1566+
14441567
/// Peels off up to the given number of references on the expression. Returns the underlying
14451568
/// expression and the number of references removed.
14461569
pub fn peel_n_hir_expr_refs(expr: &'a Expr<'a>, count: usize) -> (&'a Expr<'a>, usize) {
1447-
fn f(expr: &'a Expr<'a>, count: usize, target: usize) -> (&'a Expr<'a>, usize) {
1448-
match expr.kind {
1449-
ExprKind::AddrOf(_, _, expr) if count != target => f(expr, count + 1, target),
1450-
_ => (expr, count),
1451-
}
1452-
}
1453-
f(expr, 0, count)
1570+
let mut remaining = count;
1571+
let e = peel_hir_expr_while(expr, |e| match e.kind {
1572+
ExprKind::AddrOf(BorrowKind::Ref, _, e) if remaining != 0 => {
1573+
remaining -= 1;
1574+
Some(e)
1575+
},
1576+
_ => None,
1577+
});
1578+
(e, count - remaining)
14541579
}
14551580

14561581
/// Peels off all references on the expression. Returns the underlying expression and the number of
14571582
/// references removed.
14581583
pub fn peel_hir_expr_refs(expr: &'a Expr<'a>) -> (&'a Expr<'a>, usize) {
1459-
fn f(expr: &'a Expr<'a>, count: usize) -> (&'a Expr<'a>, usize) {
1460-
match expr.kind {
1461-
ExprKind::AddrOf(BorrowKind::Ref, _, expr) => f(expr, count + 1),
1462-
_ => (expr, count),
1463-
}
1464-
}
1465-
f(expr, 0)
1584+
let mut count = 0;
1585+
let e = peel_hir_expr_while(expr, |e| match e.kind {
1586+
ExprKind::AddrOf(BorrowKind::Ref, _, e) => {
1587+
count += 1;
1588+
Some(e)
1589+
},
1590+
_ => None,
1591+
});
1592+
(e, count)
14661593
}
14671594

14681595
#[macro_export]

clippy_utils/src/paths.rs

+4
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_STR_METHOD: [&str; 5] = ["core", "str", "traits", "FromStr", "fro
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

+9
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.

0 commit comments

Comments
 (0)