Skip to content

Commit f0cf3bd

Browse files
committed
Add lint for replacing .map().collect() with .try_for_each()
1 parent afbac89 commit f0cf3bd

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
@@ -1802,6 +1802,7 @@ Released 2018-09-13
18021802
[`manual_unwrap_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_unwrap_or
18031803
[`many_single_char_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#many_single_char_names
18041804
[`map_clone`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_clone
1805+
[`map_collect_result_unit`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_collect_result_unit
18051806
[`map_entry`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_entry
18061807
[`map_err_ignore`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_err_ignore
18071808
[`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
@@ -697,6 +697,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
697697
&methods::ITER_NTH_ZERO,
698698
&methods::ITER_SKIP_NEXT,
699699
&methods::MANUAL_SATURATING_ARITHMETIC,
700+
&methods::MAP_COLLECT_RESULT_UNIT,
700701
&methods::MAP_FLATTEN,
701702
&methods::MAP_UNWRAP_OR,
702703
&methods::NEW_RET_NO_SELF,
@@ -1420,6 +1421,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
14201421
LintId::of(&methods::ITER_NTH_ZERO),
14211422
LintId::of(&methods::ITER_SKIP_NEXT),
14221423
LintId::of(&methods::MANUAL_SATURATING_ARITHMETIC),
1424+
LintId::of(&methods::MAP_COLLECT_RESULT_UNIT),
14231425
LintId::of(&methods::NEW_RET_NO_SELF),
14241426
LintId::of(&methods::OK_EXPECT),
14251427
LintId::of(&methods::OPTION_AS_REF_DEREF),
@@ -1615,6 +1617,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
16151617
LintId::of(&methods::ITER_NTH_ZERO),
16161618
LintId::of(&methods::ITER_SKIP_NEXT),
16171619
LintId::of(&methods::MANUAL_SATURATING_ARITHMETIC),
1620+
LintId::of(&methods::MAP_COLLECT_RESULT_UNIT),
16181621
LintId::of(&methods::NEW_RET_NO_SELF),
16191622
LintId::of(&methods::OK_EXPECT),
16201623
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
@@ -1383,6 +1383,27 @@ declare_clippy_lint! {
13831383
"using unnecessary lazy evaluation, which can be replaced with simpler eager evaluation"
13841384
}
13851385

1386+
declare_clippy_lint! {
1387+
/// **What it does:** Checks for usage of `_.map(_).collect::<Result<(),_>()`.
1388+
///
1389+
/// **Why is this bad?** Using `try_for_each` instead is more readable and idiomatic.
1390+
///
1391+
/// **Known problems:** None
1392+
///
1393+
/// **Example:**
1394+
///
1395+
/// ```rust
1396+
/// (0..3).map(|t| Err(t)).collect::<Result<(), _>>();
1397+
/// ```
1398+
/// Use instead:
1399+
/// ```rust
1400+
/// (0..3).try_for_each(|t| Err(t));
1401+
/// ```
1402+
pub MAP_COLLECT_RESULT_UNIT,
1403+
style,
1404+
"using `.map(_).collect::<Result<(),_>()`, which can be replaced with `try_for_each`"
1405+
}
1406+
13861407
declare_lint_pass!(Methods => [
13871408
UNWRAP_USED,
13881409
EXPECT_USED,
@@ -1433,6 +1454,7 @@ declare_lint_pass!(Methods => [
14331454
FILETYPE_IS_FILE,
14341455
OPTION_AS_REF_DEREF,
14351456
UNNECESSARY_LAZY_EVALUATIONS,
1457+
MAP_COLLECT_RESULT_UNIT,
14361458
]);
14371459

14381460
impl<'tcx> LateLintPass<'tcx> for Methods {
@@ -1515,6 +1537,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
15151537
["unwrap_or_else", ..] => unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], "unwrap_or"),
15161538
["get_or_insert_with", ..] => unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], "get_or_insert"),
15171539
["ok_or_else", ..] => unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], "ok_or"),
1540+
["collect", "map"] => lint_map_collect(cx, expr, arg_lists[1], arg_lists[0]),
15181541
_ => {},
15191542
}
15201543

@@ -3501,6 +3524,42 @@ fn lint_option_as_ref_deref<'tcx>(
35013524
}
35023525
}
35033526

3527+
fn lint_map_collect(
3528+
cx: &LateContext<'_>,
3529+
expr: &hir::Expr<'_>,
3530+
map_args: &[hir::Expr<'_>],
3531+
collect_args: &[hir::Expr<'_>],
3532+
) {
3533+
if_chain! {
3534+
// called on Iterator
3535+
if let [map_expr] = collect_args;
3536+
if match_trait_method(cx, map_expr, &paths::ITERATOR);
3537+
// return of collect `Result<(),_>`
3538+
let collect_ret_ty = cx.typeck_results().expr_ty(expr);
3539+
if is_type_diagnostic_item(cx, collect_ret_ty, sym!(result_type));
3540+
if let ty::Adt(_, substs) = collect_ret_ty.kind();
3541+
if let Some(result_t) = substs.types().next();
3542+
if result_t.is_unit();
3543+
// get parts for snippet
3544+
if let [iter, map_fn] = map_args;
3545+
then {
3546+
span_lint_and_sugg(
3547+
cx,
3548+
MAP_COLLECT_RESULT_UNIT,
3549+
expr.span,
3550+
"`.map().collect()` can be replaced with `.try_for_each()`",
3551+
"try this",
3552+
format!(
3553+
"{}.try_for_each({})",
3554+
snippet(cx, iter.span, ".."),
3555+
snippet(cx, map_fn.span, "..")
3556+
),
3557+
Applicability::MachineApplicable,
3558+
);
3559+
}
3560+
}
3561+
}
3562+
35043563
/// Given a `Result<T, E>` type, return its error type (`E`).
35053564
fn get_error_type<'a>(cx: &LateContext<'_>, ty: Ty<'a>) -> Option<Ty<'a>> {
35063565
match ty.kind() {

src/lintlist/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1222,6 +1222,13 @@ vec![
12221222
deprecation: None,
12231223
module: "map_clone",
12241224
},
1225+
Lint {
1226+
name: "map_collect_result_unit",
1227+
group: "style",
1228+
desc: "using `.map(_).collect::<Result<(),_>()`, which can be replaced with `try_for_each`",
1229+
deprecation: None,
1230+
module: "methods",
1231+
},
12251232
Lint {
12261233
name: "map_entry",
12271234
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)