Skip to content

Commit 762651b

Browse files
committed
Minor cleanup of map_entry and a few additional tests.
1 parent 4de975c commit 762651b

File tree

9 files changed

+208
-143
lines changed

9 files changed

+208
-143
lines changed

clippy_lints/src/entry.rs

Lines changed: 68 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,14 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass {
7979
let sugg = if let Some(else_expr) = else_expr {
8080
// if .. { .. } else { .. }
8181
let else_search = match find_insert_calls(cx, &contains_expr, else_expr) {
82-
Some(search) if !(then_search.edits.is_empty() && search.edits.is_empty()) => search,
83-
_ => return,
82+
Some(search) => search,
83+
None => return,
8484
};
8585

86-
if then_search.edits.is_empty() || else_search.edits.is_empty() {
87-
// if .. { insert } else { .. } or if .. { .. } else { then } of
86+
if then_search.edits.is_empty() && else_search.edits.is_empty() {
87+
return;
88+
} else if then_search.edits.is_empty() || else_search.edits.is_empty() {
89+
// if .. { insert } else { .. } or if .. { .. } else { insert } of
8890
let (then_str, else_str, entry_kind) = if else_search.edits.is_empty() {
8991
if contains_expr.negated {
9092
(
@@ -184,15 +186,17 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass {
184186
format!("{}.entry({}).or_insert({});", map_str, key_str, value_str)
185187
}
186188
} else {
187-
// Todo: if let Some(v) = map.get_mut(k)
189+
// TODO: suggest using `if let Some(v) = map.get_mut(k) { .. }` here.
190+
// This would need to be a different lint.
188191
return;
189192
}
190193
} else {
191194
let block_str = then_search.snippet_closure(cx, then_expr.span, &mut app);
192195
if contains_expr.negated {
193196
format!("{}.entry({}).or_insert_with(|| {});", map_str, key_str, block_str)
194197
} else {
195-
// Todo: if let Some(v) = map.get_mut(k)
198+
// TODO: suggest using `if let Some(v) = map.get_mut(k) { .. }` here.
199+
// This would need to be a different lint.
196200
return;
197201
}
198202
}
@@ -222,7 +226,7 @@ impl MapType {
222226
Self::BTree => "BTreeMap",
223227
}
224228
}
225-
fn entry_path(self) -> &'staic str {
229+
fn entry_path(self) -> &'static str {
226230
match self {
227231
Self::Hash => "std::collections::hash_map::Entry",
228232
Self::BTree => "std::collections::btree_map::Entry",
@@ -312,15 +316,16 @@ struct Insertion<'tcx> {
312316
value: &'tcx Expr<'tcx>,
313317
}
314318

315-
// This visitor needs to do a multiple things:
316-
// * Find all usages of the map. Only insertions into the map which share the same key are
317-
// permitted. All others will prevent the lint.
318-
// * Determine if the final statement executed is an insertion. This is needed to use `insert_with`.
319-
// * Determine if there's any sub-expression that can't be placed in a closure.
320-
// * Determine if there's only a single insert statement. This is needed to give better suggestions.
321-
319+
/// This visitor needs to do a multiple things:
320+
/// * Find all usages of the map. An insertion can only be made before any other usages of the map.
321+
/// * Determine if there's an insertion using the same key. There's no need for the entry api
322+
/// otherwise.
323+
/// * Determine if the final statement executed is an insertion. This is needed to use
324+
/// `or_insert_with`.
325+
/// * Determine if there's any sub-expression that can't be placed in a closure.
326+
/// * Determine if there's only a single insert statement. `or_insert` can be used in this case.
322327
#[allow(clippy::struct_excessive_bools)]
323-
struct InsertSearcher<'cx, 'i, 'tcx> {
328+
struct InsertSearcher<'cx, 'tcx> {
324329
cx: &'cx LateContext<'tcx>,
325330
/// The map expression used in the contains call.
326331
map: &'tcx Expr<'tcx>,
@@ -334,13 +339,16 @@ struct InsertSearcher<'cx, 'i, 'tcx> {
334339
can_use_entry: bool,
335340
/// Whether this expression is the final expression in this code path. This may be a statement.
336341
in_tail_pos: bool,
337-
// A single insert expression has a slightly different suggestion.
342+
// Is this expression a single insert. A slightly better suggestion can be made in this case.
338343
is_single_insert: bool,
344+
/// If the visitor has seen the map being used.
339345
is_map_used: bool,
340-
edits: &'i mut Vec<Edit<'tcx>>,
346+
/// The locations where changes need to be made for the suggestion.
347+
edits: Vec<Edit<'tcx>>,
348+
/// A stack of loops the visitor is currently in.
341349
loops: Vec<HirId>,
342350
}
343-
impl<'tcx> InsertSearcher<'_, '_, 'tcx> {
351+
impl<'tcx> InsertSearcher<'_, 'tcx> {
344352
/// Visit the expression as a branch in control flow. Multiple insert calls can be used, but
345353
/// only if they are on separate code paths. This will return whether the map was used in the
346354
/// given expression.
@@ -363,7 +371,7 @@ impl<'tcx> InsertSearcher<'_, '_, 'tcx> {
363371
self.in_tail_pos = in_tail_pos;
364372
}
365373
}
366-
impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, '_, 'tcx> {
374+
impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, 'tcx> {
367375
type Map = ErasedMap<'tcx>;
368376
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
369377
NestedVisitorMap::None
@@ -483,6 +491,7 @@ impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, '_, 'tcx> {
483491
},
484492
ExprKind::Loop(block, ..) => {
485493
self.loops.push(expr.hir_id);
494+
self.is_single_insert = false;
486495
self.allow_insert_closure &= !self.in_tail_pos;
487496
// Don't allow insertions inside of a loop.
488497
let edit_len = self.edits.len();
@@ -519,7 +528,13 @@ impl InsertSearchResults<'tcx> {
519528
self.is_single_insert.then(|| self.edits[0].as_insertion().unwrap())
520529
}
521530

522-
fn snippet_occupied(&self, cx: &LateContext<'_>, mut span: Span, app: &mut Applicability) -> String {
531+
fn snippet(
532+
&self,
533+
cx: &LateContext<'_>,
534+
mut span: Span,
535+
app: &mut Applicability,
536+
write_wrapped: impl Fn(&mut String, Insertion<'_>, SyntaxContext, &mut Applicability),
537+
) -> String {
523538
let ctxt = span.ctxt();
524539
let mut res = String::new();
525540
for insertion in self.edits.iter().filter_map(|e| e.as_insertion()) {
@@ -530,56 +545,47 @@ impl InsertSearchResults<'tcx> {
530545
app,
531546
));
532547
if is_expr_used_or_unified(cx.tcx, insertion.call) {
533-
res.push_str("Some(e.insert(");
534-
res.push_str(&snippet_with_context(cx, insertion.value.span, ctxt, "..", app).0);
535-
res.push_str("))");
548+
write_wrapped(&mut res, insertion, ctxt, app);
536549
} else {
537-
res.push_str("e.insert(");
538-
res.push_str(&snippet_with_context(cx, insertion.value.span, ctxt, "..", app).0);
539-
res.push(')');
550+
let _ = write!(
551+
res,
552+
"e.insert({})",
553+
snippet_with_context(cx, insertion.value.span, ctxt, "..", app).0
554+
);
540555
}
541556
span = span.trim_start(insertion.call.span).unwrap_or(DUMMY_SP);
542557
}
543558
res.push_str(&snippet_with_applicability(cx, span, "..", app));
544559
res
545560
}
546561

547-
fn snippet_vacant(&self, cx: &LateContext<'_>, mut span: Span, app: &mut Applicability) -> String {
548-
let ctxt = span.ctxt();
549-
let mut res = String::new();
550-
for insertion in self.edits.iter().filter_map(|e| e.as_insertion()) {
551-
res.push_str(&snippet_with_applicability(
552-
cx,
553-
span.until(insertion.call.span),
554-
"..",
555-
app,
556-
));
557-
if is_expr_used_or_unified(cx.tcx, insertion.call) {
558-
if is_expr_final_block_expr(cx.tcx, insertion.call) {
559-
let _ = write!(
560-
res,
561-
"e.insert({});\n{}None",
562-
snippet_with_context(cx, insertion.value.span, ctxt, "..", app).0,
563-
snippet_indent(cx, insertion.call.span).as_deref().unwrap_or(""),
564-
);
565-
} else {
566-
let _ = write!(
567-
res,
568-
"{{ e.insert({}); None }}",
569-
snippet_with_context(cx, insertion.value.span, ctxt, "..", app).0,
570-
);
571-
}
562+
fn snippet_occupied(&self, cx: &LateContext<'_>, span: Span, app: &mut Applicability) -> String {
563+
self.snippet(cx, span, app, |res, insertion, ctxt, app| {
564+
let _ = write!(
565+
res,
566+
"e.insert({})",
567+
snippet_with_context(cx, insertion.value.span, ctxt, "..", app).0
568+
);
569+
})
570+
}
571+
572+
fn snippet_vacant(&self, cx: &LateContext<'_>, span: Span, app: &mut Applicability) -> String {
573+
self.snippet(cx, span, app, |res, insertion, ctxt, app| {
574+
let _ = if is_expr_final_block_expr(cx.tcx, insertion.call) {
575+
write!(
576+
res,
577+
"e.insert({});\n{}None",
578+
snippet_with_context(cx, insertion.value.span, ctxt, "..", app).0,
579+
snippet_indent(cx, insertion.call.span).as_deref().unwrap_or(""),
580+
)
572581
} else {
573-
let _ = write!(
582+
write!(
574583
res,
575-
"e.insert({})",
584+
"{{ e.insert({}); None }}",
576585
snippet_with_context(cx, insertion.value.span, ctxt, "..", app).0,
577-
);
578-
}
579-
span = span.trim_start(insertion.call.span).unwrap_or(DUMMY_SP);
580-
}
581-
res.push_str(&snippet_with_applicability(cx, span, "..", app));
582-
res
586+
)
587+
};
588+
})
583589
}
584590

585591
fn snippet_closure(&self, cx: &LateContext<'_>, mut span: Span, app: &mut Applicability) -> String {
@@ -607,18 +613,18 @@ impl InsertSearchResults<'tcx> {
607613
res
608614
}
609615
}
616+
610617
fn find_insert_calls(
611618
cx: &LateContext<'tcx>,
612619
contains_expr: &ContainsExpr<'tcx>,
613620
expr: &'tcx Expr<'_>,
614621
) -> Option<InsertSearchResults<'tcx>> {
615-
let mut edits = Vec::new();
616622
let mut s = InsertSearcher {
617623
cx,
618624
map: contains_expr.map,
619625
key: contains_expr.key,
620626
ctxt: expr.span.ctxt(),
621-
edits: &mut edits,
627+
edits: Vec::new(),
622628
is_map_used: false,
623629
allow_insert_closure: true,
624630
can_use_entry: true,
@@ -629,6 +635,7 @@ fn find_insert_calls(
629635
s.visit_expr(expr);
630636
let allow_insert_closure = s.allow_insert_closure;
631637
let is_single_insert = s.is_single_insert;
638+
let edits = s.edits;
632639
s.can_use_entry.then(|| InsertSearchResults {
633640
edits,
634641
allow_insert_closure,

clippy_utils/src/lib.rs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ use rustc_hir::def_id::{DefId, LOCAL_CRATE};
6161
use rustc_hir::intravisit::{self, walk_expr, ErasedMap, NestedVisitorMap, Visitor};
6262
use rustc_hir::{
6363
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,
64+
ImplItem, ImplItemKind, Item, ItemKind, LangItem, Local, MatchSource, Node, Param, Pat, PatKind, Path, PathSegment,
65+
QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitRef, TyKind,
6666
};
6767
use rustc_lint::{LateContext, Level, Lint, LintContext};
6868
use rustc_middle::hir::exports::Export;
@@ -1333,16 +1333,26 @@ pub fn get_expr_use_node(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option<Node<'tcx
13331333
}
13341334
}
13351335

1336+
/// Checks if the result of the expression is used, or just dropped immediately.
13361337
pub fn is_expr_used(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
13371338
!matches!(
13381339
get_expr_use_node(tcx, expr),
13391340
Some(Node::Stmt(Stmt {
1340-
kind: StmtKind::Expr(_) | StmtKind::Semi(_),
1341+
kind: StmtKind::Expr(_)
1342+
| StmtKind::Semi(_)
1343+
| StmtKind::Local(Local {
1344+
pat: Pat {
1345+
kind: PatKind::Wild,
1346+
..
1347+
},
1348+
..
1349+
}),
13411350
..
13421351
}))
13431352
)
13441353
}
13451354

1355+
/// Gets the node where an expression is either used, or it's type is unified with another branch.
13461356
pub fn get_expr_use_or_unification_node(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option<Node<'tcx>> {
13471357
let map = tcx.hir();
13481358
let mut child_id = expr.hir_id;
@@ -1363,6 +1373,7 @@ pub fn get_expr_use_or_unification_node(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> O
13631373
}
13641374
}
13651375

1376+
/// Checks if the result of an expression is used, or it's type is unified with another branch.
13661377
pub fn is_expr_used_or_unified(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
13671378
!matches!(
13681379
get_expr_use_or_unification_node(tcx, expr),
@@ -1373,6 +1384,7 @@ pub fn is_expr_used_or_unified(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
13731384
)
13741385
}
13751386

1387+
/// Checks if the expression is the final expression returned from a block.
13761388
pub fn is_expr_final_block_expr(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
13771389
matches!(get_parent_node(tcx, expr.hir_id), Some(Node::Block(..)))
13781390
}

0 commit comments

Comments
 (0)