Skip to content

Commit 859a14b

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

File tree

7 files changed

+122
-0
lines changed

7 files changed

+122
-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: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1383,6 +1383,31 @@ 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+
/// let iter = vec![0; 2].iter();
1397+
///
1398+
/// iter.map(|t| Err(t)).collect::<Result<(), _>>();
1399+
/// ```
1400+
/// Use instead:
1401+
/// ```rust
1402+
/// let iter = vec![0; 2].iter();
1403+
///
1404+
/// iter.try_for_each(|t| Err(t));
1405+
/// ```
1406+
pub MAP_COLLECT_RESULT_UNIT,
1407+
style,
1408+
"using `.map(_).collect::<Result<(),_>()`, which can be replaced with `try_for_each`"
1409+
}
1410+
13861411
declare_lint_pass!(Methods => [
13871412
UNWRAP_USED,
13881413
EXPECT_USED,
@@ -1433,6 +1458,7 @@ declare_lint_pass!(Methods => [
14331458
FILETYPE_IS_FILE,
14341459
OPTION_AS_REF_DEREF,
14351460
UNNECESSARY_LAZY_EVALUATIONS,
1461+
MAP_COLLECT_RESULT_UNIT,
14361462
]);
14371463

14381464
impl<'tcx> LateLintPass<'tcx> for Methods {
@@ -1515,6 +1541,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
15151541
["unwrap_or_else", ..] => unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], "unwrap_or"),
15161542
["get_or_insert_with", ..] => unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], "get_or_insert"),
15171543
["ok_or_else", ..] => unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], "ok_or"),
1544+
["collect", "map"] => lint_map_collect(cx, expr, arg_lists[1], arg_lists[0]),
15181545
_ => {},
15191546
}
15201547

@@ -3501,6 +3528,42 @@ fn lint_option_as_ref_deref<'tcx>(
35013528
}
35023529
}
35033530

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