about summary refs log tree commit diff
diff options
context:
space:
mode:
authorLzu Tao <taolzu@gmail.com>2019-08-15 10:53:11 +0700
committerLzu Tao <taolzu@gmail.com>2019-08-19 03:54:40 +0000
commit7065239da55420e26adb7cb647fc7eb4e9c8798e (patch)
tree9dfb12798a488e266c1cd7e52ea6ebd35fcb9972
parentf01a0c0e08b5905f174d30201b0cb927f5cad500 (diff)
downloadrust-7065239da55420e26adb7cb647fc7eb4e9c8798e.tar.gz
rust-7065239da55420e26adb7cb647fc7eb4e9c8798e.zip
Add option_and_then_some lint
-rw-r--r--CHANGELOG.md1
-rw-r--r--README.md2
-rw-r--r--clippy_dev/src/lib.rs6
-rw-r--r--clippy_lints/src/lib.rs2
-rw-r--r--clippy_lints/src/methods/mod.rs129
-rw-r--r--src/lintlist/mod.rs9
-rw-r--r--tests/ui/option_and_then_some.fixed21
-rw-r--r--tests/ui/option_and_then_some.rs21
-rw-r--r--tests/ui/option_and_then_some.stderr20
9 files changed, 201 insertions, 10 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7e5dfa112cf..59dfde0b6f4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1088,6 +1088,7 @@ Released 2018-09-13
 [`not_unsafe_ptr_arg_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#not_unsafe_ptr_arg_deref
 [`ok_expect`]: https://rust-lang.github.io/rust-clippy/master/index.html#ok_expect
 [`op_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#op_ref
+[`option_and_then_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_and_then_some
 [`option_map_or_none`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_or_none
 [`option_map_unit_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unit_fn
 [`option_map_unwrap_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unwrap_or
diff --git a/README.md b/README.md
index 389fe316ade..e5da763607b 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
 
 A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code.
 
-[There are 310 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
+[There are 311 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
 
 We have a bunch of lint categories to allow you to choose how much Clippy is supposed to ~~annoy~~ help you:
 
diff --git a/clippy_dev/src/lib.rs b/clippy_dev/src/lib.rs
index db407565b19..b53a5579971 100644
--- a/clippy_dev/src/lib.rs
+++ b/clippy_dev/src/lib.rs
@@ -123,13 +123,13 @@ pub fn gen_deprecated(lints: &[Lint]) -> Vec<String> {
     lints
         .iter()
         .filter_map(|l| {
-            l.clone().deprecation.and_then(|depr_text| {
-                Some(vec![
+            l.clone().deprecation.map(|depr_text| {
+                vec![
                     "    store.register_removed(".to_string(),
                     format!("        \"clippy::{}\",", l.name),
                     format!("        \"{}\",", depr_text),
                     "    );".to_string(),
-                ])
+                ]
             })
         })
         .flatten()
diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs
index 7e9a251d4bd..4d568a8a8b0 100644
--- a/clippy_lints/src/lib.rs
+++ b/clippy_lints/src/lib.rs
@@ -795,6 +795,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry<'_>, conf: &Conf) {
         methods::ITER_SKIP_NEXT,
         methods::NEW_RET_NO_SELF,
         methods::OK_EXPECT,
+        methods::OPTION_AND_THEN_SOME,
         methods::OPTION_MAP_OR_NONE,
         methods::OR_FUN_CALL,
         methods::SEARCH_IS_SOME,
@@ -1033,6 +1034,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry<'_>, conf: &Conf) {
         methods::CLONE_ON_COPY,
         methods::FILTER_NEXT,
         methods::FLAT_MAP_IDENTITY,
+        methods::OPTION_AND_THEN_SOME,
         methods::SEARCH_IS_SOME,
         methods::SUSPICIOUS_MAP,
         methods::UNNECESSARY_FILTER_MAP,
diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs
index c8e3ca14ab1..8d062bd897c 100644
--- a/clippy_lints/src/methods/mod.rs
+++ b/clippy_lints/src/methods/mod.rs
@@ -21,11 +21,11 @@ use syntax::symbol::LocalInternedString;
 use crate::utils::sugg;
 use crate::utils::usage::mutated_variables;
 use crate::utils::{
-    get_arg_name, get_parent_expr, get_trait_def_id, has_iter_method, implements_trait, in_macro, is_copy,
-    is_ctor_function, is_expn_of, iter_input_pats, last_path_segment, match_def_path, match_qpath, match_trait_method,
-    match_type, match_var, method_calls, method_chain_args, remove_blocks, return_ty, same_tys, single_segment_path,
-    snippet, snippet_with_applicability, snippet_with_macro_callsite, span_lint, span_lint_and_sugg,
-    span_lint_and_then, span_note_and_lint, walk_ptrs_ty, walk_ptrs_ty_depth, SpanlessEq,
+    get_arg_name, get_parent_expr, get_trait_def_id, has_iter_method, implements_trait, in_macro, in_macro_or_desugar,
+    is_copy, is_ctor_function, is_expn_of, iter_input_pats, last_path_segment, match_def_path, match_qpath,
+    match_trait_method, match_type, match_var, method_calls, method_chain_args, remove_blocks, return_ty, same_tys,
+    single_segment_path, snippet, snippet_with_applicability, snippet_with_macro_callsite, span_lint,
+    span_lint_and_sugg, span_lint_and_then, span_note_and_lint, walk_ptrs_ty, walk_ptrs_ty_depth, SpanlessEq,
 };
 use crate::utils::{paths, span_help_and_lint};
 
@@ -265,6 +265,32 @@ declare_clippy_lint! {
 }
 
 declare_clippy_lint! {
+    /// **What it does:** Checks for usage of `_.and_then(|x| Some(y))`.
+    ///
+    /// **Why is this bad?** Readability, this can be written more concisely as
+    /// `_.map(|x| y)`.
+    ///
+    /// **Known problems:** None
+    ///
+    /// **Example:**
+    ///
+    /// ```rust
+    /// let x = Some("foo");
+    /// let _ = x.and_then(|s| Some(s.len()));
+    /// ```
+    ///
+    /// The correct use would be:
+    ///
+    /// ```rust
+    /// let x = Some("foo");
+    /// let _ = x.map(|s| s.len());
+    /// ```
+    pub OPTION_AND_THEN_SOME,
+    complexity,
+    "using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)`"
+}
+
+declare_clippy_lint! {
     /// **What it does:** Checks for usage of `_.filter(_).next()`.
     ///
     /// **Why is this bad?** Readability, this can be written more concisely as
@@ -918,6 +944,7 @@ declare_lint_pass!(Methods => [
     OPTION_MAP_UNWRAP_OR_ELSE,
     RESULT_MAP_UNWRAP_OR_ELSE,
     OPTION_MAP_OR_NONE,
+    OPTION_AND_THEN_SOME,
     OR_FUN_CALL,
     EXPECT_FUN_CALL,
     CHARS_NEXT_CMP,
@@ -967,6 +994,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Methods {
             ["unwrap_or", "map"] => option_map_unwrap_or::lint(cx, expr, arg_lists[1], arg_lists[0]),
             ["unwrap_or_else", "map"] => lint_map_unwrap_or_else(cx, expr, arg_lists[1], arg_lists[0]),
             ["map_or", ..] => lint_map_or_none(cx, expr, arg_lists[0]),
+            ["and_then", ..] => lint_option_and_then_some(cx, expr, arg_lists[0]),
             ["next", "filter"] => lint_filter_next(cx, expr, arg_lists[1]),
             ["map", "filter"] => lint_filter_map(cx, expr, arg_lists[1], arg_lists[0]),
             ["map", "filter_map"] => lint_filter_map_map(cx, expr, arg_lists[1], arg_lists[0]),
@@ -2072,6 +2100,97 @@ fn lint_map_or_none<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr,
     }
 }
 
+/// Lint use of `_.and_then(|x| Some(y))` for `Option`s
+fn lint_option_and_then_some(cx: &LateContext<'_, '_>, expr: &hir::Expr, args: &[hir::Expr]) {
+    const LINT_MSG: &str = "using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)`";
+    const NO_OP_MSG: &str = "using `Option.and_then(Some)`, which is a no-op";
+
+    // Searches an return expressions in `y` in `_.and_then(|x| Some(y))`, which we don't lint
+    struct RetCallFinder {
+        found: bool,
+    }
+
+    impl<'tcx> intravisit::Visitor<'tcx> for RetCallFinder {
+        fn visit_expr(&mut self, expr: &'tcx hir::Expr) {
+            if self.found {
+                return;
+            }
+            if let hir::ExprKind::Ret(..) = &expr.node {
+                self.found = true;
+            } else {
+                intravisit::walk_expr(self, expr);
+            }
+        }
+
+        fn nested_visit_map<'this>(&'this mut self) -> intravisit::NestedVisitorMap<'this, 'tcx> {
+            intravisit::NestedVisitorMap::None
+        }
+    }
+
+    let ty = cx.tables.expr_ty(&args[0]);
+    if !match_type(cx, ty, &paths::OPTION) {
+        return;
+    }
+
+    match args[1].node {
+        hir::ExprKind::Closure(_, _, body_id, closure_args_span, _) => {
+            let closure_body = cx.tcx.hir().body(body_id);
+            let closure_expr = remove_blocks(&closure_body.value);
+            if_chain! {
+                if let hir::ExprKind::Call(ref some_expr, ref some_args) = closure_expr.node;
+                if let hir::ExprKind::Path(ref qpath) = some_expr.node;
+                if match_qpath(qpath, &paths::OPTION_SOME);
+                if some_args.len() == 1;
+                then {
+                    let inner_expr = &some_args[0];
+
+                    let mut finder = RetCallFinder { found: false };
+                    finder.visit_expr(inner_expr);
+                    if finder.found {
+                        return;
+                    }
+
+                    let some_inner_snip = if in_macro_or_desugar(inner_expr.span) {
+                        snippet_with_macro_callsite(cx, inner_expr.span, "_")
+                    } else {
+                        snippet(cx, inner_expr.span, "_")
+                    };
+
+                    let closure_args_snip = snippet(cx, closure_args_span, "..");
+                    let option_snip = snippet(cx, args[0].span, "..");
+                    let note = format!("{}.map({} {})", option_snip, closure_args_snip, some_inner_snip);
+                    span_lint_and_sugg(
+                        cx,
+                        OPTION_AND_THEN_SOME,
+                        expr.span,
+                        LINT_MSG,
+                        "try this",
+                        note,
+                        Applicability::MachineApplicable,
+                    );
+                }
+            }
+        },
+        // `_.and_then(Some)` case, which is no-op.
+        hir::ExprKind::Path(ref qpath) => {
+            if match_qpath(qpath, &paths::OPTION_SOME) {
+                let option_snip = snippet(cx, args[0].span, "..");
+                let note = format!("{}", option_snip);
+                span_lint_and_sugg(
+                    cx,
+                    OPTION_AND_THEN_SOME,
+                    expr.span,
+                    NO_OP_MSG,
+                    "use the expression directly",
+                    note,
+                    Applicability::MachineApplicable,
+                );
+            }
+        },
+        _ => {},
+    }
+}
+
 /// lint use of `filter().next()` for `Iterators`
 fn lint_filter_next<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr, filter_args: &'tcx [hir::Expr]) {
     // lint if caller of `.filter().next()` is an Iterator
diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs
index 485352f5cc4..9abe4558bb9 100644
--- a/src/lintlist/mod.rs
+++ b/src/lintlist/mod.rs
@@ -6,7 +6,7 @@ pub use lint::Lint;
 pub use lint::LINT_LEVELS;
 
 // begin lint list, do not remove this comment, it’s used in `update_lints`
-pub const ALL_LINTS: [Lint; 310] = [
+pub const ALL_LINTS: [Lint; 311] = [
     Lint {
         name: "absurd_extreme_comparisons",
         group: "correctness",
@@ -1317,6 +1317,13 @@ pub const ALL_LINTS: [Lint; 310] = [
         module: "eq_op",
     },
     Lint {
+        name: "option_and_then_some",
+        group: "complexity",
+        desc: "using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)`",
+        deprecation: None,
+        module: "methods",
+    },
+    Lint {
         name: "option_map_or_none",
         group: "style",
         desc: "using `Option.map_or(None, f)`, which is more succinctly expressed as `and_then(f)`",
diff --git a/tests/ui/option_and_then_some.fixed b/tests/ui/option_and_then_some.fixed
new file mode 100644
index 00000000000..852f48879a3
--- /dev/null
+++ b/tests/ui/option_and_then_some.fixed
@@ -0,0 +1,21 @@
+// run-rustfix
+#![deny(clippy::option_and_then_some)]
+
+// need a main anyway, use it get rid of unused warnings too
+pub fn main() {
+    let x = Some(5);
+    // the easiest cases
+    let _ = x;
+    let _ = x.map(|o| o + 1);
+    // and an easy counter-example
+    let _ = x.and_then(|o| if o < 32 { Some(o) } else { None });
+
+    // Different type
+    let x: Result<u32, &str> = Ok(1);
+    let _ = x.and_then(Ok);
+}
+
+pub fn foo() -> Option<String> {
+    let x = Some(String::from("hello"));
+    Some("hello".to_owned()).and_then(|s| Some(format!("{}{}", s, x?)))
+}
diff --git a/tests/ui/option_and_then_some.rs b/tests/ui/option_and_then_some.rs
new file mode 100644
index 00000000000..aebc66374a5
--- /dev/null
+++ b/tests/ui/option_and_then_some.rs
@@ -0,0 +1,21 @@
+// run-rustfix
+#![deny(clippy::option_and_then_some)]
+
+// need a main anyway, use it get rid of unused warnings too
+pub fn main() {
+    let x = Some(5);
+    // the easiest cases
+    let _ = x.and_then(Some);
+    let _ = x.and_then(|o| Some(o + 1));
+    // and an easy counter-example
+    let _ = x.and_then(|o| if o < 32 { Some(o) } else { None });
+
+    // Different type
+    let x: Result<u32, &str> = Ok(1);
+    let _ = x.and_then(Ok);
+}
+
+pub fn foo() -> Option<String> {
+    let x = Some(String::from("hello"));
+    Some("hello".to_owned()).and_then(|s| Some(format!("{}{}", s, x?)))
+}
diff --git a/tests/ui/option_and_then_some.stderr b/tests/ui/option_and_then_some.stderr
new file mode 100644
index 00000000000..a3b0a26149e
--- /dev/null
+++ b/tests/ui/option_and_then_some.stderr
@@ -0,0 +1,20 @@
+error: using `Option.and_then(Some)`, which is a no-op
+  --> $DIR/option_and_then_some.rs:8:13
+   |
+LL |     let _ = x.and_then(Some);
+   |             ^^^^^^^^^^^^^^^^ help: use the expression directly: `x`
+   |
+note: lint level defined here
+  --> $DIR/option_and_then_some.rs:2:9
+   |
+LL | #![deny(clippy::option_and_then_some)]
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)`
+  --> $DIR/option_and_then_some.rs:9:13
+   |
+LL |     let _ = x.and_then(|o| Some(o + 1));
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `x.map(|o| o + 1)`
+
+error: aborting due to 2 previous errors
+