Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 13 additions & 14 deletions clippy_lints/src/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use rustc_hir::hir_id::HirIdSet;
use rustc_hir::intravisit::{Visitor, walk_body, walk_expr};
use rustc_hir::{Block, Expr, ExprKind, HirId, Pat, Stmt, StmtKind, UnOp};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::IsSuggestable;
use rustc_session::declare_lint_pass;
use rustc_span::{DUMMY_SP, Span, SyntaxContext, sym};
use std::fmt;
Expand All @@ -25,17 +26,6 @@ declare_clippy_lint! {
/// ### Why is this bad?
/// Using `entry` is more efficient.
///
/// ### Known problems
/// The suggestion may have type inference errors in some cases. e.g.
/// ```no_run
/// let mut map = std::collections::HashMap::new();
/// let _ = if !map.contains_key(&0) {
/// map.insert(0, 0)
/// } else {
/// None
/// };
/// ```
///
/// ### Example
/// ```no_run
/// # use std::collections::HashMap;
Expand Down Expand Up @@ -653,15 +643,24 @@ impl<'tcx> InsertSearchResults<'tcx> {
(
self.snippet(cx, span, app, |res, insertion, ctxt, app| {
// Insertion into a map would return `None`, but the entry returns a mutable reference.
let value_str = snippet_with_context(cx, insertion.value.span, ctxt, "..", app).0;
let value = insertion.value;
let value_str = snippet_with_context(cx, value.span, ctxt, "..", app).0;

let value_ty = cx.typeck_results().expr_ty(value);
debug_assert!(
value_ty.is_suggestable(cx.tcx, true),
"an unsuggestable type used as the element type:{value_ty:#?}\nexpr={value:#?}"
);
let none_str = format_args!("None::<{value_ty}>");

let _: fmt::Result = if is_expr_final_block_expr(cx.tcx, insertion.call) {
write!(
res,
"e.insert({value_str});\n{indent}None",
"e.insert({value_str});\n{indent}{none_str}",
indent = snippet_indent(cx, insertion.call.span).as_deref().unwrap_or(""),
)
} else {
write!(res, "{{ e.insert({value_str}); None }}")
write!(res, "{{ e.insert({value_str}); {none_str} }}")
};
}),
"Vacant(e)",
Expand Down
7 changes: 7 additions & 0 deletions tests/ui/entry.fixed
Original file line number Diff line number Diff line change
Expand Up @@ -248,4 +248,11 @@ mod issue14449 {
}
}

fn issue15307<K: Eq + Hash, V>(mut m: HashMap<K, V>, k: K, v: V) {
if let std::collections::hash_map::Entry::Vacant(e) = m.entry(k) {
//~^ map_entry
assert!({ e.insert(v); None::<V> }.is_none());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is so ugly.

If the user is using the result of m.insert(…) which we know to be None (as opposed to dropping it), can't we just suggest the same thing as we currently do, but add that m.insert(…) would always have returned None, and suggest to rewrite the code to not depend on this value or to add an explicit type to None if needed?

I dislike suggesting a type from a rustc_middle::ty::Ty when it will not be what the user would write when aliases are involved for example.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More generally, I don't like us generating fixes that we know to be of a very bad style just to make it compatible with the surrounding code. In this case, we might as well warn about the performance issue, but not suggest an autofix.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implementing this required some substantial changes, so I opened a v2 of the PR: #15808

}
}

fn main() {}
7 changes: 7 additions & 0 deletions tests/ui/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,4 +254,11 @@ mod issue14449 {
}
}

fn issue15307<K: Eq + Hash, V>(mut m: HashMap<K, V>, k: K, v: V) {
if !m.contains_key(&k) {
//~^ map_entry
assert!(m.insert(k, v).is_none());
}
}

fn main() {}
19 changes: 18 additions & 1 deletion tests/ui/entry.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -246,5 +246,22 @@ LL + e.insert(42);
LL + }
|

error: aborting due to 11 previous errors
error: usage of `contains_key` followed by `insert` on a `HashMap`
--> tests/ui/entry.rs:258:5
|
LL | / if !m.contains_key(&k) {
LL | |
LL | | assert!(m.insert(k, v).is_none());
LL | | }
| |_____^
|
help: try
|
LL ~ if let std::collections::hash_map::Entry::Vacant(e) = m.entry(k) {
LL +
LL + assert!({ e.insert(v); None::<V> }.is_none());
LL + }
|

error: aborting due to 12 previous errors

11 changes: 10 additions & 1 deletion tests/ui/entry_with_else.fixed
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ fn insert_if_absent0<K: Eq + Hash + Copy, V: Copy>(m: &mut HashMap<K, V>, k: K,
}
std::collections::hash_map::Entry::Vacant(e) => {
e.insert(v);
None
None::<V>
}
};

Expand All @@ -73,6 +73,15 @@ fn insert_if_absent0<K: Eq + Hash + Copy, V: Copy>(m: &mut HashMap<K, V>, k: K,
} else {
None
};

// used to result in type ambiguity before #15759
let _ = if let std::collections::hash_map::Entry::Vacant(e) = m.entry(k) {
//~^ map_entry
e.insert(v);
None::<V>
} else {
None
};
}

fn main() {}
8 changes: 8 additions & 0 deletions tests/ui/entry_with_else.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,14 @@ fn insert_if_absent0<K: Eq + Hash + Copy, V: Copy>(m: &mut HashMap<K, V>, k: K,
} else {
None
};

// used to result in type ambiguity before #15759
let _ = if !m.contains_key(&k) {
//~^ map_entry
m.insert(k, v)
} else {
None
};
}

fn main() {}
27 changes: 25 additions & 2 deletions tests/ui/entry_with_else.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ LL + if true { Some(e.insert(v)) } else { Some(e.insert(v2)) }
LL + }
LL + std::collections::hash_map::Entry::Vacant(e) => {
LL + e.insert(v);
LL + None
LL + None::<V>
LL + }
LL ~ };
|
Expand Down Expand Up @@ -162,5 +162,28 @@ LL + None
LL ~ };
|

error: aborting due to 7 previous errors
error: usage of `contains_key` followed by `insert` on a `HashMap`
--> tests/ui/entry_with_else.rs:65:13
|
LL | let _ = if !m.contains_key(&k) {
| _____________^
LL | |
LL | | m.insert(k, v)
LL | | } else {
LL | | None
LL | | };
| |_____^
|
help: try
|
LL ~ let _ = if let std::collections::hash_map::Entry::Vacant(e) = m.entry(k) {
LL +
LL + e.insert(v);
LL + None::<V>
LL + } else {
LL + None
LL ~ };
|

error: aborting due to 8 previous errors