Skip to content

Commit 4c1d05c

Browse files
committed
Auto merge of #12362 - Ethiraric:fix-11935, r=llogiq
[`map_entry`]: Check insert expression for map use The lint makes sure that the map is not used (borrowed) before the call to `insert`. Since the lint creates a mutable borrow on the map with the `Entry`, it wouldn't be possible to replace such code with `Entry`. However, expressions up to the `insert` call are checked, but not expressions for the arguments of the `insert` call itself. This commit fixes that. Fixes #11935 ---- changelog: [`map_entry`]: Fix false positive when borrowing the map in the `insert` call
2 parents 4155bec + 03bb790 commit 4c1d05c

File tree

3 files changed

+75
-2
lines changed

3 files changed

+75
-2
lines changed

clippy_lints/src/entry.rs

+53-2
Original file line numberDiff line numberDiff line change
@@ -214,12 +214,31 @@ impl MapType {
214214
}
215215
}
216216

217+
/// Details on an expression checking whether a map contains a key.
218+
///
219+
/// For instance, with the following:
220+
/// ```ignore
221+
/// !!!self.the_map.contains_key("the_key")
222+
/// ```
223+
///
224+
/// - `negated` will be set to `true` (the 3 `!` negate the condition)
225+
/// - `map` will be the `self.the_map` expression
226+
/// - `key` will be the `"the_key"` expression
217227
struct ContainsExpr<'tcx> {
228+
/// Whether the check for `contains_key` was negated.
218229
negated: bool,
230+
/// The map on which the check is performed.
219231
map: &'tcx Expr<'tcx>,
232+
/// The key that is checked to be contained.
220233
key: &'tcx Expr<'tcx>,
234+
/// The context of the whole condition expression.
221235
call_ctxt: SyntaxContext,
222236
}
237+
238+
/// Inspect the given expression and return details about the `contains_key` check.
239+
///
240+
/// If the given expression is not a `contains_key` check against a `BTreeMap` or a `HashMap`,
241+
/// return `None`.
223242
fn try_parse_contains<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> Option<(MapType, ContainsExpr<'tcx>)> {
224243
let mut negated = false;
225244
let expr = peel_hir_expr_while(expr, |e| match e.kind {
@@ -229,6 +248,7 @@ fn try_parse_contains<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> Optio
229248
},
230249
_ => None,
231250
});
251+
232252
match expr.kind {
233253
ExprKind::MethodCall(
234254
_,
@@ -261,11 +281,28 @@ fn try_parse_contains<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> Optio
261281
}
262282
}
263283

284+
/// Details on an expression inserting a key into a map.
285+
///
286+
/// For instance, on the following:
287+
/// ```ignore
288+
/// self.the_map.insert("the_key", 3 + 4);
289+
/// ```
290+
///
291+
/// - `map` will be the `self.the_map` expression
292+
/// - `key` will be the `"the_key"` expression
293+
/// - `value` will be the `3 + 4` expression
264294
struct InsertExpr<'tcx> {
295+
/// The map into which the insertion is performed.
265296
map: &'tcx Expr<'tcx>,
297+
/// The key at which to insert.
266298
key: &'tcx Expr<'tcx>,
299+
/// The value to insert.
267300
value: &'tcx Expr<'tcx>,
268301
}
302+
303+
/// Inspect the given expression and return details about the `insert` call.
304+
///
305+
/// If the given expression is not an `insert` call into a `BTreeMap` or a `HashMap`, return `None`.
269306
fn try_parse_insert<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<InsertExpr<'tcx>> {
270307
if let ExprKind::MethodCall(_, map, [key, value], _) = expr.kind {
271308
let id = cx.typeck_results().type_dependent_def_id(expr.hir_id)?;
@@ -298,7 +335,7 @@ struct Insertion<'tcx> {
298335
value: &'tcx Expr<'tcx>,
299336
}
300337

301-
/// This visitor needs to do a multiple things:
338+
/// This visitor needs to do multiple things:
302339
/// * Find all usages of the map. An insertion can only be made before any other usages of the map.
303340
/// * Determine if there's an insertion using the same key. There's no need for the entry api
304341
/// otherwise.
@@ -346,14 +383,27 @@ impl<'tcx> InsertSearcher<'_, 'tcx> {
346383
res
347384
}
348385

349-
/// Visits an expression which is not itself in a tail position, but other sibling expressions
386+
/// Visit an expression which is not itself in a tail position, but other sibling expressions
350387
/// may be. e.g. if conditions
351388
fn visit_non_tail_expr(&mut self, e: &'tcx Expr<'_>) {
352389
let in_tail_pos = self.in_tail_pos;
353390
self.in_tail_pos = false;
354391
self.visit_expr(e);
355392
self.in_tail_pos = in_tail_pos;
356393
}
394+
395+
/// Visit the key and value expression of an insert expression.
396+
/// There may not be uses of the map in either of those two either.
397+
fn visit_insert_expr_arguments(&mut self, e: &InsertExpr<'tcx>) {
398+
let in_tail_pos = self.in_tail_pos;
399+
let allow_insert_closure = self.allow_insert_closure;
400+
let is_single_insert = self.is_single_insert;
401+
walk_expr(self, e.key);
402+
walk_expr(self, e.value);
403+
self.in_tail_pos = in_tail_pos;
404+
self.allow_insert_closure = allow_insert_closure;
405+
self.is_single_insert = is_single_insert;
406+
}
357407
}
358408
impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, 'tcx> {
359409
fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) {
@@ -425,6 +475,7 @@ impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, 'tcx> {
425475

426476
match try_parse_insert(self.cx, expr) {
427477
Some(insert_expr) if SpanlessEq::new(self.cx).eq_expr(self.map, insert_expr.map) => {
478+
self.visit_insert_expr_arguments(&insert_expr);
428479
// Multiple inserts, inserts with a different key, and inserts from a macro can't use the entry api.
429480
if self.is_map_used
430481
|| !SpanlessEq::new(self.cx).eq_expr(self.key, insert_expr.key)

tests/ui/entry.fixed

+11
Original file line numberDiff line numberDiff line change
@@ -165,4 +165,15 @@ pub fn issue_10331() {
165165
}
166166
}
167167

168+
/// Issue 11935
169+
/// Do not suggest using entries if the map is used inside the `insert` expression.
170+
pub fn issue_11935() {
171+
let mut counts: HashMap<u64, u64> = HashMap::new();
172+
if !counts.contains_key(&1) {
173+
counts.insert(1, 1);
174+
} else {
175+
counts.insert(1, counts.get(&1).unwrap() + 1);
176+
}
177+
}
178+
168179
fn main() {}

tests/ui/entry.rs

+11
Original file line numberDiff line numberDiff line change
@@ -169,4 +169,15 @@ pub fn issue_10331() {
169169
}
170170
}
171171

172+
/// Issue 11935
173+
/// Do not suggest using entries if the map is used inside the `insert` expression.
174+
pub fn issue_11935() {
175+
let mut counts: HashMap<u64, u64> = HashMap::new();
176+
if !counts.contains_key(&1) {
177+
counts.insert(1, 1);
178+
} else {
179+
counts.insert(1, counts.get(&1).unwrap() + 1);
180+
}
181+
}
182+
172183
fn main() {}

0 commit comments

Comments
 (0)