Skip to content

Commit 8f7cade

Browse files
committed
Add or_else_then_unwrap
This is the same as or_then_unwrap but for or_else calls with a closure immediately calling `Some`.
1 parent 1c14af8 commit 8f7cade

File tree

7 files changed

+285
-0
lines changed

7 files changed

+285
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6485,6 +6485,7 @@ Released 2018-09-13
64856485
[`option_map_unwrap_or_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unwrap_or_else
64866486
[`option_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_option
64876487
[`option_unwrap_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_unwrap_used
6488+
[`or_else_then_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#or_else_then_unwrap
64886489
[`or_fun_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#or_fun_call
64896490
[`or_then_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#or_then_unwrap
64906491
[`out_of_bounds_indexing`]: https://rust-lang.github.io/rust-clippy/master/index.html#out_of_bounds_indexing

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
444444
crate::methods::OPTION_AS_REF_DEREF_INFO,
445445
crate::methods::OPTION_FILTER_MAP_INFO,
446446
crate::methods::OPTION_MAP_OR_NONE_INFO,
447+
crate::methods::OR_ELSE_THEN_UNWRAP_INFO,
447448
crate::methods::OR_FUN_CALL_INFO,
448449
crate::methods::OR_THEN_UNWRAP_INFO,
449450
crate::methods::PATH_BUF_PUSH_OVERWRITE_INFO,

clippy_lints/src/methods/mod.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ mod option_as_ref_cloned;
8787
mod option_as_ref_deref;
8888
mod option_map_or_none;
8989
mod option_map_unwrap_or;
90+
mod or_else_then_unwrap;
9091
mod or_fun_call;
9192
mod or_then_unwrap;
9293
mod path_buf_push_overwrite;
@@ -916,6 +917,42 @@ declare_clippy_lint! {
916917
"using `.chars().next()` to check if a string starts with a char"
917918
}
918919

920+
declare_clippy_lint! {
921+
/// ### What it does
922+
/// Checks for `.or_else(…).unwrap()` calls to Options and Results.
923+
///
924+
/// ### Why is this bad?
925+
/// You should use `.unwrap_or_else(…)` instead for clarity.
926+
///
927+
/// ### Example
928+
/// ```no_run
929+
/// # let fallback = "fallback";
930+
/// // Result
931+
/// # type Error = &'static str;
932+
/// # let result: Result<Vec<&str>, Error> = Err("error");
933+
/// let value = result.or_else(|_| Ok::<Vec<&str>, Error>(vec![fallback])).unwrap();
934+
///
935+
/// // Option
936+
/// # let option: Option<Vec<&str>> = None;
937+
/// let value = option.or_else(|| Some(vec![fallback])).unwrap();
938+
/// ```
939+
/// Use instead:
940+
/// ```no_run
941+
/// # let fallback = "fallback";
942+
/// // Result
943+
/// # let result: Result<Vec<&str>, &str> = Err("error");
944+
/// let value = result.unwrap_or_else(|_| vec![fallback]);
945+
///
946+
/// // Option
947+
/// # let option: Option<Vec<&str>> = None;
948+
/// let value = option.unwrap_or_else(|| vec![fallback]);
949+
/// ```
950+
#[clippy::version = "1.90.0"]
951+
pub OR_ELSE_THEN_UNWRAP,
952+
complexity,
953+
"checks for `.or_else(…).unwrap()` calls to Options and Results."
954+
}
955+
919956
declare_clippy_lint! {
920957
/// ### What it does
921958
/// Checks for calls to `.or(foo(..))`, `.unwrap_or(foo(..))`,
@@ -4680,6 +4717,7 @@ impl_lint_pass!(Methods => [
46804717
RESULT_MAP_OR_INTO_OPTION,
46814718
OPTION_MAP_OR_NONE,
46824719
BIND_INSTEAD_OF_MAP,
4720+
OR_ELSE_THEN_UNWRAP,
46834721
OR_FUN_CALL,
46844722
OR_THEN_UNWRAP,
46854723
EXPECT_FUN_CALL,
@@ -5534,6 +5572,9 @@ impl Methods {
55345572
Some((sym::or, recv, [or_arg], or_span, _)) => {
55355573
or_then_unwrap::check(cx, expr, recv, or_arg, or_span);
55365574
},
5575+
Some((sym::or_else, recv, [or_arg], or_span, _)) => {
5576+
or_else_then_unwrap::check(cx, expr, recv, or_arg, or_span);
5577+
},
55375578
_ => {},
55385579
}
55395580
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::source::snippet_with_applicability;
3+
use clippy_utils::ty::is_type_diagnostic_item;
4+
use clippy_utils::{is_res_lang_ctor, path_res};
5+
use rustc_errors::Applicability;
6+
use rustc_hir::lang_items::LangItem;
7+
use rustc_hir::{Body, Expr, ExprKind};
8+
use rustc_lint::LateContext;
9+
use rustc_span::{Span, sym};
10+
11+
use super::OR_ELSE_THEN_UNWRAP;
12+
13+
pub(super) fn check<'tcx>(
14+
cx: &LateContext<'tcx>,
15+
unwrap_expr: &Expr<'_>,
16+
recv: &'tcx Expr<'tcx>,
17+
or_else_arg: &'tcx Expr<'_>,
18+
or_span: Span,
19+
) {
20+
let ty = cx.typeck_results().expr_ty(recv); // get type of x (we later check if it's Option or Result)
21+
let title;
22+
let or_else_arg_content: Span;
23+
24+
if is_type_diagnostic_item(cx, ty, sym::Option) {
25+
title = "found `.or_else(|| Some(…)).unwrap()`";
26+
if let Some(content) = get_content_if_ctor_matches_in_closure(cx, or_else_arg, LangItem::OptionSome) {
27+
or_else_arg_content = content;
28+
} else {
29+
return;
30+
}
31+
} else if is_type_diagnostic_item(cx, ty, sym::Result) {
32+
title = "found `.or_else(|| Ok(…)).unwrap()`";
33+
if let Some(content) = get_content_if_ctor_matches_in_closure(cx, or_else_arg, LangItem::ResultOk) {
34+
or_else_arg_content = content;
35+
} else {
36+
return;
37+
}
38+
} else {
39+
// Someone has implemented a struct with .or(...).unwrap() chaining,
40+
// but it's not an Option or a Result, so bail
41+
return;
42+
}
43+
44+
let mut applicability = Applicability::MachineApplicable;
45+
let suggestion = format!(
46+
"unwrap_or_else(|| {})",
47+
snippet_with_applicability(cx, or_else_arg_content, "..", &mut applicability)
48+
);
49+
50+
span_lint_and_sugg(
51+
cx,
52+
OR_ELSE_THEN_UNWRAP,
53+
unwrap_expr.span.with_lo(or_span.lo()),
54+
title,
55+
"try",
56+
suggestion,
57+
applicability,
58+
);
59+
}
60+
61+
fn get_content_if_ctor_matches_in_closure(cx: &LateContext<'_>, expr: &Expr<'_>, item: LangItem) -> Option<Span> {
62+
if let ExprKind::Closure(closure) = expr.kind {
63+
if let Body {
64+
params: [],
65+
value: body,
66+
} = cx.tcx.hir_body(closure.body)
67+
{
68+
if let ExprKind::Call(some_expr, [arg]) = body.kind
69+
&& is_res_lang_ctor(cx, path_res(cx, some_expr), item)
70+
{
71+
Some(arg.span.source_callsite())
72+
} else {
73+
None
74+
}
75+
} else {
76+
None
77+
}
78+
} else {
79+
None
80+
}
81+
}

tests/ui/or_else_then_unwrap.fixed

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#![warn(clippy::or_then_unwrap)]
2+
#![allow(clippy::map_identity, clippy::let_unit_value, clippy::unnecessary_literal_unwrap)]
3+
4+
struct SomeStruct;
5+
impl SomeStruct {
6+
fn or_else<F: FnOnce() -> Option<Self>>(self, _: F) -> Self {
7+
self
8+
}
9+
fn unwrap(&self) {}
10+
}
11+
12+
struct SomeOtherStruct;
13+
impl SomeOtherStruct {
14+
fn or_else(self) -> Self {
15+
self
16+
}
17+
fn unwrap(&self) {}
18+
}
19+
20+
struct Wrapper {
21+
inner: &'static str,
22+
}
23+
impl Wrapper {
24+
fn new(inner: &'static str) -> Self {
25+
Self { inner }
26+
}
27+
}
28+
29+
fn main() {
30+
let option: Option<Wrapper> = None;
31+
let _ = option.unwrap_or_else(|| Wrapper::new("fallback")); // should trigger lint
32+
//
33+
//~^^ or_else_then_unwrap
34+
35+
// as part of a method chain
36+
let option: Option<Wrapper> = None;
37+
let _ = option
38+
.map(|v| v)
39+
.unwrap_or_else(|| Wrapper::new("fallback"))
40+
.inner
41+
.to_string()
42+
.chars();
43+
44+
// Call with macro should preserve the macro call rather than expand it
45+
let option: Option<Vec<&'static str>> = None;
46+
let _ = option.unwrap_or_else(|| vec!["fallback"]); // should trigger lint
47+
//
48+
//~^^ or_else_then_unwrap
49+
50+
// Not Option/Result
51+
let instance = SomeStruct {};
52+
let _ = instance.or_else(|| Some(SomeStruct {})).unwrap(); // should not trigger lint
53+
54+
// or takes no argument
55+
let instance = SomeOtherStruct {};
56+
let _ = instance.or_else().unwrap(); // should not trigger lint and should not panic
57+
58+
// None in or
59+
let option: Option<Wrapper> = None;
60+
#[allow(clippy::unnecessary_lazy_evaluations)]
61+
let _ = option.or_else(|| None).unwrap(); // should not trigger lint
62+
63+
// other function between
64+
let option: Option<Wrapper> = None;
65+
let _ = option.or_else(|| Some(Wrapper::new("fallback"))).map(|v| v).unwrap(); // should not trigger lint
66+
}

tests/ui/or_else_then_unwrap.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#![warn(clippy::or_then_unwrap)]
2+
#![allow(clippy::map_identity, clippy::let_unit_value, clippy::unnecessary_literal_unwrap)]
3+
4+
struct SomeStruct;
5+
impl SomeStruct {
6+
fn or_else<F: FnOnce() -> Option<Self>>(self, _: F) -> Self {
7+
self
8+
}
9+
fn unwrap(&self) {}
10+
}
11+
12+
struct SomeOtherStruct;
13+
impl SomeOtherStruct {
14+
fn or_else(self) -> Self {
15+
self
16+
}
17+
fn unwrap(&self) {}
18+
}
19+
20+
struct Wrapper {
21+
inner: &'static str,
22+
}
23+
impl Wrapper {
24+
fn new(inner: &'static str) -> Self {
25+
Self { inner }
26+
}
27+
}
28+
29+
fn main() {
30+
let option: Option<Wrapper> = None;
31+
let _ = option.or_else(|| Some(Wrapper::new("fallback"))).unwrap(); // should trigger lint
32+
//
33+
//~^^ or_else_then_unwrap
34+
35+
// as part of a method chain
36+
let option: Option<Wrapper> = None;
37+
let _ = option
38+
.map(|v| v)
39+
.or_else(|| Some(Wrapper::new("fallback"))) // should trigger lint
40+
//
41+
//~^^ or_else_then_unwrap
42+
.unwrap()
43+
.inner
44+
.to_string()
45+
.chars();
46+
47+
// Call with macro should preserve the macro call rather than expand it
48+
let option: Option<Vec<&'static str>> = None;
49+
let _ = option.or_else(|| Some(vec!["fallback"])).unwrap(); // should trigger lint
50+
//
51+
//~^^ or_else_then_unwrap
52+
53+
// Not Option/Result
54+
let instance = SomeStruct {};
55+
let _ = instance.or_else(|| Some(SomeStruct {})).unwrap(); // should not trigger lint
56+
57+
// or takes no argument
58+
let instance = SomeOtherStruct {};
59+
let _ = instance.or_else().unwrap(); // should not trigger lint and should not panic
60+
61+
// None in or
62+
let option: Option<Wrapper> = None;
63+
#[allow(clippy::unnecessary_lazy_evaluations)]
64+
let _ = option.or_else(|| None).unwrap(); // should not trigger lint
65+
66+
// other function between
67+
let option: Option<Wrapper> = None;
68+
let _ = option.or_else(|| Some(Wrapper::new("fallback"))).map(|v| v).unwrap(); // should not trigger lint
69+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
error: found `.or_else(|| Some(…)).unwrap()`
2+
--> tests/ui/or_else_then_unwrap.rs:31:20
3+
|
4+
LL | let _ = option.or_else(|| Some(Wrapper::new("fallback"))).unwrap(); // should trigger lint
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| Wrapper::new("fallback"))`
6+
|
7+
= note: `-D clippy::or-else-then-unwrap` implied by `-D warnings`
8+
= help: to override `-D warnings` add `#[allow(clippy::or_else_then_unwrap)]`
9+
10+
error: found `.or_else(|| Some(…)).unwrap()`
11+
--> tests/ui/or_else_then_unwrap.rs:39:10
12+
|
13+
LL | .or_else(|| Some(Wrapper::new("fallback"))) // should trigger lint
14+
| __________^
15+
... |
16+
LL | | .unwrap()
17+
| |_________________^ help: try: `unwrap_or_else(|| Wrapper::new("fallback"))`
18+
19+
error: found `.or_else(|| Some(…)).unwrap()`
20+
--> tests/ui/or_else_then_unwrap.rs:49:20
21+
|
22+
LL | let _ = option.or_else(|| Some(vec!["fallback"])).unwrap(); // should trigger lint
23+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| vec!["fallback"])`
24+
25+
error: aborting due to 3 previous errors
26+

0 commit comments

Comments
 (0)