Skip to content

Commit aee2279

Browse files
committed
Add entry_or_insert_with_default lint
1 parent 83bb5ec commit aee2279

File tree

6 files changed

+144
-0
lines changed

6 files changed

+144
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1694,6 +1694,7 @@ Released 2018-09-13
16941694
[`empty_enum`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_enum
16951695
[`empty_line_after_outer_attr`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_line_after_outer_attr
16961696
[`empty_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_loop
1697+
[`entry_or_insert_with_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#entry_or_insert_with_default
16971698
[`enum_clike_unportable_variant`]: https://rust-lang.github.io/rust-clippy/master/index.html#enum_clike_unportable_variant
16981699
[`enum_glob_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#enum_glob_use
16991700
[`enum_variant_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#enum_variant_names

clippy_lints/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -679,6 +679,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
679679
&methods::CLONE_DOUBLE_REF,
680680
&methods::CLONE_ON_COPY,
681681
&methods::CLONE_ON_REF_PTR,
682+
&methods::ENTRY_OR_INSERT_WITH_DEFAULT,
682683
&methods::EXPECT_FUN_CALL,
683684
&methods::EXPECT_USED,
684685
&methods::FILETYPE_IS_FILE,
@@ -1409,6 +1410,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
14091410
LintId::of(&methods::CHARS_NEXT_CMP),
14101411
LintId::of(&methods::CLONE_DOUBLE_REF),
14111412
LintId::of(&methods::CLONE_ON_COPY),
1413+
LintId::of(&methods::ENTRY_OR_INSERT_WITH_DEFAULT),
14121414
LintId::of(&methods::EXPECT_FUN_CALL),
14131415
LintId::of(&methods::FILTER_NEXT),
14141416
LintId::of(&methods::FLAT_MAP_IDENTITY),
@@ -1609,6 +1611,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
16091611
LintId::of(&mem_replace::MEM_REPLACE_WITH_DEFAULT),
16101612
LintId::of(&methods::CHARS_LAST_CMP),
16111613
LintId::of(&methods::CHARS_NEXT_CMP),
1614+
LintId::of(&methods::ENTRY_OR_INSERT_WITH_DEFAULT),
16121615
LintId::of(&methods::INTO_ITER_ON_REF),
16131616
LintId::of(&methods::ITER_CLONED_COLLECT),
16141617
LintId::of(&methods::ITER_NEXT_SLICE),

clippy_lints/src/methods/mod.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,42 @@ declare_clippy_lint! {
563563
"using `.chars().next()` to check if a string starts with a char"
564564
}
565565

566+
declare_clippy_lint! {
567+
/// **What it does:** Checks for calls to `.or_insert_with` on `Entry`
568+
/// (for `BTreeMap` or `HashMap`) with `Default::default` or equivalent
569+
/// as the argument, and suggests `.or_default`.
570+
///
571+
/// **Why is this bad?** There is a specific method for this, and using it can make
572+
/// the code cleaner.
573+
///
574+
/// **Known problems:** None.
575+
///
576+
/// **Example:**
577+
///
578+
/// ```rust
579+
/// fn btree(map: &mut BTreeMap<i32, i32>) -> i32 {
580+
/// map.entry(42).or_insert_with(Default::default)
581+
/// }
582+
///
583+
/// fn hash(map: &mut HashMap<i32, i32>) -> i32 {
584+
/// map.entry(42).or_insert_with(i32::default)
585+
/// }
586+
/// ```
587+
/// both can be instead written as
588+
/// ```rust
589+
/// fn btree(map: &mut BTreeMap<i32, i32>) -> i32 {
590+
/// map.entry(42).or_default()
591+
/// }
592+
///
593+
/// fn hash(map: &mut HashMap<i32, i32>) -> i32 {
594+
/// map.entry(42).or_default()
595+
/// }
596+
/// ```
597+
pub ENTRY_OR_INSERT_WITH_DEFAULT,
598+
style,
599+
"calling `or_insert_with` on an `Entry` with `Default::default`"
600+
}
601+
566602
declare_clippy_lint! {
567603
/// **What it does:** Checks for calls to `.or(foo(..))`, `.unwrap_or(foo(..))`,
568604
/// etc., and suggests to use `or_else`, `unwrap_or_else`, etc., or
@@ -1394,6 +1430,7 @@ declare_lint_pass!(Methods => [
13941430
RESULT_MAP_OR_INTO_OPTION,
13951431
OPTION_MAP_OR_NONE,
13961432
BIND_INSTEAD_OF_MAP,
1433+
ENTRY_OR_INSERT_WITH_DEFAULT,
13971434
OR_FUN_CALL,
13981435
EXPECT_FUN_CALL,
13991436
CHARS_NEXT_CMP,
@@ -1515,6 +1552,9 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
15151552
["unwrap_or_else", ..] => unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], "unwrap_or"),
15161553
["get_or_insert_with", ..] => unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], "get_or_insert"),
15171554
["ok_or_else", ..] => unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], "ok_or"),
1555+
["or_insert_with", ..] => {
1556+
lint_entry_or_insert_with_default(cx, method_spans[0].with_hi(expr.span.hi()), arg_lists[0])
1557+
},
15181558
_ => {},
15191559
}
15201560

@@ -1709,6 +1749,28 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
17091749
}
17101750
}
17111751

1752+
/// Checks for the `ENTRY_OR_INSERT_WITH_DEFAULT` lint.
1753+
fn lint_entry_or_insert_with_default(cx: &LateContext<'tcx>, span: Span, args: &'tcx [hir::Expr<'_>]) {
1754+
if_chain! {
1755+
let ty = cx.typeck_results().expr_ty(&args[0]);
1756+
if match_type(cx, ty, &paths::BTREEMAP_ENTRY) || match_type(cx, ty, &paths::HASHMAP_ENTRY);
1757+
if let hir::ExprKind::Path(arg_path) = &args[1].kind;
1758+
if let hir::def::Res::Def(_, arg_def_id) = cx.qpath_res(&arg_path, args[1].hir_id);
1759+
if match_def_path(cx, arg_def_id, &paths::DEFAULT_TRAIT_METHOD);
1760+
then {
1761+
span_lint_and_sugg(
1762+
cx,
1763+
ENTRY_OR_INSERT_WITH_DEFAULT,
1764+
span,
1765+
"use of `or_insert_with` with `Default::default`",
1766+
"replace with",
1767+
"or_default()".to_owned(),
1768+
Applicability::MachineApplicable,
1769+
);
1770+
}
1771+
}
1772+
}
1773+
17121774
/// Checks for the `OR_FUN_CALL` lint.
17131775
#[allow(clippy::too_many_lines)]
17141776
fn lint_or_fun_call<'tcx>(

src/lintlist/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,13 @@ vec![
494494
deprecation: None,
495495
module: "loops",
496496
},
497+
Lint {
498+
name: "entry_or_insert_with_default",
499+
group: "style",
500+
desc: "calling `or_insert_with` on an `Entry` with `Default::default`",
501+
deprecation: None,
502+
module: "methods",
503+
},
497504
Lint {
498505
name: "enum_clike_unportable_variant",
499506
group: "correctness",
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#![warn(clippy::entry_or_insert_with_default)]
2+
3+
use std::collections::{BTreeMap, HashMap};
4+
5+
fn btree(map: &mut BTreeMap<i32, i32>) {
6+
map.entry(42).or_insert_with(Default::default);
7+
map.entry(42).or_insert_with(i32::default);
8+
map.entry(42).or_default();
9+
}
10+
11+
fn hash(map: &mut HashMap<i32, i32>) {
12+
map.entry(42).or_insert_with(Default::default);
13+
map.entry(42).or_insert_with(i32::default);
14+
map.entry(42).or_default();
15+
}
16+
17+
struct InformalDefault;
18+
19+
impl InformalDefault {
20+
fn default() -> Self {
21+
InformalDefault
22+
}
23+
}
24+
25+
fn not_default_btree(map: &mut BTreeMap<i32, InformalDefault>) {
26+
map.entry(42).or_insert_with(InformalDefault::default);
27+
}
28+
29+
fn not_default_hash(map: &mut HashMap<i32, InformalDefault>) {
30+
map.entry(42).or_insert_with(InformalDefault::default);
31+
}
32+
33+
#[derive(Default)]
34+
struct NotAnEntry;
35+
36+
impl NotAnEntry {
37+
fn or_insert_with(self, f: impl Fn() -> Self) {}
38+
}
39+
40+
fn main() {
41+
NotAnEntry.or_insert_with(Default::default);
42+
NotAnEntry.or_insert_with(NotAnEntry::default);
43+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
error: use of `or_insert_with` with `Default::default`
2+
--> $DIR/entry_or_insert_with_default.rs:6:19
3+
|
4+
LL | map.entry(42).or_insert_with(Default::default);
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `or_default()`
6+
|
7+
= note: `-D clippy::entry-or-insert-with-default` implied by `-D warnings`
8+
9+
error: use of `or_insert_with` with `Default::default`
10+
--> $DIR/entry_or_insert_with_default.rs:7:19
11+
|
12+
LL | map.entry(42).or_insert_with(i32::default);
13+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `or_default()`
14+
15+
error: use of `or_insert_with` with `Default::default`
16+
--> $DIR/entry_or_insert_with_default.rs:12:19
17+
|
18+
LL | map.entry(42).or_insert_with(Default::default);
19+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `or_default()`
20+
21+
error: use of `or_insert_with` with `Default::default`
22+
--> $DIR/entry_or_insert_with_default.rs:13:19
23+
|
24+
LL | map.entry(42).or_insert_with(i32::default);
25+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `or_default()`
26+
27+
error: aborting due to 4 previous errors
28+

0 commit comments

Comments
 (0)