Skip to content

Commit c57d8ae

Browse files
committed
Auto merge of rust-lang#6227 - HMPerson1:collect_map, r=phansch
Add lint for replacing `.map().collect()` with `.try_for_each()` Fixes rust-lang#6208 changelog: Add `map_collect_result_unit`
2 parents e1a2845 + f0cf3bd commit c57d8ae

File tree

7 files changed

+118
-0
lines changed

7 files changed

+118
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1803,6 +1803,7 @@ Released 2018-09-13
18031803
[`manual_unwrap_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_unwrap_or
18041804
[`many_single_char_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#many_single_char_names
18051805
[`map_clone`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_clone
1806+
[`map_collect_result_unit`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_collect_result_unit
18061807
[`map_entry`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_entry
18071808
[`map_err_ignore`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_err_ignore
18081809
[`map_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_flatten

clippy_lints/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -702,6 +702,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
702702
&methods::ITER_NTH_ZERO,
703703
&methods::ITER_SKIP_NEXT,
704704
&methods::MANUAL_SATURATING_ARITHMETIC,
705+
&methods::MAP_COLLECT_RESULT_UNIT,
705706
&methods::MAP_FLATTEN,
706707
&methods::MAP_UNWRAP_OR,
707708
&methods::NEW_RET_NO_SELF,
@@ -1428,6 +1429,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
14281429
LintId::of(&methods::ITER_NTH_ZERO),
14291430
LintId::of(&methods::ITER_SKIP_NEXT),
14301431
LintId::of(&methods::MANUAL_SATURATING_ARITHMETIC),
1432+
LintId::of(&methods::MAP_COLLECT_RESULT_UNIT),
14311433
LintId::of(&methods::NEW_RET_NO_SELF),
14321434
LintId::of(&methods::OK_EXPECT),
14331435
LintId::of(&methods::OPTION_AS_REF_DEREF),
@@ -1623,6 +1625,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
16231625
LintId::of(&methods::ITER_NTH_ZERO),
16241626
LintId::of(&methods::ITER_SKIP_NEXT),
16251627
LintId::of(&methods::MANUAL_SATURATING_ARITHMETIC),
1628+
LintId::of(&methods::MAP_COLLECT_RESULT_UNIT),
16261629
LintId::of(&methods::NEW_RET_NO_SELF),
16271630
LintId::of(&methods::OK_EXPECT),
16281631
LintId::of(&methods::OPTION_MAP_OR_NONE),

clippy_lints/src/methods/mod.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1349,6 +1349,27 @@ declare_clippy_lint! {
13491349
"using unnecessary lazy evaluation, which can be replaced with simpler eager evaluation"
13501350
}
13511351

1352+
declare_clippy_lint! {
1353+
/// **What it does:** Checks for usage of `_.map(_).collect::<Result<(),_>()`.
1354+
///
1355+
/// **Why is this bad?** Using `try_for_each` instead is more readable and idiomatic.
1356+
///
1357+
/// **Known problems:** None
1358+
///
1359+
/// **Example:**
1360+
///
1361+
/// ```rust
1362+
/// (0..3).map(|t| Err(t)).collect::<Result<(), _>>();
1363+
/// ```
1364+
/// Use instead:
1365+
/// ```rust
1366+
/// (0..3).try_for_each(|t| Err(t));
1367+
/// ```
1368+
pub MAP_COLLECT_RESULT_UNIT,
1369+
style,
1370+
"using `.map(_).collect::<Result<(),_>()`, which can be replaced with `try_for_each`"
1371+
}
1372+
13521373
declare_lint_pass!(Methods => [
13531374
UNWRAP_USED,
13541375
EXPECT_USED,
@@ -1398,6 +1419,7 @@ declare_lint_pass!(Methods => [
13981419
FILETYPE_IS_FILE,
13991420
OPTION_AS_REF_DEREF,
14001421
UNNECESSARY_LAZY_EVALUATIONS,
1422+
MAP_COLLECT_RESULT_UNIT,
14011423
]);
14021424

14031425
impl<'tcx> LateLintPass<'tcx> for Methods {
@@ -1479,6 +1501,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
14791501
["unwrap_or_else", ..] => unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], "unwrap_or"),
14801502
["get_or_insert_with", ..] => unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], "get_or_insert"),
14811503
["ok_or_else", ..] => unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], "ok_or"),
1504+
["collect", "map"] => lint_map_collect(cx, expr, arg_lists[1], arg_lists[0]),
14821505
_ => {},
14831506
}
14841507

@@ -3445,6 +3468,42 @@ fn lint_option_as_ref_deref<'tcx>(
34453468
}
34463469
}
34473470

3471+
fn lint_map_collect(
3472+
cx: &LateContext<'_>,
3473+
expr: &hir::Expr<'_>,
3474+
map_args: &[hir::Expr<'_>],
3475+
collect_args: &[hir::Expr<'_>],
3476+
) {
3477+
if_chain! {
3478+
// called on Iterator
3479+
if let [map_expr] = collect_args;
3480+
if match_trait_method(cx, map_expr, &paths::ITERATOR);
3481+
// return of collect `Result<(),_>`
3482+
let collect_ret_ty = cx.typeck_results().expr_ty(expr);
3483+
if is_type_diagnostic_item(cx, collect_ret_ty, sym!(result_type));
3484+
if let ty::Adt(_, substs) = collect_ret_ty.kind();
3485+
if let Some(result_t) = substs.types().next();
3486+
if result_t.is_unit();
3487+
// get parts for snippet
3488+
if let [iter, map_fn] = map_args;
3489+
then {
3490+
span_lint_and_sugg(
3491+
cx,
3492+
MAP_COLLECT_RESULT_UNIT,
3493+
expr.span,
3494+
"`.map().collect()` can be replaced with `.try_for_each()`",
3495+
"try this",
3496+
format!(
3497+
"{}.try_for_each({})",
3498+
snippet(cx, iter.span, ".."),
3499+
snippet(cx, map_fn.span, "..")
3500+
),
3501+
Applicability::MachineApplicable,
3502+
);
3503+
}
3504+
}
3505+
}
3506+
34483507
/// Given a `Result<T, E>` type, return its error type (`E`).
34493508
fn get_error_type<'a>(cx: &LateContext<'_>, ty: Ty<'a>) -> Option<Ty<'a>> {
34503509
match ty.kind() {

src/lintlist/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1229,6 +1229,13 @@ vec![
12291229
deprecation: None,
12301230
module: "map_clone",
12311231
},
1232+
Lint {
1233+
name: "map_collect_result_unit",
1234+
group: "style",
1235+
desc: "using `.map(_).collect::<Result<(),_>()`, which can be replaced with `try_for_each`",
1236+
deprecation: None,
1237+
module: "methods",
1238+
},
12321239
Lint {
12331240
name: "map_entry",
12341241
group: "perf",
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// run-rustfix
2+
#![warn(clippy::map_collect_result_unit)]
3+
4+
fn main() {
5+
{
6+
let _ = (0..3).try_for_each(|t| Err(t + 1));
7+
let _: Result<(), _> = (0..3).try_for_each(|t| Err(t + 1));
8+
9+
let _ = (0..3).try_for_each(|t| Err(t + 1));
10+
}
11+
}
12+
13+
fn _ignore() {
14+
let _ = (0..3).map(|t| Err(t + 1)).collect::<Result<Vec<i32>, _>>();
15+
let _ = (0..3).map(|t| Err(t + 1)).collect::<Vec<Result<(), _>>>();
16+
}

tests/ui/map_collect_result_unit.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// run-rustfix
2+
#![warn(clippy::map_collect_result_unit)]
3+
4+
fn main() {
5+
{
6+
let _ = (0..3).map(|t| Err(t + 1)).collect::<Result<(), _>>();
7+
let _: Result<(), _> = (0..3).map(|t| Err(t + 1)).collect();
8+
9+
let _ = (0..3).try_for_each(|t| Err(t + 1));
10+
}
11+
}
12+
13+
fn _ignore() {
14+
let _ = (0..3).map(|t| Err(t + 1)).collect::<Result<Vec<i32>, _>>();
15+
let _ = (0..3).map(|t| Err(t + 1)).collect::<Vec<Result<(), _>>>();
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
error: `.map().collect()` can be replaced with `.try_for_each()`
2+
--> $DIR/map_collect_result_unit.rs:6:17
3+
|
4+
LL | let _ = (0..3).map(|t| Err(t + 1)).collect::<Result<(), _>>();
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `(0..3).try_for_each(|t| Err(t + 1))`
6+
|
7+
= note: `-D clippy::map-collect-result-unit` implied by `-D warnings`
8+
9+
error: `.map().collect()` can be replaced with `.try_for_each()`
10+
--> $DIR/map_collect_result_unit.rs:7:32
11+
|
12+
LL | let _: Result<(), _> = (0..3).map(|t| Err(t + 1)).collect();
13+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `(0..3).try_for_each(|t| Err(t + 1))`
14+
15+
error: aborting due to 2 previous errors
16+

0 commit comments

Comments
 (0)