about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2021-04-19 13:34:23 +0000
committerbors <bors@rust-lang.org>2021-04-19 13:34:23 +0000
commitfe5cefcebac17967fd56de37f344ecec1fae436c (patch)
tree9a73a069238be2a3b8739bd5087f964ed0016400
parentc569d33be52a9be7b1070d1a7c9bc1255e004e87 (diff)
parent5af078ac1b705abde41cc2e7c71be38701943cdb (diff)
downloadrust-fe5cefcebac17967fd56de37f344ecec1fae436c.tar.gz
rust-fe5cefcebac17967fd56de37f344ecec1fae436c.zip
Auto merge of #7101 - camsteffen:flat-map-option, r=giraffate
Add flat_map_option lint

changelog: Add flat_map_option lint

Closes #2241
-rw-r--r--CHANGELOG.md1
-rw-r--r--clippy_lints/src/lib.rs2
-rw-r--r--clippy_lints/src/matches.rs2
-rw-r--r--clippy_lints/src/methods/flat_map_option.rs34
-rw-r--r--clippy_lints/src/methods/mod.rs30
-rw-r--r--clippy_utils/src/attrs.rs2
-rw-r--r--tests/ui/flat_map_option.fixed13
-rw-r--r--tests/ui/flat_map_option.rs13
-rw-r--r--tests/ui/flat_map_option.stderr16
9 files changed, 110 insertions, 3 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 483365b5e91..56cdd356469 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2216,6 +2216,7 @@ Released 2018-09-13
 [`filter_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_next
 [`find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#find_map
 [`flat_map_identity`]: https://rust-lang.github.io/rust-clippy/master/index.html#flat_map_identity
+[`flat_map_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#flat_map_option
 [`float_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_arithmetic
 [`float_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_cmp
 [`float_cmp_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_cmp_const
diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs
index 0f48799d339..a7871b7aba7 100644
--- a/clippy_lints/src/lib.rs
+++ b/clippy_lints/src/lib.rs
@@ -770,6 +770,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
         methods::FILTER_MAP_NEXT,
         methods::FILTER_NEXT,
         methods::FLAT_MAP_IDENTITY,
+        methods::FLAT_MAP_OPTION,
         methods::FROM_ITER_INSTEAD_OF_COLLECT,
         methods::GET_UNWRAP,
         methods::IMPLICIT_CLONE,
@@ -1383,6 +1384,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
         LintId::of(matches::SINGLE_MATCH_ELSE),
         LintId::of(methods::CLONED_INSTEAD_OF_COPIED),
         LintId::of(methods::FILTER_MAP_NEXT),
+        LintId::of(methods::FLAT_MAP_OPTION),
         LintId::of(methods::IMPLICIT_CLONE),
         LintId::of(methods::INEFFICIENT_TO_STRING),
         LintId::of(methods::MAP_FLATTEN),
diff --git a/clippy_lints/src/matches.rs b/clippy_lints/src/matches.rs
index 2c8d8a0f132..44b4eb29035 100644
--- a/clippy_lints/src/matches.rs
+++ b/clippy_lints/src/matches.rs
@@ -1509,7 +1509,7 @@ fn opt_parent_let<'a>(cx: &LateContext<'a>, ex: &Expr<'a>) -> Option<&'a Local<'
 /// Gets all arms that are unbounded `PatRange`s.
 fn all_ranges<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>], ty: Ty<'tcx>) -> Vec<SpannedRange<Constant>> {
     arms.iter()
-        .flat_map(|arm| {
+        .filter_map(|arm| {
             if let Arm { pat, guard: None, .. } = *arm {
                 if let PatKind::Range(ref lhs, ref rhs, range_end) = pat.kind {
                     let lhs = match lhs {
diff --git a/clippy_lints/src/methods/flat_map_option.rs b/clippy_lints/src/methods/flat_map_option.rs
new file mode 100644
index 00000000000..12d560653ed
--- /dev/null
+++ b/clippy_lints/src/methods/flat_map_option.rs
@@ -0,0 +1,34 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::is_trait_method;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_span::{source_map::Span, sym};
+
+use super::FLAT_MAP_OPTION;
+use clippy_utils::ty::is_type_diagnostic_item;
+
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, arg: &'tcx hir::Expr<'_>, span: Span) {
+    if !is_trait_method(cx, expr, sym::Iterator) {
+        return;
+    }
+    let arg_ty = cx.typeck_results().expr_ty_adjusted(arg);
+    let sig = match arg_ty.kind() {
+        ty::Closure(_, substs) => substs.as_closure().sig(),
+        _ if arg_ty.is_fn() => arg_ty.fn_sig(cx.tcx),
+        _ => return,
+    };
+    if !is_type_diagnostic_item(cx, sig.output().skip_binder(), sym::option_type) {
+        return;
+    }
+    span_lint_and_sugg(
+        cx,
+        FLAT_MAP_OPTION,
+        span,
+        "used `flat_map` where `filter_map` could be used instead",
+        "try",
+        "filter_map".into(),
+        Applicability::MachineApplicable,
+    )
+}
diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs
index 49a9af2a465..c2cd3011d14 100644
--- a/clippy_lints/src/methods/mod.rs
+++ b/clippy_lints/src/methods/mod.rs
@@ -17,6 +17,7 @@ mod filter_map_identity;
 mod filter_map_next;
 mod filter_next;
 mod flat_map_identity;
+mod flat_map_option;
 mod from_iter_instead_of_collect;
 mod get_unwrap;
 mod implicit_clone;
@@ -98,6 +99,29 @@ declare_clippy_lint! {
 }
 
 declare_clippy_lint! {
+    /// **What it does:** Checks for usages of `Iterator::flat_map()` where `filter_map()` could be
+    /// used instead.
+    ///
+    /// **Why is this bad?** When applicable, `filter_map()` is more clear since it shows that
+    /// `Option` is used to produce 0 or 1 items.
+    ///
+    /// **Known problems:** None.
+    ///
+    /// **Example:**
+    ///
+    /// ```rust
+    /// let nums: Vec<i32> = ["1", "2", "whee!"].iter().flat_map(|x| x.parse().ok()).collect();
+    /// ```
+    /// Use instead:
+    /// ```rust
+    /// let nums: Vec<i32> = ["1", "2", "whee!"].iter().filter_map(|x| x.parse().ok()).collect();
+    /// ```
+    pub FLAT_MAP_OPTION,
+    pedantic,
+    "used `flat_map` where `filter_map` could be used instead"
+}
+
+declare_clippy_lint! {
     /// **What it does:** Checks for `.unwrap()` calls on `Option`s and on `Result`s.
     ///
     /// **Why is this bad?** It is better to handle the `None` or `Err` case,
@@ -1663,6 +1687,7 @@ impl_lint_pass!(Methods => [
     CLONE_ON_REF_PTR,
     CLONE_DOUBLE_REF,
     CLONED_INSTEAD_OF_COPIED,
+    FLAT_MAP_OPTION,
     INEFFICIENT_TO_STRING,
     NEW_RET_NO_SELF,
     SINGLE_CHAR_PATTERN,
@@ -1958,7 +1983,10 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
                 unnecessary_filter_map::check(cx, expr, arg);
                 filter_map_identity::check(cx, expr, arg, span);
             },
-            ("flat_map", [flm_arg]) => flat_map_identity::check(cx, expr, flm_arg, span),
+            ("flat_map", [arg]) => {
+                flat_map_identity::check(cx, expr, arg, span);
+                flat_map_option::check(cx, expr, arg, span);
+            },
             ("flatten", []) => {
                 if let Some(("map", [recv, map_arg], _)) = method_call!(recv) {
                     map_flatten::check(cx, expr, recv, map_arg);
diff --git a/clippy_utils/src/attrs.rs b/clippy_utils/src/attrs.rs
index 5e44c7b3ee0..c0584e1e226 100644
--- a/clippy_utils/src/attrs.rs
+++ b/clippy_utils/src/attrs.rs
@@ -154,6 +154,6 @@ pub fn is_doc_hidden(attrs: &[ast::Attribute]) -> bool {
     attrs
         .iter()
         .filter(|attr| attr.has_name(sym::doc))
-        .flat_map(ast::Attribute::meta_item_list)
+        .filter_map(ast::Attribute::meta_item_list)
         .any(|l| attr::list_contains_name(&l, sym::hidden))
 }
diff --git a/tests/ui/flat_map_option.fixed b/tests/ui/flat_map_option.fixed
new file mode 100644
index 00000000000..6a34f008995
--- /dev/null
+++ b/tests/ui/flat_map_option.fixed
@@ -0,0 +1,13 @@
+// run-rustfix
+#![warn(clippy::flat_map_option)]
+#![allow(clippy::redundant_closure, clippy::unnecessary_filter_map)]
+
+fn main() {
+    // yay
+    let c = |x| Some(x);
+    let _ = [1].iter().filter_map(c);
+    let _ = [1].iter().filter_map(Some);
+
+    // nay
+    let _ = [1].iter().flat_map(|_| &Some(1));
+}
diff --git a/tests/ui/flat_map_option.rs b/tests/ui/flat_map_option.rs
new file mode 100644
index 00000000000..2479abddbf0
--- /dev/null
+++ b/tests/ui/flat_map_option.rs
@@ -0,0 +1,13 @@
+// run-rustfix
+#![warn(clippy::flat_map_option)]
+#![allow(clippy::redundant_closure, clippy::unnecessary_filter_map)]
+
+fn main() {
+    // yay
+    let c = |x| Some(x);
+    let _ = [1].iter().flat_map(c);
+    let _ = [1].iter().flat_map(Some);
+
+    // nay
+    let _ = [1].iter().flat_map(|_| &Some(1));
+}
diff --git a/tests/ui/flat_map_option.stderr b/tests/ui/flat_map_option.stderr
new file mode 100644
index 00000000000..a9d8056dee9
--- /dev/null
+++ b/tests/ui/flat_map_option.stderr
@@ -0,0 +1,16 @@
+error: used `flat_map` where `filter_map` could be used instead
+  --> $DIR/flat_map_option.rs:8:24
+   |
+LL |     let _ = [1].iter().flat_map(c);
+   |                        ^^^^^^^^ help: try: `filter_map`
+   |
+   = note: `-D clippy::flat-map-option` implied by `-D warnings`
+
+error: used `flat_map` where `filter_map` could be used instead
+  --> $DIR/flat_map_option.rs:9:24
+   |
+LL |     let _ = [1].iter().flat_map(Some);
+   |                        ^^^^^^^^ help: try: `filter_map`
+
+error: aborting due to 2 previous errors
+