Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 3b8f62f

Browse files
Add new unnecessary_result_map_or_else lint
1 parent 37947ff commit 3b8f62f

File tree

4 files changed

+125
-1
lines changed

4 files changed

+125
-1
lines changed

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
451451
crate::methods::UNNECESSARY_JOIN_INFO,
452452
crate::methods::UNNECESSARY_LAZY_EVALUATIONS_INFO,
453453
crate::methods::UNNECESSARY_LITERAL_UNWRAP_INFO,
454+
crate::methods::UNNECESSARY_RESULT_MAP_OR_ELSE_INFO,
454455
crate::methods::UNNECESSARY_SORT_BY_INFO,
455456
crate::methods::UNNECESSARY_TO_OWNED_INFO,
456457
crate::methods::UNWRAP_OR_DEFAULT_INFO,

clippy_lints/src/methods/map_unwrap_or.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ pub(super) fn check<'tcx>(
2121
unwrap_arg: &'tcx hir::Expr<'_>,
2222
msrv: &Msrv,
2323
) -> bool {
24-
// lint if the caller of `map()` is an `Option`
24+
// lint if the caller of `map()` is an `Option` or a `Result`.
2525
let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Option);
2626
let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result);
2727

clippy_lints/src/methods/mod.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ mod unnecessary_iter_cloned;
113113
mod unnecessary_join;
114114
mod unnecessary_lazy_eval;
115115
mod unnecessary_literal_unwrap;
116+
mod unnecessary_result_map_or_else;
116117
mod unnecessary_sort_by;
117118
mod unnecessary_to_owned;
118119
mod unwrap_expect_used;
@@ -3913,6 +3914,31 @@ declare_clippy_lint! {
39133914
"cloning an `Option` via `as_ref().cloned()`"
39143915
}
39153916

3917+
declare_clippy_lint! {
3918+
/// ### What it does
3919+
/// Checks for usage of `.map_or_else()` "map closure" for `Result` type.
3920+
///
3921+
/// ### Why is this bad?
3922+
/// This can be written more concisely by using `unwrap_or_else()`.
3923+
///
3924+
/// ### Example
3925+
/// ```no_run
3926+
/// # fn handle_error(_: ()) -> u32 { 0 }
3927+
/// let x: Result<u32, ()> = Ok(0);
3928+
/// let y = x.map_or_else(|err| handle_error(err), |n| n);
3929+
/// ```
3930+
/// Use instead:
3931+
/// ```no_run
3932+
/// # fn handle_error(_: ()) -> u32 { 0 }
3933+
/// let x: Result<u32, ()> = Ok(0);
3934+
/// let y = x.unwrap_or_else(|err| handle_error(err));
3935+
/// ```
3936+
#[clippy::version = "1.77.0"]
3937+
pub UNNECESSARY_RESULT_MAP_OR_ELSE,
3938+
suspicious,
3939+
"making no use of the \"map closure\" when calling `.map_or_else(|err| handle_error(err), |n| n)`"
3940+
}
3941+
39163942
pub struct Methods {
39173943
avoid_breaking_exported_api: bool,
39183944
msrv: Msrv,
@@ -4070,6 +4096,7 @@ impl_lint_pass!(Methods => [
40704096
MANUAL_IS_VARIANT_AND,
40714097
STR_SPLIT_AT_NEWLINE,
40724098
OPTION_AS_REF_CLONED,
4099+
UNNECESSARY_RESULT_MAP_OR_ELSE,
40734100
]);
40744101

40754102
/// Extracts a method call name, args, and `Span` of the method name.
@@ -4553,6 +4580,7 @@ impl Methods {
45534580
},
45544581
("map_or_else", [def, map]) => {
45554582
result_map_or_else_none::check(cx, expr, recv, def, map);
4583+
unnecessary_result_map_or_else::check(cx, expr, recv, def, map);
45564584
},
45574585
("next", []) => {
45584586
if let Some((name2, recv2, args2, _, _)) = method_call(recv) {
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
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 as hir;
7+
use rustc_hir::{Closure, Expr, ExprKind, HirId, QPath, Stmt, StmtKind};
8+
use rustc_lint::LateContext;
9+
use rustc_span::symbol::sym;
10+
11+
use super::UNNECESSARY_RESULT_MAP_OR_ELSE;
12+
13+
fn emit_lint(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, def_arg: &Expr<'_>) {
14+
let msg = "unused \"map closure\" when calling `Result::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_RESULT_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 get_last_chain_binding_hir_id(mut hir_id: HirId, statements: &[Stmt<'_>]) -> Option<HirId> {
29+
for stmt in statements {
30+
if let StmtKind::Local(local) = stmt.kind
31+
&& let Some(init) = local.init
32+
&& let ExprKind::Path(QPath::Resolved(_, path)) = init.kind
33+
&& let hir::def::Res::Local(local_hir_id) = path.res
34+
&& local_hir_id == hir_id
35+
{
36+
hir_id = local.pat.hir_id;
37+
} else {
38+
return None;
39+
}
40+
}
41+
Some(hir_id)
42+
}
43+
44+
fn handle_qpath(
45+
cx: &LateContext<'_>,
46+
expr: &Expr<'_>,
47+
recv: &Expr<'_>,
48+
def_arg: &Expr<'_>,
49+
expected_hir_id: HirId,
50+
qpath: QPath<'_>,
51+
) {
52+
if let QPath::Resolved(_, path) = qpath
53+
&& let hir::def::Res::Local(hir_id) = path.res
54+
&& expected_hir_id == hir_id
55+
{
56+
emit_lint(cx, expr, recv, def_arg);
57+
}
58+
}
59+
60+
/// lint use of `_.map_or_else(|err| err, |n| n)` for `Result`s.
61+
pub(super) fn check<'tcx>(
62+
cx: &LateContext<'tcx>,
63+
expr: &'tcx Expr<'_>,
64+
recv: &'tcx Expr<'_>,
65+
def_arg: &'tcx Expr<'_>,
66+
map_arg: &'tcx Expr<'_>,
67+
) {
68+
// lint if the caller of `map_or_else()` is a `Result`
69+
if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result)
70+
&& let ExprKind::Closure(&Closure { body, .. }) = map_arg.kind
71+
&& let body = cx.tcx.hir().body(body)
72+
&& let Some(first_param) = body.params.first()
73+
{
74+
let body_expr = peel_blocks(body.value);
75+
76+
match body_expr.kind {
77+
ExprKind::Path(qpath) => {
78+
handle_qpath(cx, expr, recv, def_arg, first_param.pat.hir_id, qpath);
79+
},
80+
// If this is a block (that wasn't peeled off), then it means there are statements.
81+
ExprKind::Block(block, _) => {
82+
if let Some(block_expr) = block.expr
83+
// First we ensure that this is a "binding chain" (each statement is a binding
84+
// of the previous one) and that it is a binding of the closure argument.
85+
&& let Some(last_chain_binding_id) =
86+
get_last_chain_binding_hir_id(first_param.pat.hir_id, block.stmts)
87+
&& let ExprKind::Path(qpath) = block_expr.kind
88+
{
89+
handle_qpath(cx, expr, recv, def_arg, last_chain_binding_id, qpath);
90+
}
91+
},
92+
_ => {},
93+
}
94+
}
95+
}

0 commit comments

Comments
 (0)