Skip to content

Commit a0c4eca

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

File tree

9 files changed

+207
-185
lines changed

9 files changed

+207
-185
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+
"Some(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: 14 additions & 45 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;
@@ -1301,48 +1301,7 @@ pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
13011301
did.map_or(false, |did| must_use_attr(&cx.tcx.get_attrs(did)).is_some())
13021302
}
13031303

1304-
pub fn get_expr_use_node(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option<Node<'tcx>> {
1305-
let map = tcx.hir();
1306-
let mut child_id = expr.hir_id;
1307-
let mut iter = map.parent_iter(child_id);
1308-
loop {
1309-
match iter.next() {
1310-
None => break None,
1311-
Some((id, Node::Block(_))) => child_id = id,
1312-
Some((id, Node::Arm(arm))) if arm.body.hir_id == child_id => child_id = id,
1313-
Some((_, Node::Expr(expr))) => match expr.kind {
1314-
ExprKind::Break(
1315-
Destination {
1316-
target_id: Ok(dest), ..
1317-
},
1318-
_,
1319-
) => {
1320-
iter = map.parent_iter(dest);
1321-
child_id = dest;
1322-
},
1323-
ExprKind::DropTemps(_) | ExprKind::Block(..) => child_id = expr.hir_id,
1324-
ExprKind::If(control_expr, ..) | ExprKind::Match(control_expr, ..)
1325-
if control_expr.hir_id != child_id =>
1326-
{
1327-
child_id = expr.hir_id
1328-
},
1329-
_ => break Some(Node::Expr(expr)),
1330-
},
1331-
Some((_, node)) => break Some(node),
1332-
}
1333-
}
1334-
}
1335-
1336-
pub fn is_expr_used(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
1337-
!matches!(
1338-
get_expr_use_node(tcx, expr),
1339-
Some(Node::Stmt(Stmt {
1340-
kind: StmtKind::Expr(_) | StmtKind::Semi(_),
1341-
..
1342-
}))
1343-
)
1344-
}
1345-
1304+
/// Gets the node where an expression is either used, or it's type is unified with another branch.
13461305
pub fn get_expr_use_or_unification_node(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option<Node<'tcx>> {
13471306
let map = tcx.hir();
13481307
let mut child_id = expr.hir_id;
@@ -1363,16 +1322,26 @@ pub fn get_expr_use_or_unification_node(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> O
13631322
}
13641323
}
13651324

1325+
/// Checks if the result of an expression is used, or it's type is unified with another branch.
13661326
pub fn is_expr_used_or_unified(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
13671327
!matches!(
13681328
get_expr_use_or_unification_node(tcx, expr),
13691329
None | Some(Node::Stmt(Stmt {
1370-
kind: StmtKind::Expr(_) | StmtKind::Semi(_),
1330+
kind: StmtKind::Expr(_)
1331+
| StmtKind::Semi(_)
1332+
| StmtKind::Local(Local {
1333+
pat: Pat {
1334+
kind: PatKind::Wild,
1335+
..
1336+
},
1337+
..
1338+
}),
13711339
..
13721340
}))
13731341
)
13741342
}
13751343

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

0 commit comments

Comments
 (0)