Auto merge of #6591 - camsteffen:manual-filter-map, r=llogiq

`manual_filter_map` and `manual_find_map`

changelog: Add `manual_filter_map` and replace `find_map` with `manual_find_map`

Replaces #6453

Fixes #3188
Fixes #4193

~Depends on #6567 (to fix an internal lint false positive)~

This replaces `filter_map` and `find_map` with `manual_filter_map` and `manual_find_map` respectively. However, `filter_map` is left in place since it is used for a variety of other cases. See discussion in #6453.
This commit is contained in:
bors 2021-01-22 22:28:41 +00:00
commit 3c3f4a75b4
15 changed files with 355 additions and 97 deletions

View File

@ -2035,6 +2035,8 @@ Released 2018-09-13
[`macro_use_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#macro_use_imports
[`main_recursion`]: https://rust-lang.github.io/rust-clippy/master/index.html#main_recursion
[`manual_async_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_async_fn
[`manual_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_filter_map
[`manual_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_find_map
[`manual_memcpy`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_memcpy
[`manual_non_exhaustive`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_non_exhaustive
[`manual_ok_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ok_or

View File

@ -1,5 +1,3 @@
#![allow(clippy::filter_map)]
use std::fs;
use std::fs::File;
use std::io::prelude::*;

View File

@ -166,3 +166,8 @@ declare_deprecated_lint! {
pub PANIC_PARAMS,
"this lint has been uplifted to rustc and is now called `panic_fmt`"
}
declare_deprecated_lint! {
pub FIND_MAP,
"this lint is replaced by `manual_find_map`, a more specific lint"
}

View File

@ -505,6 +505,10 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
"clippy::panic_params",
"this lint has been uplifted to rustc and is now called `panic_fmt`",
);
store.register_removed(
"clippy::find_map",
"this lint is replaced by `manual_find_map`, a more specific lint",
);
// end deprecated lints, do not remove this comment, its used in `update_lints`
// begin register lints, do not remove this comment, its used in `update_lints`
@ -732,7 +736,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
&methods::FILTER_MAP,
&methods::FILTER_MAP_NEXT,
&methods::FILTER_NEXT,
&methods::FIND_MAP,
&methods::FLAT_MAP_IDENTITY,
&methods::FROM_ITER_INSTEAD_OF_COLLECT,
&methods::GET_UNWRAP,
@ -745,6 +748,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
&methods::ITER_NTH,
&methods::ITER_NTH_ZERO,
&methods::ITER_SKIP_NEXT,
&methods::MANUAL_FILTER_MAP,
&methods::MANUAL_FIND_MAP,
&methods::MANUAL_SATURATING_ARITHMETIC,
&methods::MAP_COLLECT_RESULT_UNIT,
&methods::MAP_FLATTEN,
@ -1331,7 +1336,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&matches::SINGLE_MATCH_ELSE),
LintId::of(&methods::FILTER_MAP),
LintId::of(&methods::FILTER_MAP_NEXT),
LintId::of(&methods::FIND_MAP),
LintId::of(&methods::INEFFICIENT_TO_STRING),
LintId::of(&methods::MAP_FLATTEN),
LintId::of(&methods::MAP_UNWRAP_OR),
@ -1526,6 +1530,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&methods::ITER_NTH),
LintId::of(&methods::ITER_NTH_ZERO),
LintId::of(&methods::ITER_SKIP_NEXT),
LintId::of(&methods::MANUAL_FILTER_MAP),
LintId::of(&methods::MANUAL_FIND_MAP),
LintId::of(&methods::MANUAL_SATURATING_ARITHMETIC),
LintId::of(&methods::MAP_COLLECT_RESULT_UNIT),
LintId::of(&methods::NEW_RET_NO_SELF),
@ -1823,6 +1829,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&methods::FILTER_NEXT),
LintId::of(&methods::FLAT_MAP_IDENTITY),
LintId::of(&methods::INSPECT_FOR_EACH),
LintId::of(&methods::MANUAL_FILTER_MAP),
LintId::of(&methods::MANUAL_FIND_MAP),
LintId::of(&methods::OPTION_AS_REF_DEREF),
LintId::of(&methods::SEARCH_IS_SOME),
LintId::of(&methods::SKIP_WHILE_NEXT),

View File

@ -1069,7 +1069,6 @@ fn get_assignments<'a: 'c, 'tcx: 'c, 'c>(
) -> impl Iterator<Item = Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)>> + 'c {
// As the `filter` and `map` below do different things, I think putting together
// just increases complexity. (cc #3188 and #4193)
#[allow(clippy::filter_map)]
stmts
.iter()
.filter_map(move |stmt| match stmt.kind {

View File

@ -15,7 +15,8 @@ use if_chain::if_chain;
use rustc_ast::ast;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::{TraitItem, TraitItemKind};
use rustc_hir::def::Res;
use rustc_hir::{Expr, ExprKind, PatKind, QPath, TraitItem, TraitItemKind, UnOp};
use rustc_lint::{LateContext, LateLintPass, Lint, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::{self, TraitRef, Ty, TyS};
@ -450,6 +451,58 @@ declare_clippy_lint! {
"using combinations of `filter`, `map`, `filter_map` and `flat_map` which can usually be written as a single method call"
}
declare_clippy_lint! {
/// **What it does:** Checks for usage of `_.filter(_).map(_)` that can be written more simply
/// as `filter_map(_)`.
///
/// **Why is this bad?** Redundant code in the `filter` and `map` operations is poor style and
/// less performant.
///
/// **Known problems:** None.
///
/// **Example:**
/// Bad:
/// ```rust
/// (0_i32..10)
/// .filter(|n| n.checked_add(1).is_some())
/// .map(|n| n.checked_add(1).unwrap());
/// ```
///
/// Good:
/// ```rust
/// (0_i32..10).filter_map(|n| n.checked_add(1));
/// ```
pub MANUAL_FILTER_MAP,
complexity,
"using `_.filter(_).map(_)` in a way that can be written more simply as `filter_map(_)`"
}
declare_clippy_lint! {
/// **What it does:** Checks for usage of `_.find(_).map(_)` that can be written more simply
/// as `find_map(_)`.
///
/// **Why is this bad?** Redundant code in the `find` and `map` operations is poor style and
/// less performant.
///
/// **Known problems:** None.
///
/// **Example:**
/// Bad:
/// ```rust
/// (0_i32..10)
/// .find(|n| n.checked_add(1).is_some())
/// .map(|n| n.checked_add(1).unwrap());
/// ```
///
/// Good:
/// ```rust
/// (0_i32..10).find_map(|n| n.checked_add(1));
/// ```
pub MANUAL_FIND_MAP,
complexity,
"using `_.find(_).map(_)` in a way that can be written more simply as `find_map(_)`"
}
declare_clippy_lint! {
/// **What it does:** Checks for usage of `_.filter_map(_).next()`.
///
@ -494,28 +547,6 @@ declare_clippy_lint! {
"call to `flat_map` where `flatten` is sufficient"
}
declare_clippy_lint! {
/// **What it does:** Checks for usage of `_.find(_).map(_)`.
///
/// **Why is this bad?** Readability, this can be written more concisely as
/// `_.find_map(_)`.
///
/// **Known problems:** Often requires a condition + Option/Iterator creation
/// inside the closure.
///
/// **Example:**
/// ```rust
/// (0..3).find(|x| *x == 2).map(|x| x * 2);
/// ```
/// Can be written as
/// ```rust
/// (0..3).find_map(|x| if x == 2 { Some(x * 2) } else { None });
/// ```
pub FIND_MAP,
pedantic,
"using a combination of `find` and `map` can usually be written as a single method call"
}
declare_clippy_lint! {
/// **What it does:** Checks for an iterator or string search (such as `find()`,
/// `position()`, or `rposition()`) followed by a call to `is_some()`.
@ -1473,9 +1504,10 @@ impl_lint_pass!(Methods => [
FILTER_NEXT,
SKIP_WHILE_NEXT,
FILTER_MAP,
MANUAL_FILTER_MAP,
MANUAL_FIND_MAP,
FILTER_MAP_NEXT,
FLAT_MAP_IDENTITY,
FIND_MAP,
MAP_FLATTEN,
ITERATOR_STEP_BY_ZERO,
ITER_NEXT_SLICE,
@ -1540,10 +1572,10 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
["next", "filter"] => lint_filter_next(cx, expr, arg_lists[1]),
["next", "skip_while"] => lint_skip_while_next(cx, expr, arg_lists[1]),
["next", "iter"] => lint_iter_next(cx, expr, arg_lists[1]),
["map", "filter"] => lint_filter_map(cx, expr, arg_lists[1], arg_lists[0]),
["map", "filter"] => lint_filter_map(cx, expr, false),
["map", "filter_map"] => lint_filter_map_map(cx, expr, arg_lists[1], arg_lists[0]),
["next", "filter_map"] => lint_filter_map_next(cx, expr, arg_lists[1], self.msrv.as_ref()),
["map", "find"] => lint_find_map(cx, expr, arg_lists[1], arg_lists[0]),
["map", "find"] => lint_filter_map(cx, expr, true),
["flat_map", "filter"] => lint_filter_flat_map(cx, expr, arg_lists[1], arg_lists[0]),
["flat_map", "filter_map"] => lint_filter_map_flat_map(cx, expr, arg_lists[1], arg_lists[0]),
["flat_map", ..] => lint_flat_map_identity(cx, expr, arg_lists[0], method_spans[0]),
@ -2988,18 +3020,79 @@ fn lint_skip_while_next<'tcx>(
}
}
/// lint use of `filter().map()` for `Iterators`
fn lint_filter_map<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx hir::Expr<'_>,
_filter_args: &'tcx [hir::Expr<'_>],
_map_args: &'tcx [hir::Expr<'_>],
) {
// lint if caller of `.filter().map()` is an Iterator
if match_trait_method(cx, expr, &paths::ITERATOR) {
let msg = "called `filter(..).map(..)` on an `Iterator`";
let hint = "this is more succinctly expressed by calling `.filter_map(..)` instead";
span_lint_and_help(cx, FILTER_MAP, expr.span, msg, None, hint);
/// lint use of `filter().map()` or `find().map()` for `Iterators`
fn lint_filter_map<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, is_find: bool) {
if_chain! {
if let ExprKind::MethodCall(_, _, [map_recv, map_arg], map_span) = expr.kind;
if let ExprKind::MethodCall(_, _, [_, filter_arg], filter_span) = map_recv.kind;
if match_trait_method(cx, map_recv, &paths::ITERATOR);
// filter(|x| ...is_some())...
if let ExprKind::Closure(_, _, filter_body_id, ..) = filter_arg.kind;
let filter_body = cx.tcx.hir().body(filter_body_id);
if let [filter_param] = filter_body.params;
// optional ref pattern: `filter(|&x| ..)`
let (filter_pat, is_filter_param_ref) = if let PatKind::Ref(ref_pat, _) = filter_param.pat.kind {
(ref_pat, true)
} else {
(filter_param.pat, false)
};
// closure ends with is_some() or is_ok()
if let PatKind::Binding(_, filter_param_id, _, None) = filter_pat.kind;
if let ExprKind::MethodCall(path, _, [filter_arg], _) = filter_body.value.kind;
if let Some(opt_ty) = cx.typeck_results().expr_ty(filter_arg).ty_adt_def();
if let Some(is_result) = if cx.tcx.is_diagnostic_item(sym::option_type, opt_ty.did) {
Some(false)
} else if cx.tcx.is_diagnostic_item(sym::result_type, opt_ty.did) {
Some(true)
} else {
None
};
if path.ident.name.as_str() == if is_result { "is_ok" } else { "is_some" };
// ...map(|x| ...unwrap())
if let ExprKind::Closure(_, _, map_body_id, ..) = map_arg.kind;
let map_body = cx.tcx.hir().body(map_body_id);
if let [map_param] = map_body.params;
if let PatKind::Binding(_, map_param_id, map_param_ident, None) = map_param.pat.kind;
// closure ends with expect() or unwrap()
if let ExprKind::MethodCall(seg, _, [map_arg, ..], _) = map_body.value.kind;
if matches!(seg.ident.name, sym::expect | sym::unwrap | sym::unwrap_or);
let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| {
// in `filter(|x| ..)`, replace `*x` with `x`
let a_path = if_chain! {
if !is_filter_param_ref;
if let ExprKind::Unary(UnOp::UnDeref, expr_path) = a.kind;
then { expr_path } else { a }
};
// let the filter closure arg and the map closure arg be equal
if_chain! {
if let ExprKind::Path(QPath::Resolved(None, a_path)) = a_path.kind;
if let ExprKind::Path(QPath::Resolved(None, b_path)) = b.kind;
if a_path.res == Res::Local(filter_param_id);
if b_path.res == Res::Local(map_param_id);
if TyS::same_type(cx.typeck_results().expr_ty_adjusted(a), cx.typeck_results().expr_ty_adjusted(b));
then {
return true;
}
}
false
};
if SpanlessEq::new(cx).expr_fallback(eq_fallback).eq_expr(filter_arg, map_arg);
then {
let span = filter_span.to(map_span);
let (filter_name, lint) = if is_find {
("find", MANUAL_FIND_MAP)
} else {
("filter", MANUAL_FILTER_MAP)
};
let msg = format!("`{}(..).map(..)` can be simplified as `{0}_map(..)`", filter_name);
let to_opt = if is_result { ".ok()" } else { "" };
let sugg = format!("{}_map(|{}| {}{})", filter_name, map_param_ident,
snippet(cx, map_arg.span, ".."), to_opt);
span_lint_and_sugg(cx, lint, span, &msg, "try", sugg, Applicability::MachineApplicable);
}
}
}
@ -3037,21 +3130,6 @@ fn lint_filter_map_next<'tcx>(
}
}
/// lint use of `find().map()` for `Iterators`
fn lint_find_map<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx hir::Expr<'_>,
_find_args: &'tcx [hir::Expr<'_>],
map_args: &'tcx [hir::Expr<'_>],
) {
// lint if caller of `.filter().map()` is an Iterator
if match_trait_method(cx, &map_args[0], &paths::ITERATOR) {
let msg = "called `find(..).map(..)` on an `Iterator`";
let hint = "this is more succinctly expressed by calling `.find_map(..)` instead";
span_lint_and_help(cx, FIND_MAP, expr.span, msg, None, hint);
}
}
/// lint use of `filter_map().map()` for `Iterators`
fn lint_filter_map_map<'tcx>(
cx: &LateContext<'tcx>,
@ -3059,7 +3137,7 @@ fn lint_filter_map_map<'tcx>(
_filter_args: &'tcx [hir::Expr<'_>],
_map_args: &'tcx [hir::Expr<'_>],
) {
// lint if caller of `.filter().map()` is an Iterator
// lint if caller of `.filter_map().map()` is an Iterator
if match_trait_method(cx, expr, &paths::ITERATOR) {
let msg = "called `filter_map(..).map(..)` on an `Iterator`";
let hint = "this is more succinctly expressed by only calling `.filter_map(..)` instead";

View File

@ -24,6 +24,7 @@ pub struct SpanlessEq<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
maybe_typeck_results: Option<&'tcx TypeckResults<'tcx>>,
allow_side_effects: bool,
expr_fallback: Option<Box<dyn Fn(&Expr<'_>, &Expr<'_>) -> bool + 'a>>,
}
impl<'a, 'tcx> SpanlessEq<'a, 'tcx> {
@ -32,6 +33,7 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> {
cx,
maybe_typeck_results: cx.maybe_typeck_results(),
allow_side_effects: true,
expr_fallback: None,
}
}
@ -43,6 +45,13 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> {
}
}
pub fn expr_fallback(self, expr_fallback: impl Fn(&Expr<'_>, &Expr<'_>) -> bool + 'a) -> Self {
Self {
expr_fallback: Some(Box::new(expr_fallback)),
..self
}
}
/// Checks whether two statements are the same.
pub fn eq_stmt(&mut self, left: &Stmt<'_>, right: &Stmt<'_>) -> bool {
match (&left.kind, &right.kind) {
@ -81,7 +90,7 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> {
}
}
match (&reduce_exprkind(&left.kind), &reduce_exprkind(&right.kind)) {
let is_eq = match (&reduce_exprkind(&left.kind), &reduce_exprkind(&right.kind)) {
(&ExprKind::AddrOf(lb, l_mut, ref le), &ExprKind::AddrOf(rb, r_mut, ref re)) => {
lb == rb && l_mut == r_mut && self.eq_expr(le, re)
},
@ -158,7 +167,8 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> {
(&ExprKind::Array(l), &ExprKind::Array(r)) => self.eq_exprs(l, r),
(&ExprKind::DropTemps(ref le), &ExprKind::DropTemps(ref re)) => self.eq_expr(le, re),
_ => false,
}
};
is_eq || self.expr_fallback.as_ref().map_or(false, |f| f(left, right))
}
fn eq_exprs(&mut self, left: &[Expr<'_>], right: &[Expr<'_>]) -> bool {

View File

@ -1,12 +1,3 @@
error: called `filter(..).map(..)` on an `Iterator`
--> $DIR/filter_methods.rs:6:21
|
LL | let _: Vec<_> = vec![5; 6].into_iter().filter(|&x| x == 0).map(|x| x * 2).collect();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `-D clippy::filter-map` implied by `-D warnings`
= help: this is more succinctly expressed by calling `.filter_map(..)` instead
error: called `filter(..).flat_map(..)` on an `Iterator`
--> $DIR/filter_methods.rs:8:21
|
@ -17,6 +8,7 @@ LL | | .filter(|&x| x == 0)
LL | | .flat_map(|x| x.checked_mul(2))
| |_______________________________________^
|
= note: `-D clippy::filter-map` implied by `-D warnings`
= help: this is more succinctly expressed by calling `.flat_map(..)` and filtering by returning `iter::empty()`
error: called `filter_map(..).flat_map(..)` on an `Iterator`
@ -43,5 +35,5 @@ LL | | .map(|x| x.checked_mul(2))
|
= help: this is more succinctly expressed by only calling `.filter_map(..)` instead
error: aborting due to 4 previous errors
error: aborting due to 3 previous errors

View File

@ -1,26 +0,0 @@
error: called `find(..).map(..)` on an `Iterator`
--> $DIR/find_map.rs:20:26
|
LL | let _: Option<i32> = a.iter().find(|s| s.parse::<i32>().is_ok()).map(|s| s.parse().unwrap());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `-D clippy::find-map` implied by `-D warnings`
= help: this is more succinctly expressed by calling `.find_map(..)` instead
error: called `find(..).map(..)` on an `Iterator`
--> $DIR/find_map.rs:23:29
|
LL | let _: Option<Flavor> = desserts_of_the_week
| _____________________________^
LL | | .iter()
LL | | .find(|dessert| match *dessert {
LL | | Dessert::Cake(_) => true,
... |
LL | | _ => unreachable!(),
LL | | });
| |__________^
|
= help: this is more succinctly expressed by calling `.find_map(..)` instead
error: aborting due to 2 previous errors

View File

@ -0,0 +1,37 @@
// run-rustfix
#![allow(dead_code)]
#![warn(clippy::manual_filter_map)]
#![allow(clippy::redundant_closure)] // FIXME suggestion may have redundant closure
fn main() {
// is_some(), unwrap()
let _ = (0..).filter_map(|a| to_opt(a));
// ref pattern, expect()
let _ = (0..).filter_map(|a| to_opt(a));
// is_ok(), unwrap_or()
let _ = (0..).filter_map(|a| to_res(a).ok());
}
fn no_lint() {
// no shared code
let _ = (0..).filter(|n| *n > 1).map(|n| n + 1);
// very close but different since filter() provides a reference
let _ = (0..).filter(|n| to_opt(n).is_some()).map(|a| to_opt(a).unwrap());
// similar but different
let _ = (0..).filter(|n| to_opt(n).is_some()).map(|n| to_res(n).unwrap());
let _ = (0..)
.filter(|n| to_opt(n).map(|n| n + 1).is_some())
.map(|a| to_opt(a).unwrap());
}
fn to_opt<T>(_: T) -> Option<T> {
unimplemented!()
}
fn to_res<T>(_: T) -> Result<T, ()> {
unimplemented!()
}

View File

@ -0,0 +1,37 @@
// run-rustfix
#![allow(dead_code)]
#![warn(clippy::manual_filter_map)]
#![allow(clippy::redundant_closure)] // FIXME suggestion may have redundant closure
fn main() {
// is_some(), unwrap()
let _ = (0..).filter(|n| to_opt(*n).is_some()).map(|a| to_opt(a).unwrap());
// ref pattern, expect()
let _ = (0..).filter(|&n| to_opt(n).is_some()).map(|a| to_opt(a).expect("hi"));
// is_ok(), unwrap_or()
let _ = (0..).filter(|&n| to_res(n).is_ok()).map(|a| to_res(a).unwrap_or(1));
}
fn no_lint() {
// no shared code
let _ = (0..).filter(|n| *n > 1).map(|n| n + 1);
// very close but different since filter() provides a reference
let _ = (0..).filter(|n| to_opt(n).is_some()).map(|a| to_opt(a).unwrap());
// similar but different
let _ = (0..).filter(|n| to_opt(n).is_some()).map(|n| to_res(n).unwrap());
let _ = (0..)
.filter(|n| to_opt(n).map(|n| n + 1).is_some())
.map(|a| to_opt(a).unwrap());
}
fn to_opt<T>(_: T) -> Option<T> {
unimplemented!()
}
fn to_res<T>(_: T) -> Result<T, ()> {
unimplemented!()
}

View File

@ -0,0 +1,22 @@
error: `filter(..).map(..)` can be simplified as `filter_map(..)`
--> $DIR/manual_filter_map.rs:8:19
|
LL | let _ = (0..).filter(|n| to_opt(*n).is_some()).map(|a| to_opt(a).unwrap());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter_map(|a| to_opt(a))`
|
= note: `-D clippy::manual-filter-map` implied by `-D warnings`
error: `filter(..).map(..)` can be simplified as `filter_map(..)`
--> $DIR/manual_filter_map.rs:11:19
|
LL | let _ = (0..).filter(|&n| to_opt(n).is_some()).map(|a| to_opt(a).expect("hi"));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter_map(|a| to_opt(a))`
error: `filter(..).map(..)` can be simplified as `filter_map(..)`
--> $DIR/manual_filter_map.rs:14:19
|
LL | let _ = (0..).filter(|&n| to_res(n).is_ok()).map(|a| to_res(a).unwrap_or(1));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter_map(|a| to_res(a).ok())`
error: aborting due to 3 previous errors

View File

@ -0,0 +1,37 @@
// run-rustfix
#![allow(dead_code)]
#![warn(clippy::manual_find_map)]
#![allow(clippy::redundant_closure)] // FIXME suggestion may have redundant closure
fn main() {
// is_some(), unwrap()
let _ = (0..).find_map(|a| to_opt(a));
// ref pattern, expect()
let _ = (0..).find_map(|a| to_opt(a));
// is_ok(), unwrap_or()
let _ = (0..).find_map(|a| to_res(a).ok());
}
fn no_lint() {
// no shared code
let _ = (0..).filter(|n| *n > 1).map(|n| n + 1);
// very close but different since filter() provides a reference
let _ = (0..).find(|n| to_opt(n).is_some()).map(|a| to_opt(a).unwrap());
// similar but different
let _ = (0..).find(|n| to_opt(n).is_some()).map(|n| to_res(n).unwrap());
let _ = (0..)
.find(|n| to_opt(n).map(|n| n + 1).is_some())
.map(|a| to_opt(a).unwrap());
}
fn to_opt<T>(_: T) -> Option<T> {
unimplemented!()
}
fn to_res<T>(_: T) -> Result<T, ()> {
unimplemented!()
}

View File

@ -0,0 +1,37 @@
// run-rustfix
#![allow(dead_code)]
#![warn(clippy::manual_find_map)]
#![allow(clippy::redundant_closure)] // FIXME suggestion may have redundant closure
fn main() {
// is_some(), unwrap()
let _ = (0..).find(|n| to_opt(*n).is_some()).map(|a| to_opt(a).unwrap());
// ref pattern, expect()
let _ = (0..).find(|&n| to_opt(n).is_some()).map(|a| to_opt(a).expect("hi"));
// is_ok(), unwrap_or()
let _ = (0..).find(|&n| to_res(n).is_ok()).map(|a| to_res(a).unwrap_or(1));
}
fn no_lint() {
// no shared code
let _ = (0..).filter(|n| *n > 1).map(|n| n + 1);
// very close but different since filter() provides a reference
let _ = (0..).find(|n| to_opt(n).is_some()).map(|a| to_opt(a).unwrap());
// similar but different
let _ = (0..).find(|n| to_opt(n).is_some()).map(|n| to_res(n).unwrap());
let _ = (0..)
.find(|n| to_opt(n).map(|n| n + 1).is_some())
.map(|a| to_opt(a).unwrap());
}
fn to_opt<T>(_: T) -> Option<T> {
unimplemented!()
}
fn to_res<T>(_: T) -> Result<T, ()> {
unimplemented!()
}

View File

@ -0,0 +1,22 @@
error: `find(..).map(..)` can be simplified as `find_map(..)`
--> $DIR/manual_find_map.rs:8:19
|
LL | let _ = (0..).find(|n| to_opt(*n).is_some()).map(|a| to_opt(a).unwrap());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|a| to_opt(a))`
|
= note: `-D clippy::manual-find-map` implied by `-D warnings`
error: `find(..).map(..)` can be simplified as `find_map(..)`
--> $DIR/manual_find_map.rs:11:19
|
LL | let _ = (0..).find(|&n| to_opt(n).is_some()).map(|a| to_opt(a).expect("hi"));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|a| to_opt(a))`
error: `find(..).map(..)` can be simplified as `find_map(..)`
--> $DIR/manual_find_map.rs:14:19
|
LL | let _ = (0..).find(|&n| to_res(n).is_ok()).map(|a| to_res(a).unwrap_or(1));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|a| to_res(a).ok())`
error: aborting due to 3 previous errors