about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2023-11-25 11:35:46 +0000
committerbors <bors@rust-lang.org>2023-11-25 11:35:46 +0000
commit3664d6328d12cdb1dc6dd4b226d2c635b7400bcc (patch)
tree92573a2e398c5080d9fa5648315e44f08b8ba6aa
parentfbf13cea16b98fd65487559fb2b82c6cf1207c73 (diff)
parentef38969e06127a664ea532e0fd154638b3431ccb (diff)
downloadrust-3664d6328d12cdb1dc6dd4b226d2c635b7400bcc.tar.gz
rust-3664d6328d12cdb1dc6dd4b226d2c635b7400bcc.zip
Auto merge of #11864 - GuillaumeGomez:option_map_or_err_ok, r=flip1995
Create new lint `option_map_or_err_ok`

Fixes #10045.

For the following code:

```rust
let opt = Some(1);
opt.map_or(Err("error"), Ok);
```

It suggests to instead write:

```rust
let opt = Some(1);
opt.ok_or("error");
```

r? `@flip1995`

changelog: Create new lint `option_map_or_err_ok`
-rw-r--r--CHANGELOG.md1
-rw-r--r--clippy_lints/src/declared_lints.rs1
-rw-r--r--clippy_lints/src/methods/mod.rs28
-rw-r--r--clippy_lints/src/methods/option_map_or_err_ok.rs41
-rw-r--r--tests/ui/manual_ok_or.stderr11
-rw-r--r--tests/ui/option_map_or_err_ok.fixed7
-rw-r--r--tests/ui/option_map_or_err_ok.rs7
-rw-r--r--tests/ui/option_map_or_err_ok.stderr11
8 files changed, 106 insertions, 1 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 11a2d1e7ef9..abe975fa42b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5382,6 +5382,7 @@ Released 2018-09-13
 [`option_expect_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_expect_used
 [`option_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_filter_map
 [`option_if_let_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_if_let_else
+[`option_map_or_err_ok`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_or_err_ok
 [`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/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs
index 7d4ab9fde1f..5a109fcc2cc 100644
--- a/clippy_lints/src/declared_lints.rs
+++ b/clippy_lints/src/declared_lints.rs
@@ -405,6 +405,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
     crate::methods::OK_EXPECT_INFO,
     crate::methods::OPTION_AS_REF_DEREF_INFO,
     crate::methods::OPTION_FILTER_MAP_INFO,
+    crate::methods::OPTION_MAP_OR_ERR_OK_INFO,
     crate::methods::OPTION_MAP_OR_NONE_INFO,
     crate::methods::OR_FUN_CALL_INFO,
     crate::methods::OR_THEN_UNWRAP_INFO,
diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs
index 71de77acfc1..7ce14242cae 100644
--- a/clippy_lints/src/methods/mod.rs
+++ b/clippy_lints/src/methods/mod.rs
@@ -70,6 +70,7 @@ mod obfuscated_if_else;
 mod ok_expect;
 mod open_options;
 mod option_as_ref_deref;
+mod option_map_or_err_ok;
 mod option_map_or_none;
 mod option_map_unwrap_or;
 mod or_fun_call;
@@ -3726,6 +3727,31 @@ declare_clippy_lint! {
     "calls to `Path::join` which will overwrite the original path"
 }
 
+declare_clippy_lint! {
+    /// ### What it does
+    /// Checks for usage of `_.map_or(Err(_), Ok)`.
+    ///
+    /// ### Why is this bad?
+    /// Readability, this can be written more concisely as
+    /// `_.ok_or(_)`.
+    ///
+    /// ### Example
+    /// ```no_run
+    /// # let opt = Some(1);
+    /// opt.map_or(Err("error"), Ok);
+    /// ```
+    ///
+    /// Use instead:
+    /// ```no_run
+    /// # let opt = Some(1);
+    /// opt.ok_or("error");
+    /// ```
+    #[clippy::version = "1.76.0"]
+    pub OPTION_MAP_OR_ERR_OK,
+    style,
+    "using `Option.map_or(Err(_), Ok)`, which is more succinctly expressed as `Option.ok_or(_)`"
+}
+
 pub struct Methods {
     avoid_breaking_exported_api: bool,
     msrv: Msrv,
@@ -3876,6 +3902,7 @@ impl_lint_pass!(Methods => [
     WAKER_CLONE_WAKE,
     UNNECESSARY_FALLIBLE_CONVERSIONS,
     JOIN_ABSOLUTE_PATHS,
+    OPTION_MAP_OR_ERR_OK,
 ]);
 
 /// Extracts a method call name, args, and `Span` of the method name.
@@ -4335,6 +4362,7 @@ impl Methods {
                 ("map_or", [def, map]) => {
                     option_map_or_none::check(cx, expr, recv, def, map);
                     manual_ok_or::check(cx, expr, recv, def, map);
+                    option_map_or_err_ok::check(cx, expr, recv, def, map);
                 },
                 ("map_or_else", [def, map]) => {
                     result_map_or_else_none::check(cx, expr, recv, def, map);
diff --git a/clippy_lints/src/methods/option_map_or_err_ok.rs b/clippy_lints/src/methods/option_map_or_err_ok.rs
new file mode 100644
index 00000000000..91e39d5a1cd
--- /dev/null
+++ b/clippy_lints/src/methods/option_map_or_err_ok.rs
@@ -0,0 +1,41 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::{is_res_lang_ctor, path_res};
+use rustc_errors::Applicability;
+use rustc_hir::LangItem::{ResultErr, ResultOk};
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::LateContext;
+use rustc_span::symbol::sym;
+
+use super::OPTION_MAP_OR_ERR_OK;
+
+pub(super) fn check<'tcx>(
+    cx: &LateContext<'tcx>,
+    expr: &'tcx Expr<'tcx>,
+    recv: &'tcx Expr<'_>,
+    or_expr: &'tcx Expr<'_>,
+    map_expr: &'tcx Expr<'_>,
+) {
+    // We check that it's called on an `Option` type.
+    if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Option)
+        // We check that first we pass an `Err`.
+        && let ExprKind::Call(call, &[arg]) = or_expr.kind
+        && is_res_lang_ctor(cx, path_res(cx, call), ResultErr)
+        // And finally we check that it is mapped as `Ok`.
+        && is_res_lang_ctor(cx, path_res(cx, map_expr), ResultOk)
+    {
+        let msg = "called `map_or(Err(_), Ok)` on an `Option` value";
+        let self_snippet = snippet(cx, recv.span, "..");
+        let err_snippet = snippet(cx, arg.span, "..");
+        span_lint_and_sugg(
+            cx,
+            OPTION_MAP_OR_ERR_OK,
+            expr.span,
+            msg,
+            "try using `ok_or` instead",
+            format!("{self_snippet}.ok_or({err_snippet})"),
+            Applicability::MachineApplicable,
+        );
+    }
+}
diff --git a/tests/ui/manual_ok_or.stderr b/tests/ui/manual_ok_or.stderr
index ddb2cf261e4..b277d22e59b 100644
--- a/tests/ui/manual_ok_or.stderr
+++ b/tests/ui/manual_ok_or.stderr
@@ -13,6 +13,15 @@ error: this pattern reimplements `Option::ok_or`
 LL |     foo.map_or(Err("error"), Ok);
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `foo.ok_or("error")`
 
+error: called `map_or(Err(_), Ok)` on an `Option` value
+  --> $DIR/manual_ok_or.rs:14:5
+   |
+LL |     foo.map_or(Err("error"), Ok);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `ok_or` instead: `foo.ok_or("error")`
+   |
+   = note: `-D clippy::option-map-or-err-ok` implied by `-D warnings`
+   = help: to override `-D warnings` add `#[allow(clippy::option_map_or_err_ok)]`
+
 error: this pattern reimplements `Option::ok_or`
   --> $DIR/manual_ok_or.rs:17:5
    |
@@ -38,5 +47,5 @@ LL +         "{}{}{}{}{}{}{}",
 LL ~         "Alice", "Bob", "Sarah", "Marc", "Sandra", "Eric", "Jenifer"));
    |
 
-error: aborting due to 4 previous errors
+error: aborting due to 5 previous errors
 
diff --git a/tests/ui/option_map_or_err_ok.fixed b/tests/ui/option_map_or_err_ok.fixed
new file mode 100644
index 00000000000..131f4b2093e
--- /dev/null
+++ b/tests/ui/option_map_or_err_ok.fixed
@@ -0,0 +1,7 @@
+#![warn(clippy::option_map_or_err_ok)]
+
+fn main() {
+    let x = Some("a");
+    let _ = x.ok_or("a");
+    //~^ ERROR: called `map_or(Err(_), Ok)` on an `Option` value
+}
diff --git a/tests/ui/option_map_or_err_ok.rs b/tests/ui/option_map_or_err_ok.rs
new file mode 100644
index 00000000000..0f07a592ae5
--- /dev/null
+++ b/tests/ui/option_map_or_err_ok.rs
@@ -0,0 +1,7 @@
+#![warn(clippy::option_map_or_err_ok)]
+
+fn main() {
+    let x = Some("a");
+    let _ = x.map_or(Err("a"), Ok);
+    //~^ ERROR: called `map_or(Err(_), Ok)` on an `Option` value
+}
diff --git a/tests/ui/option_map_or_err_ok.stderr b/tests/ui/option_map_or_err_ok.stderr
new file mode 100644
index 00000000000..8476881aef7
--- /dev/null
+++ b/tests/ui/option_map_or_err_ok.stderr
@@ -0,0 +1,11 @@
+error: called `map_or(Err(_), Ok)` on an `Option` value
+  --> $DIR/option_map_or_err_ok.rs:5:13
+   |
+LL |     let _ = x.map_or(Err("a"), Ok);
+   |             ^^^^^^^^^^^^^^^^^^^^^^ help: try using `ok_or` instead: `x.ok_or("a")`
+   |
+   = note: `-D clippy::option-map-or-err-ok` implied by `-D warnings`
+   = help: to override `-D warnings` add `#[allow(clippy::option_map_or_err_ok)]`
+
+error: aborting due to previous error
+