Skip to content

Commit 4a07662

Browse files
committed
Auto merge of #8561 - FoseFx:use_unwrap_or, r=xFrednet
add `or_then_unwrap` Closes #8557 changelog: New lint [`or_then_unwrap`]
2 parents ff0a368 + 765cce1 commit 4a07662

9 files changed

+239
-0
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -3367,6 +3367,7 @@ Released 2018-09-13
33673367
[`option_map_unit_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unit_fn
33683368
[`option_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_option
33693369
[`or_fun_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#or_fun_call
3370+
[`or_then_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#or_then_unwrap
33703371
[`out_of_bounds_indexing`]: https://rust-lang.github.io/rust-clippy/master/index.html#out_of_bounds_indexing
33713372
[`overflow_check_conditional`]: https://rust-lang.github.io/rust-clippy/master/index.html#overflow_check_conditional
33723373
[`panic`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic

clippy_lints/src/lib.register_all.rs

+1
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
182182
LintId::of(methods::OPTION_FILTER_MAP),
183183
LintId::of(methods::OPTION_MAP_OR_NONE),
184184
LintId::of(methods::OR_FUN_CALL),
185+
LintId::of(methods::OR_THEN_UNWRAP),
185186
LintId::of(methods::RESULT_MAP_OR_INTO_OPTION),
186187
LintId::of(methods::SEARCH_IS_SOME),
187188
LintId::of(methods::SHOULD_IMPLEMENT_TRAIT),

clippy_lints/src/lib.register_complexity.rs

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec!
4747
LintId::of(methods::NEEDLESS_SPLITN),
4848
LintId::of(methods::OPTION_AS_REF_DEREF),
4949
LintId::of(methods::OPTION_FILTER_MAP),
50+
LintId::of(methods::OR_THEN_UNWRAP),
5051
LintId::of(methods::SEARCH_IS_SOME),
5152
LintId::of(methods::SKIP_WHILE_NEXT),
5253
LintId::of(methods::UNNECESSARY_FILTER_MAP),

clippy_lints/src/lib.register_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,7 @@ store.register_lints(&[
320320
methods::OPTION_FILTER_MAP,
321321
methods::OPTION_MAP_OR_NONE,
322322
methods::OR_FUN_CALL,
323+
methods::OR_THEN_UNWRAP,
323324
methods::RESULT_MAP_OR_INTO_OPTION,
324325
methods::SEARCH_IS_SOME,
325326
methods::SHOULD_IMPLEMENT_TRAIT,

clippy_lints/src/methods/mod.rs

+41
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ mod option_as_ref_deref;
4545
mod option_map_or_none;
4646
mod option_map_unwrap_or;
4747
mod or_fun_call;
48+
mod or_then_unwrap;
4849
mod search_is_some;
4950
mod single_char_add_str;
5051
mod single_char_insert_string;
@@ -778,6 +779,42 @@ declare_clippy_lint! {
778779
"using any `*or` method with a function call, which suggests `*or_else`"
779780
}
780781

782+
declare_clippy_lint! {
783+
/// ### What it does
784+
/// Checks for `.or(…).unwrap()` calls to Options and Results.
785+
///
786+
/// ### Why is this bad?
787+
/// You should use `.unwrap_or(…)` instead for clarity.
788+
///
789+
/// ### Example
790+
/// ```rust
791+
/// # let fallback = "fallback";
792+
/// // Result
793+
/// # type Error = &'static str;
794+
/// # let result: Result<&str, Error> = Err("error");
795+
/// let value = result.or::<Error>(Ok(fallback)).unwrap();
796+
///
797+
/// // Option
798+
/// # let option: Option<&str> = None;
799+
/// let value = option.or(Some(fallback)).unwrap();
800+
/// ```
801+
/// Use instead:
802+
/// ```rust
803+
/// # let fallback = "fallback";
804+
/// // Result
805+
/// # let result: Result<&str, &str> = Err("error");
806+
/// let value = result.unwrap_or(fallback);
807+
///
808+
/// // Option
809+
/// # let option: Option<&str> = None;
810+
/// let value = option.unwrap_or(fallback);
811+
/// ```
812+
#[clippy::version = "1.61.0"]
813+
pub OR_THEN_UNWRAP,
814+
complexity,
815+
"checks for `.or(…).unwrap()` calls to Options and Results."
816+
}
817+
781818
declare_clippy_lint! {
782819
/// ### What it does
783820
/// Checks for calls to `.expect(&format!(...))`, `.expect(foo(..))`,
@@ -2039,6 +2076,7 @@ impl_lint_pass!(Methods => [
20392076
OPTION_MAP_OR_NONE,
20402077
BIND_INSTEAD_OF_MAP,
20412078
OR_FUN_CALL,
2079+
OR_THEN_UNWRAP,
20422080
EXPECT_FUN_CALL,
20432081
CHARS_NEXT_CMP,
20442082
CHARS_LAST_CMP,
@@ -2474,6 +2512,9 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
24742512
Some(("get_mut", [recv, get_arg], _)) => {
24752513
get_unwrap::check(cx, expr, recv, get_arg, true);
24762514
},
2515+
Some(("or", [recv, or_arg], or_span)) => {
2516+
or_then_unwrap::check(cx, expr, recv, or_arg, or_span);
2517+
},
24772518
_ => {},
24782519
}
24792520
unwrap_used::check(cx, expr, recv);
+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
use clippy_utils::source::snippet_with_applicability;
2+
use clippy_utils::ty::is_type_diagnostic_item;
3+
use clippy_utils::{diagnostics::span_lint_and_sugg, is_lang_ctor};
4+
use rustc_errors::Applicability;
5+
use rustc_hir::{lang_items::LangItem, Expr, ExprKind};
6+
use rustc_lint::LateContext;
7+
use rustc_span::{sym, Span};
8+
9+
use super::OR_THEN_UNWRAP;
10+
11+
pub(super) fn check<'tcx>(
12+
cx: &LateContext<'tcx>,
13+
unwrap_expr: &Expr<'_>,
14+
recv: &'tcx Expr<'tcx>,
15+
or_arg: &'tcx Expr<'_>,
16+
or_span: Span,
17+
) {
18+
let ty = cx.typeck_results().expr_ty(recv); // get type of x (we later check if it's Option or Result)
19+
let title;
20+
let or_arg_content: Span;
21+
22+
if is_type_diagnostic_item(cx, ty, sym::Option) {
23+
title = "found `.or(Some(…)).unwrap()`";
24+
if let Some(content) = get_content_if_ctor_matches(cx, or_arg, LangItem::OptionSome) {
25+
or_arg_content = content;
26+
} else {
27+
return;
28+
}
29+
} else if is_type_diagnostic_item(cx, ty, sym::Result) {
30+
title = "found `.or(Ok(…)).unwrap()`";
31+
if let Some(content) = get_content_if_ctor_matches(cx, or_arg, LangItem::ResultOk) {
32+
or_arg_content = content;
33+
} else {
34+
return;
35+
}
36+
} else {
37+
// Someone has implemented a struct with .or(...).unwrap() chaining,
38+
// but it's not an Option or a Result, so bail
39+
return;
40+
}
41+
42+
let mut applicability = Applicability::MachineApplicable;
43+
let suggestion = format!(
44+
"unwrap_or({})",
45+
snippet_with_applicability(cx, or_arg_content, "..", &mut applicability)
46+
);
47+
48+
span_lint_and_sugg(
49+
cx,
50+
OR_THEN_UNWRAP,
51+
unwrap_expr.span.with_lo(or_span.lo()),
52+
title,
53+
"try this",
54+
suggestion,
55+
applicability,
56+
);
57+
}
58+
59+
fn get_content_if_ctor_matches(cx: &LateContext<'_>, expr: &Expr<'_>, item: LangItem) -> Option<Span> {
60+
if let ExprKind::Call(some_expr, [arg]) = expr.kind
61+
&& let ExprKind::Path(qpath) = &some_expr.kind
62+
&& is_lang_ctor(cx, qpath, item)
63+
{
64+
Some(arg.span)
65+
} else {
66+
None
67+
}
68+
}

tests/ui/or_then_unwrap.fixed

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// run-rustfix
2+
3+
#![warn(clippy::or_then_unwrap)]
4+
#![allow(clippy::map_identity)]
5+
6+
struct SomeStruct {}
7+
impl SomeStruct {
8+
fn or(self, _: Option<Self>) -> Self {
9+
self
10+
}
11+
fn unwrap(&self) {}
12+
}
13+
14+
struct SomeOtherStruct {}
15+
impl SomeOtherStruct {
16+
fn or(self) -> Self {
17+
self
18+
}
19+
fn unwrap(&self) {}
20+
}
21+
22+
fn main() {
23+
let option: Option<&str> = None;
24+
let _ = option.unwrap_or("fallback"); // should trigger lint
25+
26+
let result: Result<&str, &str> = Err("Error");
27+
let _ = result.unwrap_or("fallback"); // should trigger lint
28+
29+
// as part of a method chain
30+
let option: Option<&str> = None;
31+
let _ = option.map(|v| v).unwrap_or("fallback").to_string().chars(); // should trigger lint
32+
33+
// Not Option/Result
34+
let instance = SomeStruct {};
35+
let _ = instance.or(Some(SomeStruct {})).unwrap(); // should not trigger lint
36+
37+
// or takes no argument
38+
let instance = SomeOtherStruct {};
39+
let _ = instance.or().unwrap(); // should not trigger lint and should not panic
40+
41+
// None in or
42+
let option: Option<&str> = None;
43+
let _ = option.or(None).unwrap(); // should not trigger lint
44+
45+
// Not Err in or
46+
let result: Result<&str, &str> = Err("Error");
47+
let _ = result.or::<&str>(Err("Other Error")).unwrap(); // should not trigger lint
48+
49+
// other function between
50+
let option: Option<&str> = None;
51+
let _ = option.or(Some("fallback")).map(|v| v).unwrap(); // should not trigger lint
52+
}

tests/ui/or_then_unwrap.rs

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// run-rustfix
2+
3+
#![warn(clippy::or_then_unwrap)]
4+
#![allow(clippy::map_identity)]
5+
6+
struct SomeStruct {}
7+
impl SomeStruct {
8+
fn or(self, _: Option<Self>) -> Self {
9+
self
10+
}
11+
fn unwrap(&self) {}
12+
}
13+
14+
struct SomeOtherStruct {}
15+
impl SomeOtherStruct {
16+
fn or(self) -> Self {
17+
self
18+
}
19+
fn unwrap(&self) {}
20+
}
21+
22+
fn main() {
23+
let option: Option<&str> = None;
24+
let _ = option.or(Some("fallback")).unwrap(); // should trigger lint
25+
26+
let result: Result<&str, &str> = Err("Error");
27+
let _ = result.or::<&str>(Ok("fallback")).unwrap(); // should trigger lint
28+
29+
// as part of a method chain
30+
let option: Option<&str> = None;
31+
let _ = option.map(|v| v).or(Some("fallback")).unwrap().to_string().chars(); // should trigger lint
32+
33+
// Not Option/Result
34+
let instance = SomeStruct {};
35+
let _ = instance.or(Some(SomeStruct {})).unwrap(); // should not trigger lint
36+
37+
// or takes no argument
38+
let instance = SomeOtherStruct {};
39+
let _ = instance.or().unwrap(); // should not trigger lint and should not panic
40+
41+
// None in or
42+
let option: Option<&str> = None;
43+
let _ = option.or(None).unwrap(); // should not trigger lint
44+
45+
// Not Err in or
46+
let result: Result<&str, &str> = Err("Error");
47+
let _ = result.or::<&str>(Err("Other Error")).unwrap(); // should not trigger lint
48+
49+
// other function between
50+
let option: Option<&str> = None;
51+
let _ = option.or(Some("fallback")).map(|v| v).unwrap(); // should not trigger lint
52+
}

tests/ui/or_then_unwrap.stderr

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
error: found `.or(Some(…)).unwrap()`
2+
--> $DIR/or_then_unwrap.rs:24:20
3+
|
4+
LL | let _ = option.or(Some("fallback")).unwrap(); // should trigger lint
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or("fallback")`
6+
|
7+
= note: `-D clippy::or-then-unwrap` implied by `-D warnings`
8+
9+
error: found `.or(Ok(…)).unwrap()`
10+
--> $DIR/or_then_unwrap.rs:27:20
11+
|
12+
LL | let _ = result.or::<&str>(Ok("fallback")).unwrap(); // should trigger lint
13+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or("fallback")`
14+
15+
error: found `.or(Some(…)).unwrap()`
16+
--> $DIR/or_then_unwrap.rs:31:31
17+
|
18+
LL | let _ = option.map(|v| v).or(Some("fallback")).unwrap().to_string().chars(); // should trigger lint
19+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or("fallback")`
20+
21+
error: aborting due to 3 previous errors
22+

0 commit comments

Comments
 (0)