Skip to content

Commit 111b902

Browse files
committed
add manual_ok_or lint
1 parent afbac89 commit 111b902

File tree

7 files changed

+234
-0
lines changed

7 files changed

+234
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1795,6 +1795,7 @@ Released 2018-09-13
17951795
[`manual_async_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_async_fn
17961796
[`manual_memcpy`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_memcpy
17971797
[`manual_non_exhaustive`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_non_exhaustive
1798+
[`manual_ok_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ok_or
17981799
[`manual_range_contains`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_contains
17991800
[`manual_saturating_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_saturating_arithmetic
18001801
[`manual_strip`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_strip

clippy_lints/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ mod macro_use;
233233
mod main_recursion;
234234
mod manual_async_fn;
235235
mod manual_non_exhaustive;
236+
mod manual_ok_or;
236237
mod manual_strip;
237238
mod manual_unwrap_or;
238239
mod map_clone;
@@ -645,6 +646,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
645646
&main_recursion::MAIN_RECURSION,
646647
&manual_async_fn::MANUAL_ASYNC_FN,
647648
&manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE,
649+
&manual_ok_or::MANUAL_OK_OR,
648650
&manual_strip::MANUAL_STRIP,
649651
&manual_unwrap_or::MANUAL_UNWRAP_OR,
650652
&map_clone::MAP_CLONE,
@@ -1140,6 +1142,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
11401142
store.register_late_pass(|| box unwrap_in_result::UnwrapInResult);
11411143
store.register_late_pass(|| box self_assignment::SelfAssignment);
11421144
store.register_late_pass(|| box manual_unwrap_or::ManualUnwrapOr);
1145+
store.register_late_pass(|| box manual_ok_or::ManualOkOr);
11431146
store.register_late_pass(|| box float_equality_without_abs::FloatEqualityWithoutAbs);
11441147
store.register_late_pass(|| box async_yields_async::AsyncYieldsAsync);
11451148
store.register_late_pass(|| box manual_strip::ManualStrip);
@@ -1229,6 +1232,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
12291232
LintId::of(&loops::EXPLICIT_INTO_ITER_LOOP),
12301233
LintId::of(&loops::EXPLICIT_ITER_LOOP),
12311234
LintId::of(&macro_use::MACRO_USE_IMPORTS),
1235+
LintId::of(&manual_ok_or::MANUAL_OK_OR),
12321236
LintId::of(&map_err_ignore::MAP_ERR_IGNORE),
12331237
LintId::of(&match_on_vec_items::MATCH_ON_VEC_ITEMS),
12341238
LintId::of(&matches::MATCH_BOOL),

clippy_lints/src/manual_ok_or.rs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
use crate::utils;
2+
use if_chain::if_chain;
3+
use rustc_errors::Applicability;
4+
use rustc_hir::{def, Expr, ExprKind, PatKind, QPath};
5+
use rustc_lint::LintContext;
6+
use rustc_lint::{LateContext, LateLintPass};
7+
use rustc_middle::lint::in_external_macro;
8+
use rustc_session::{declare_lint_pass, declare_tool_lint};
9+
10+
declare_clippy_lint! {
11+
/// **What it does:**
12+
/// Finds patterns that reimplement `Option::ok_or`.
13+
///
14+
/// **Why is this bad?**
15+
/// Concise code helps focusing on behavior instead of boilerplate.
16+
///
17+
/// **Known problems:** None.
18+
///
19+
/// **Examples:**
20+
/// ```rust
21+
/// let foo: Option<i32> = None;
22+
/// foo.map_or(Err("error"), |v| Ok(v));
23+
///
24+
/// let foo: Option<i32> = None;
25+
/// foo.map_or(Err("error"), |v| Ok(v));
26+
/// ```
27+
///
28+
/// Use instead:
29+
/// ```rust
30+
/// let foo: Option<i32> = None;
31+
/// foo.ok_or("error");
32+
/// ```
33+
pub MANUAL_OK_OR,
34+
pedantic,
35+
"finds patterns that can be encoded more concisely with `Option::ok_or`"
36+
}
37+
38+
declare_lint_pass!(ManualOkOr => [MANUAL_OK_OR]);
39+
40+
impl LateLintPass<'_> for ManualOkOr {
41+
fn check_expr(&mut self, cx: &LateContext<'tcx>, scrutinee: &'tcx Expr<'tcx>) {
42+
if in_external_macro(cx.sess(), scrutinee.span) {
43+
return;
44+
}
45+
46+
if_chain! {
47+
if let ExprKind::MethodCall(method_segment, _, args, _) = scrutinee.kind;
48+
if method_segment.ident.name == sym!(map_or);
49+
if args.len() == 3;
50+
let method_receiver = &args[0];
51+
let ty = cx.typeck_results().expr_ty(method_receiver);
52+
if utils::is_type_diagnostic_item(cx, ty, sym!(option_type));
53+
let or_expr = &args[1];
54+
if is_ok_wrapping(cx, &args[2]);
55+
if let ExprKind::Call(Expr { kind: ExprKind::Path(err_path), .. }, &[ref err_arg]) = or_expr.kind;
56+
if utils::match_qpath(err_path, &utils::paths::RESULT_ERR);
57+
if let Some(method_receiver_snippet) = utils::snippet_opt(cx, method_receiver.span);
58+
if let Some(err_arg_snippet) = utils::snippet_opt(cx, err_arg.span);
59+
if let Some(indent) = utils::indent_of(cx, scrutinee.span);
60+
then {
61+
let reindented_err_arg_snippet =
62+
utils::reindent_multiline(err_arg_snippet.into(), true, Some(indent + 4));
63+
utils::span_lint_and_sugg(
64+
cx,
65+
MANUAL_OK_OR,
66+
scrutinee.span,
67+
"this pattern reimplements `Option::ok_or`",
68+
"replace with",
69+
format!(
70+
"{}.ok_or({})",
71+
method_receiver_snippet,
72+
reindented_err_arg_snippet
73+
),
74+
Applicability::MachineApplicable,
75+
);
76+
}
77+
}
78+
}
79+
}
80+
81+
fn is_ok_wrapping(cx: &LateContext<'_>, map_expr: &Expr<'_>) -> bool {
82+
if let ExprKind::Path(ref qpath) = map_expr.kind {
83+
if utils::match_qpath(qpath, &utils::paths::RESULT_OK) {
84+
return true;
85+
}
86+
}
87+
if_chain! {
88+
if let ExprKind::Closure(_, _, body_id, ..) = map_expr.kind;
89+
let body = cx.tcx.hir().body(body_id);
90+
if let PatKind::Binding(_, param_id, ..) = body.params[0].pat.kind;
91+
if let ExprKind::Call(Expr { kind: ExprKind::Path(ok_path), .. }, &[ref ok_arg]) = body.value.kind;
92+
if utils::match_qpath(ok_path, &utils::paths::RESULT_OK);
93+
if let ExprKind::Path(QPath::Resolved(_, ok_arg_path)) = ok_arg.kind;
94+
if let def::Res::Local(ok_arg_path_id) = ok_arg_path.res;
95+
then { param_id == ok_arg_path_id } else { false }
96+
}
97+
}

src/lintlist/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1173,6 +1173,13 @@ vec![
11731173
deprecation: None,
11741174
module: "manual_non_exhaustive",
11751175
},
1176+
Lint {
1177+
name: "manual_ok_or",
1178+
group: "pedantic",
1179+
desc: "finds patterns that can be encoded more concisely with `Option::ok_or`",
1180+
deprecation: None,
1181+
module: "manual_ok_or",
1182+
},
11761183
Lint {
11771184
name: "manual_range_contains",
11781185
group: "style",

tests/ui/manual_ok_or.fixed

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// run-rustfix
2+
#![warn(clippy::manual_ok_or)]
3+
#![allow(clippy::blacklisted_name)]
4+
#![allow(clippy::redundant_closure)]
5+
#![allow(dead_code)]
6+
#![allow(unused_must_use)]
7+
8+
fn main() {
9+
// basic case
10+
let foo: Option<i32> = None;
11+
foo.ok_or("error");
12+
13+
// eta expansion case
14+
foo.ok_or("error");
15+
16+
// turbo fish syntax
17+
None::<i32>.ok_or("error");
18+
19+
// multiline case
20+
#[rustfmt::skip]
21+
foo.ok_or(&format!(
22+
"{}{}{}{}{}{}{}",
23+
"Alice", "Bob", "Sarah", "Marc", "Sandra", "Eric", "Jenifer"));
24+
25+
// not applicable, closure isn't direct `Ok` wrapping
26+
foo.map_or(Err("error"), |v| Ok(v + 1));
27+
28+
// not applicable, or side isn't `Result::Err`
29+
foo.map_or(Ok::<i32, &str>(1), |v| Ok(v));
30+
31+
// not applicatble, expr is not a `Result` value
32+
foo.map_or(42, |v| v);
33+
34+
// TODO patterns not covered yet
35+
match foo {
36+
Some(v) => Ok(v),
37+
None => Err("error"),
38+
};
39+
foo.map_or_else(|| Err("error"), |v| Ok(v));
40+
}

tests/ui/manual_ok_or.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// run-rustfix
2+
#![warn(clippy::manual_ok_or)]
3+
#![allow(clippy::blacklisted_name)]
4+
#![allow(clippy::redundant_closure)]
5+
#![allow(dead_code)]
6+
#![allow(unused_must_use)]
7+
8+
fn main() {
9+
// basic case
10+
let foo: Option<i32> = None;
11+
foo.map_or(Err("error"), |v| Ok(v));
12+
13+
// eta expansion case
14+
foo.map_or(Err("error"), Ok);
15+
16+
// turbo fish syntax
17+
None::<i32>.map_or(Err("error"), |v| Ok(v));
18+
19+
// multiline case
20+
#[rustfmt::skip]
21+
foo.map_or(Err::<i32, &str>(
22+
&format!(
23+
"{}{}{}{}{}{}{}",
24+
"Alice", "Bob", "Sarah", "Marc", "Sandra", "Eric", "Jenifer")
25+
),
26+
|v| Ok(v),
27+
);
28+
29+
// not applicable, closure isn't direct `Ok` wrapping
30+
foo.map_or(Err("error"), |v| Ok(v + 1));
31+
32+
// not applicable, or side isn't `Result::Err`
33+
foo.map_or(Ok::<i32, &str>(1), |v| Ok(v));
34+
35+
// not applicatble, expr is not a `Result` value
36+
foo.map_or(42, |v| v);
37+
38+
// TODO patterns not covered yet
39+
match foo {
40+
Some(v) => Ok(v),
41+
None => Err("error"),
42+
};
43+
foo.map_or_else(|| Err("error"), |v| Ok(v));
44+
}

tests/ui/manual_ok_or.stderr

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
error: this pattern reimplements `Option::ok_or`
2+
--> $DIR/manual_ok_or.rs:11:5
3+
|
4+
LL | foo.map_or(Err("error"), |v| Ok(v));
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `foo.ok_or("error")`
6+
|
7+
= note: `-D clippy::manual-ok-or` implied by `-D warnings`
8+
9+
error: this pattern reimplements `Option::ok_or`
10+
--> $DIR/manual_ok_or.rs:14:5
11+
|
12+
LL | foo.map_or(Err("error"), Ok);
13+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `foo.ok_or("error")`
14+
15+
error: this pattern reimplements `Option::ok_or`
16+
--> $DIR/manual_ok_or.rs:17:5
17+
|
18+
LL | None::<i32>.map_or(Err("error"), |v| Ok(v));
19+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `None::<i32>.ok_or("error")`
20+
21+
error: this pattern reimplements `Option::ok_or`
22+
--> $DIR/manual_ok_or.rs:21:5
23+
|
24+
LL | / foo.map_or(Err::<i32, &str>(
25+
LL | | &format!(
26+
LL | | "{}{}{}{}{}{}{}",
27+
LL | | "Alice", "Bob", "Sarah", "Marc", "Sandra", "Eric", "Jenifer")
28+
LL | | ),
29+
LL | | |v| Ok(v),
30+
LL | | );
31+
| |_____^
32+
|
33+
help: replace with
34+
|
35+
LL | foo.ok_or(&format!(
36+
LL | "{}{}{}{}{}{}{}",
37+
LL | "Alice", "Bob", "Sarah", "Marc", "Sandra", "Eric", "Jenifer"));
38+
|
39+
40+
error: aborting due to 4 previous errors
41+

0 commit comments

Comments
 (0)