about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--clippy_lints/src/lib.rs4
-rw-r--r--clippy_lints/src/manual_ok_or.rs97
-rw-r--r--src/lintlist/mod.rs7
-rw-r--r--tests/ui/manual_ok_or.fixed40
-rw-r--r--tests/ui/manual_ok_or.rs44
-rw-r--r--tests/ui/manual_ok_or.stderr41
7 files changed, 234 insertions, 0 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 25f3b5da198..cd884e0665d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1795,6 +1795,7 @@ Released 2018-09-13
 [`manual_async_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_async_fn
 [`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
 [`manual_range_contains`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_contains
 [`manual_saturating_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_saturating_arithmetic
 [`manual_strip`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_strip
diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs
index 3be8bc0e36d..9b6f8e73454 100644
--- a/clippy_lints/src/lib.rs
+++ b/clippy_lints/src/lib.rs
@@ -233,6 +233,7 @@ mod macro_use;
 mod main_recursion;
 mod manual_async_fn;
 mod manual_non_exhaustive;
+mod manual_ok_or;
 mod manual_strip;
 mod manual_unwrap_or;
 mod map_clone;
@@ -645,6 +646,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
         &main_recursion::MAIN_RECURSION,
         &manual_async_fn::MANUAL_ASYNC_FN,
         &manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE,
+        &manual_ok_or::MANUAL_OK_OR,
         &manual_strip::MANUAL_STRIP,
         &manual_unwrap_or::MANUAL_UNWRAP_OR,
         &map_clone::MAP_CLONE,
@@ -1140,6 +1142,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
     store.register_late_pass(|| box unwrap_in_result::UnwrapInResult);
     store.register_late_pass(|| box self_assignment::SelfAssignment);
     store.register_late_pass(|| box manual_unwrap_or::ManualUnwrapOr);
+    store.register_late_pass(|| box manual_ok_or::ManualOkOr);
     store.register_late_pass(|| box float_equality_without_abs::FloatEqualityWithoutAbs);
     store.register_late_pass(|| box async_yields_async::AsyncYieldsAsync);
     store.register_late_pass(|| box manual_strip::ManualStrip);
@@ -1229,6 +1232,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
         LintId::of(&loops::EXPLICIT_INTO_ITER_LOOP),
         LintId::of(&loops::EXPLICIT_ITER_LOOP),
         LintId::of(&macro_use::MACRO_USE_IMPORTS),
+        LintId::of(&manual_ok_or::MANUAL_OK_OR),
         LintId::of(&map_err_ignore::MAP_ERR_IGNORE),
         LintId::of(&match_on_vec_items::MATCH_ON_VEC_ITEMS),
         LintId::of(&matches::MATCH_BOOL),
diff --git a/clippy_lints/src/manual_ok_or.rs b/clippy_lints/src/manual_ok_or.rs
new file mode 100644
index 00000000000..38298eb813a
--- /dev/null
+++ b/clippy_lints/src/manual_ok_or.rs
@@ -0,0 +1,97 @@
+use crate::utils;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{def, Expr, ExprKind, PatKind, QPath};
+use rustc_lint::LintContext;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::lint::in_external_macro;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+    /// **What it does:**
+    /// Finds patterns that reimplement `Option::ok_or`.
+    ///
+    /// **Why is this bad?**
+    /// Concise code helps focusing on behavior instead of boilerplate.
+    ///
+    /// **Known problems:** None.
+    ///
+    /// **Examples:**
+    /// ```rust
+    /// let foo: Option<i32> = None;
+    /// foo.map_or(Err("error"), |v| Ok(v));
+    ///
+    /// let foo: Option<i32> = None;
+    /// foo.map_or(Err("error"), |v| Ok(v));
+    /// ```
+    ///
+    /// Use instead:
+    /// ```rust
+    /// let foo: Option<i32> = None;
+    /// foo.ok_or("error");
+    /// ```
+    pub MANUAL_OK_OR,
+    pedantic,
+    "finds patterns that can be encoded more concisely with `Option::ok_or`"
+}
+
+declare_lint_pass!(ManualOkOr => [MANUAL_OK_OR]);
+
+impl LateLintPass<'_> for ManualOkOr {
+    fn check_expr(&mut self, cx: &LateContext<'tcx>, scrutinee: &'tcx Expr<'tcx>) {
+        if in_external_macro(cx.sess(), scrutinee.span) {
+            return;
+        }
+
+        if_chain! {
+            if let ExprKind::MethodCall(method_segment, _, args, _) = scrutinee.kind;
+            if method_segment.ident.name == sym!(map_or);
+            if args.len() == 3;
+            let method_receiver = &args[0];
+            let ty = cx.typeck_results().expr_ty(method_receiver);
+            if utils::is_type_diagnostic_item(cx, ty, sym!(option_type));
+            let or_expr = &args[1];
+            if is_ok_wrapping(cx, &args[2]);
+            if let ExprKind::Call(Expr { kind: ExprKind::Path(err_path), .. }, &[ref err_arg]) = or_expr.kind;
+            if utils::match_qpath(err_path, &utils::paths::RESULT_ERR);
+            if let Some(method_receiver_snippet) = utils::snippet_opt(cx, method_receiver.span);
+            if let Some(err_arg_snippet) = utils::snippet_opt(cx, err_arg.span);
+            if let Some(indent) = utils::indent_of(cx, scrutinee.span);
+            then {
+                let reindented_err_arg_snippet =
+                    utils::reindent_multiline(err_arg_snippet.into(), true, Some(indent + 4));
+                utils::span_lint_and_sugg(
+                    cx,
+                    MANUAL_OK_OR,
+                    scrutinee.span,
+                    "this pattern reimplements `Option::ok_or`",
+                    "replace with",
+                    format!(
+                        "{}.ok_or({})",
+                        method_receiver_snippet,
+                        reindented_err_arg_snippet
+                    ),
+                    Applicability::MachineApplicable,
+                );
+            }
+        }
+    }
+}
+
+fn is_ok_wrapping(cx: &LateContext<'_>, map_expr: &Expr<'_>) -> bool {
+    if let ExprKind::Path(ref qpath) = map_expr.kind {
+        if utils::match_qpath(qpath, &utils::paths::RESULT_OK) {
+            return true;
+        }
+    }
+    if_chain! {
+        if let ExprKind::Closure(_, _, body_id, ..) = map_expr.kind;
+        let body = cx.tcx.hir().body(body_id);
+        if let PatKind::Binding(_, param_id, ..) = body.params[0].pat.kind;
+        if let ExprKind::Call(Expr { kind: ExprKind::Path(ok_path), .. }, &[ref ok_arg]) = body.value.kind;
+        if utils::match_qpath(ok_path, &utils::paths::RESULT_OK);
+        if let ExprKind::Path(QPath::Resolved(_, ok_arg_path)) = ok_arg.kind;
+        if let def::Res::Local(ok_arg_path_id) = ok_arg_path.res;
+        then { param_id == ok_arg_path_id } else { false }
+    }
+}
diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs
index 6272ce45efb..f438da00720 100644
--- a/src/lintlist/mod.rs
+++ b/src/lintlist/mod.rs
@@ -1174,6 +1174,13 @@ vec![
         module: "manual_non_exhaustive",
     },
     Lint {
+        name: "manual_ok_or",
+        group: "pedantic",
+        desc: "finds patterns that can be encoded more concisely with `Option::ok_or`",
+        deprecation: None,
+        module: "manual_ok_or",
+    },
+    Lint {
         name: "manual_range_contains",
         group: "style",
         desc: "manually reimplementing {`Range`, `RangeInclusive`}`::contains`",
diff --git a/tests/ui/manual_ok_or.fixed b/tests/ui/manual_ok_or.fixed
new file mode 100644
index 00000000000..b42e94bd727
--- /dev/null
+++ b/tests/ui/manual_ok_or.fixed
@@ -0,0 +1,40 @@
+// run-rustfix
+#![warn(clippy::manual_ok_or)]
+#![allow(clippy::blacklisted_name)]
+#![allow(clippy::redundant_closure)]
+#![allow(dead_code)]
+#![allow(unused_must_use)]
+
+fn main() {
+    // basic case
+    let foo: Option<i32> = None;
+    foo.ok_or("error");
+
+    // eta expansion case
+    foo.ok_or("error");
+
+    // turbo fish syntax
+    None::<i32>.ok_or("error");
+
+    // multiline case
+    #[rustfmt::skip]
+    foo.ok_or(&format!(
+        "{}{}{}{}{}{}{}",
+        "Alice", "Bob", "Sarah", "Marc", "Sandra", "Eric", "Jenifer"));
+
+    // not applicable, closure isn't direct `Ok` wrapping
+    foo.map_or(Err("error"), |v| Ok(v + 1));
+
+    // not applicable, or side isn't `Result::Err`
+    foo.map_or(Ok::<i32, &str>(1), |v| Ok(v));
+
+    // not applicatble, expr is not a `Result` value
+    foo.map_or(42, |v| v);
+
+    // TODO patterns not covered yet
+    match foo {
+        Some(v) => Ok(v),
+        None => Err("error"),
+    };
+    foo.map_or_else(|| Err("error"), |v| Ok(v));
+}
diff --git a/tests/ui/manual_ok_or.rs b/tests/ui/manual_ok_or.rs
new file mode 100644
index 00000000000..e5a6056fbf5
--- /dev/null
+++ b/tests/ui/manual_ok_or.rs
@@ -0,0 +1,44 @@
+// run-rustfix
+#![warn(clippy::manual_ok_or)]
+#![allow(clippy::blacklisted_name)]
+#![allow(clippy::redundant_closure)]
+#![allow(dead_code)]
+#![allow(unused_must_use)]
+
+fn main() {
+    // basic case
+    let foo: Option<i32> = None;
+    foo.map_or(Err("error"), |v| Ok(v));
+
+    // eta expansion case
+    foo.map_or(Err("error"), Ok);
+
+    // turbo fish syntax
+    None::<i32>.map_or(Err("error"), |v| Ok(v));
+
+    // multiline case
+    #[rustfmt::skip]
+    foo.map_or(Err::<i32, &str>(
+        &format!(
+            "{}{}{}{}{}{}{}",
+            "Alice", "Bob", "Sarah", "Marc", "Sandra", "Eric", "Jenifer")
+        ),
+        |v| Ok(v),
+    );
+
+    // not applicable, closure isn't direct `Ok` wrapping
+    foo.map_or(Err("error"), |v| Ok(v + 1));
+
+    // not applicable, or side isn't `Result::Err`
+    foo.map_or(Ok::<i32, &str>(1), |v| Ok(v));
+
+    // not applicatble, expr is not a `Result` value
+    foo.map_or(42, |v| v);
+
+    // TODO patterns not covered yet
+    match foo {
+        Some(v) => Ok(v),
+        None => Err("error"),
+    };
+    foo.map_or_else(|| Err("error"), |v| Ok(v));
+}
diff --git a/tests/ui/manual_ok_or.stderr b/tests/ui/manual_ok_or.stderr
new file mode 100644
index 00000000000..8ea10ac5436
--- /dev/null
+++ b/tests/ui/manual_ok_or.stderr
@@ -0,0 +1,41 @@
+error: this pattern reimplements `Option::ok_or`
+  --> $DIR/manual_ok_or.rs:11:5
+   |
+LL |     foo.map_or(Err("error"), |v| Ok(v));
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `foo.ok_or("error")`
+   |
+   = note: `-D clippy::manual-ok-or` implied by `-D warnings`
+
+error: this pattern reimplements `Option::ok_or`
+  --> $DIR/manual_ok_or.rs:14:5
+   |
+LL |     foo.map_or(Err("error"), Ok);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `foo.ok_or("error")`
+
+error: this pattern reimplements `Option::ok_or`
+  --> $DIR/manual_ok_or.rs:17:5
+   |
+LL |     None::<i32>.map_or(Err("error"), |v| Ok(v));
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `None::<i32>.ok_or("error")`
+
+error: this pattern reimplements `Option::ok_or`
+  --> $DIR/manual_ok_or.rs:21:5
+   |
+LL | /     foo.map_or(Err::<i32, &str>(
+LL | |         &format!(
+LL | |             "{}{}{}{}{}{}{}",
+LL | |             "Alice", "Bob", "Sarah", "Marc", "Sandra", "Eric", "Jenifer")
+LL | |         ),
+LL | |         |v| Ok(v),
+LL | |     );
+   | |_____^
+   |
+help: replace with
+   |
+LL |     foo.ok_or(&format!(
+LL |         "{}{}{}{}{}{}{}",
+LL |         "Alice", "Bob", "Sarah", "Marc", "Sandra", "Eric", "Jenifer"));
+   |
+
+error: aborting due to 4 previous errors
+