Skip to content

Commit 4c128d9

Browse files
committed
Add lint unnecessary_option_map_or_else
1 parent 3e218d1 commit 4c128d9

13 files changed

+322
-80
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6738,6 +6738,7 @@ Released 2018-09-13
67386738
[`unnecessary_min_or_max`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_min_or_max
67396739
[`unnecessary_mut_passed`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_mut_passed
67406740
[`unnecessary_operation`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_operation
6741+
[`unnecessary_option_map_or_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_option_map_or_else
67416742
[`unnecessary_owned_empty_strings`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_owned_empty_strings
67426743
[`unnecessary_result_map_or_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_result_map_or_else
67436744
[`unnecessary_safety_comment`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_safety_comment

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
488488
crate::methods::UNNECESSARY_LITERAL_UNWRAP_INFO,
489489
crate::methods::UNNECESSARY_MAP_OR_INFO,
490490
crate::methods::UNNECESSARY_MIN_OR_MAX_INFO,
491+
crate::methods::UNNECESSARY_OPTION_MAP_OR_ELSE_INFO,
491492
crate::methods::UNNECESSARY_RESULT_MAP_OR_ELSE_INFO,
492493
crate::methods::UNNECESSARY_SORT_BY_INFO,
493494
crate::methods::UNNECESSARY_TO_OWNED_INFO,

clippy_lints/src/methods/mod.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ mod unnecessary_lazy_eval;
130130
mod unnecessary_literal_unwrap;
131131
mod unnecessary_map_or;
132132
mod unnecessary_min_or_max;
133+
mod unnecessary_option_map_or_else;
133134
mod unnecessary_result_map_or_else;
134135
mod unnecessary_sort_by;
135136
mod unnecessary_to_owned;
@@ -4637,6 +4638,30 @@ declare_clippy_lint! {
46374638
"detects redundant calls to `Iterator::cloned`"
46384639
}
46394640

4641+
declare_clippy_lint! {
4642+
/// Checks for usage of `.map_or_else()` "map closure" for `Option` type.
4643+
///
4644+
/// ### Why is this bad?
4645+
/// This can be written more concisely by using `unwrap_or_else()`.
4646+
///
4647+
/// ### Example
4648+
/// ```no_run
4649+
/// let k = 10;
4650+
/// let x: Option<u32> = Some(4);
4651+
/// let y = x.map_or_else(|| 2 * k, |n| n);
4652+
/// ```
4653+
/// Use instead:
4654+
/// ```no_run
4655+
/// let k = 10;
4656+
/// let x: Option<u32> = Some(4);
4657+
/// let y = x.unwrap_or_else(|| 2 * k);
4658+
/// ```
4659+
#[clippy::version = "1.88.0"]
4660+
pub UNNECESSARY_OPTION_MAP_OR_ELSE,
4661+
suspicious,
4662+
"making no use of the \"map closure\" when calling `.map_or_else(|| 2 * k, |n| n)`"
4663+
}
4664+
46404665
#[expect(clippy::struct_excessive_bools)]
46414666
pub struct Methods {
46424667
avoid_breaking_exported_api: bool,
@@ -4818,6 +4843,7 @@ impl_lint_pass!(Methods => [
48184843
SWAP_WITH_TEMPORARY,
48194844
IP_CONSTANT,
48204845
REDUNDANT_ITER_CLONED,
4846+
UNNECESSARY_OPTION_MAP_OR_ELSE,
48214847
]);
48224848

48234849
/// Extracts a method call name, args, and `Span` of the method name.
@@ -5354,6 +5380,7 @@ impl Methods {
53545380
},
53555381
(sym::map_or_else, [def, map]) => {
53565382
result_map_or_else_none::check(cx, expr, recv, def, map);
5383+
unnecessary_option_map_or_else::check(cx, expr, recv, def, map);
53575384
unnecessary_result_map_or_else::check(cx, expr, recv, def, map);
53585385
},
53595386
(sym::next, []) => {
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::peel_blocks;
3+
use clippy_utils::source::snippet;
4+
use clippy_utils::ty::is_type_diagnostic_item;
5+
use rustc_errors::Applicability;
6+
use rustc_hir::{Closure, Expr, ExprKind, HirId, QPath};
7+
use rustc_lint::LateContext;
8+
use rustc_span::symbol::sym;
9+
10+
use super::UNNECESSARY_OPTION_MAP_OR_ELSE;
11+
use super::utils::get_last_chain_binding_hir_id;
12+
13+
fn emit_lint(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, def_arg: &Expr<'_>) {
14+
let msg = "unused \"map closure\" when calling `Option::map_or_else` value";
15+
let self_snippet = snippet(cx, recv.span, "..");
16+
let err_snippet = snippet(cx, def_arg.span, "..");
17+
span_lint_and_sugg(
18+
cx,
19+
UNNECESSARY_OPTION_MAP_OR_ELSE,
20+
expr.span,
21+
msg,
22+
"consider using `unwrap_or_else`",
23+
format!("{self_snippet}.unwrap_or_else({err_snippet})"),
24+
Applicability::MachineApplicable,
25+
);
26+
}
27+
28+
fn handle_qpath(
29+
cx: &LateContext<'_>,
30+
expr: &Expr<'_>,
31+
recv: &Expr<'_>,
32+
def_arg: &Expr<'_>,
33+
expected_hir_id: HirId,
34+
qpath: QPath<'_>,
35+
) {
36+
if let QPath::Resolved(_, path) = qpath
37+
&& let rustc_hir::def::Res::Local(hir_id) = path.res
38+
&& expected_hir_id == hir_id
39+
{
40+
emit_lint(cx, expr, recv, def_arg);
41+
}
42+
}
43+
44+
/// lint use of `_.map_or_else(|err| err, |n| n)` for `Option`s.
45+
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, def_arg: &Expr<'_>, map_arg: &Expr<'_>) {
46+
// lint if the caller of `map_or_else()` is an `Option`
47+
if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Option)
48+
&& let ExprKind::Closure(&Closure { body, .. }) = map_arg.kind
49+
&& let body = cx.tcx.hir_body(body)
50+
&& let Some(first_param) = body.params.first()
51+
{
52+
let body_expr = peel_blocks(body.value);
53+
54+
match body_expr.kind {
55+
ExprKind::Path(qpath) => {
56+
handle_qpath(cx, expr, recv, def_arg, first_param.pat.hir_id, qpath);
57+
},
58+
// If this is a block (that wasn't peeled off), then it means there are statements.
59+
ExprKind::Block(block, _) => {
60+
if let Some(block_expr) = block.expr
61+
// First we ensure that this is a "binding chain" (each statement is a binding
62+
// of the previous one) and that it is a binding of the closure argument.
63+
&& let Some(last_chain_binding_id) =
64+
get_last_chain_binding_hir_id(first_param.pat.hir_id, block.stmts)
65+
&& let ExprKind::Path(qpath) = block_expr.kind
66+
{
67+
handle_qpath(cx, expr, recv, def_arg, last_chain_binding_id, qpath);
68+
}
69+
},
70+
_ => {},
71+
}
72+
}
73+
}

tests/ui/option_if_let_else.fixed

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
clippy::redundant_locals,
77
clippy::manual_midpoint,
88
clippy::manual_unwrap_or_default,
9-
clippy::manual_unwrap_or
9+
clippy::manual_unwrap_or,
10+
clippy::unnecessary_option_map_or_else
1011
)]
1112

1213
fn bad1(string: Option<&str>) -> (bool, &str) {

tests/ui/option_if_let_else.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
clippy::redundant_locals,
77
clippy::manual_midpoint,
88
clippy::manual_unwrap_or_default,
9-
clippy::manual_unwrap_or
9+
clippy::manual_unwrap_or,
10+
clippy::unnecessary_option_map_or_else
1011
)]
1112

1213
fn bad1(string: Option<&str>) -> (bool, &str) {

0 commit comments

Comments
 (0)