Skip to content

Commit 85e08cd

Browse files
committed
Auto merge of rust-lang#12169 - GuillaumeGomez:unnecessary_result_map_or_else, r=llogiq
Add new `unnecessary_result_map_or_else` lint Fixes rust-lang/rust-clippy#7328. r? `@llogiq` changelog: Add new `unnecessary_result_map_or_else` lint
2 parents 79f10cf + e86da9e commit 85e08cd

8 files changed

+291
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5679,6 +5679,7 @@ Released 2018-09-13
56795679
[`unnecessary_mut_passed`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_mut_passed
56805680
[`unnecessary_operation`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_operation
56815681
[`unnecessary_owned_empty_strings`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_owned_empty_strings
5682+
[`unnecessary_result_map_or_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_result_map_or_else
56825683
[`unnecessary_safety_comment`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_safety_comment
56835684
[`unnecessary_safety_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_safety_doc
56845685
[`unnecessary_self_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_self_imports

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
453453
crate::methods::UNNECESSARY_JOIN_INFO,
454454
crate::methods::UNNECESSARY_LAZY_EVALUATIONS_INFO,
455455
crate::methods::UNNECESSARY_LITERAL_UNWRAP_INFO,
456+
crate::methods::UNNECESSARY_RESULT_MAP_OR_ELSE_INFO,
456457
crate::methods::UNNECESSARY_SORT_BY_INFO,
457458
crate::methods::UNNECESSARY_TO_OWNED_INFO,
458459
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;
@@ -3951,6 +3952,31 @@ declare_clippy_lint! {
39513952
"cloning an `Option` via `as_ref().cloned()`"
39523953
}
39533954

3955+
declare_clippy_lint! {
3956+
/// ### What it does
3957+
/// Checks for usage of `.map_or_else()` "map closure" for `Result` type.
3958+
///
3959+
/// ### Why is this bad?
3960+
/// This can be written more concisely by using `unwrap_or_else()`.
3961+
///
3962+
/// ### Example
3963+
/// ```no_run
3964+
/// # fn handle_error(_: ()) -> u32 { 0 }
3965+
/// let x: Result<u32, ()> = Ok(0);
3966+
/// let y = x.map_or_else(|err| handle_error(err), |n| n);
3967+
/// ```
3968+
/// Use instead:
3969+
/// ```no_run
3970+
/// # fn handle_error(_: ()) -> u32 { 0 }
3971+
/// let x: Result<u32, ()> = Ok(0);
3972+
/// let y = x.unwrap_or_else(|err| handle_error(err));
3973+
/// ```
3974+
#[clippy::version = "1.77.0"]
3975+
pub UNNECESSARY_RESULT_MAP_OR_ELSE,
3976+
suspicious,
3977+
"making no use of the \"map closure\" when calling `.map_or_else(|err| handle_error(err), |n| n)`"
3978+
}
3979+
39543980
pub struct Methods {
39553981
avoid_breaking_exported_api: bool,
39563982
msrv: Msrv,
@@ -4109,6 +4135,7 @@ impl_lint_pass!(Methods => [
41094135
MANUAL_IS_VARIANT_AND,
41104136
STR_SPLIT_AT_NEWLINE,
41114137
OPTION_AS_REF_CLONED,
4138+
UNNECESSARY_RESULT_MAP_OR_ELSE,
41124139
]);
41134140

41144141
/// Extracts a method call name, args, and `Span` of the method name.
@@ -4592,6 +4619,7 @@ impl Methods {
45924619
},
45934620
("map_or_else", [def, map]) => {
45944621
result_map_or_else_none::check(cx, expr, recv, def, map);
4622+
unnecessary_result_map_or_else::check(cx, expr, recv, def, map);
45954623
},
45964624
("next", []) => {
45974625
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+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#![warn(clippy::unnecessary_result_map_or_else)]
2+
#![allow(clippy::unnecessary_literal_unwrap, clippy::let_and_return, clippy::let_unit_value)]
3+
4+
fn main() {
5+
let x: Result<(), ()> = Ok(());
6+
x.unwrap_or_else(|err| err); //~ ERROR: unused "map closure" when calling
7+
8+
// Type ascribtion.
9+
let x: Result<(), ()> = Ok(());
10+
x.unwrap_or_else(|err: ()| err); //~ ERROR: unused "map closure" when calling
11+
12+
// Auto-deref.
13+
let y = String::new();
14+
let x: Result<&String, &String> = Ok(&y);
15+
let y: &str = x.unwrap_or_else(|err| err); //~ ERROR: unused "map closure" when calling
16+
17+
// Temporary variable.
18+
let x: Result<(), ()> = Ok(());
19+
x.unwrap_or_else(|err| err);
20+
21+
// Should not warn.
22+
let x: Result<usize, usize> = Ok(0);
23+
x.map_or_else(|err| err, |n| n + 1);
24+
25+
// Should not warn.
26+
let y = ();
27+
let x: Result<(), ()> = Ok(());
28+
x.map_or_else(|err| err, |_| y);
29+
30+
// Should not warn.
31+
let y = ();
32+
let x: Result<(), ()> = Ok(());
33+
x.map_or_else(
34+
|err| err,
35+
|_| {
36+
let tmp = y;
37+
tmp
38+
},
39+
);
40+
41+
// Should not warn.
42+
let x: Result<usize, usize> = Ok(1);
43+
x.map_or_else(
44+
|err| err,
45+
|n| {
46+
let tmp = n + 1;
47+
tmp
48+
},
49+
);
50+
51+
// Should not warn.
52+
let y = 0;
53+
let x: Result<usize, usize> = Ok(1);
54+
x.map_or_else(
55+
|err| err,
56+
|n| {
57+
let tmp = n;
58+
y
59+
},
60+
);
61+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#![warn(clippy::unnecessary_result_map_or_else)]
2+
#![allow(clippy::unnecessary_literal_unwrap, clippy::let_and_return, clippy::let_unit_value)]
3+
4+
fn main() {
5+
let x: Result<(), ()> = Ok(());
6+
x.map_or_else(|err| err, |n| n); //~ ERROR: unused "map closure" when calling
7+
8+
// Type ascribtion.
9+
let x: Result<(), ()> = Ok(());
10+
x.map_or_else(|err: ()| err, |n: ()| n); //~ ERROR: unused "map closure" when calling
11+
12+
// Auto-deref.
13+
let y = String::new();
14+
let x: Result<&String, &String> = Ok(&y);
15+
let y: &str = x.map_or_else(|err| err, |n| n); //~ ERROR: unused "map closure" when calling
16+
17+
// Temporary variable.
18+
let x: Result<(), ()> = Ok(());
19+
x.map_or_else(
20+
//~^ ERROR: unused "map closure" when calling
21+
|err| err,
22+
|n| {
23+
let tmp = n;
24+
let tmp2 = tmp;
25+
tmp2
26+
},
27+
);
28+
29+
// Should not warn.
30+
let x: Result<usize, usize> = Ok(0);
31+
x.map_or_else(|err| err, |n| n + 1);
32+
33+
// Should not warn.
34+
let y = ();
35+
let x: Result<(), ()> = Ok(());
36+
x.map_or_else(|err| err, |_| y);
37+
38+
// Should not warn.
39+
let y = ();
40+
let x: Result<(), ()> = Ok(());
41+
x.map_or_else(
42+
|err| err,
43+
|_| {
44+
let tmp = y;
45+
tmp
46+
},
47+
);
48+
49+
// Should not warn.
50+
let x: Result<usize, usize> = Ok(1);
51+
x.map_or_else(
52+
|err| err,
53+
|n| {
54+
let tmp = n + 1;
55+
tmp
56+
},
57+
);
58+
59+
// Should not warn.
60+
let y = 0;
61+
let x: Result<usize, usize> = Ok(1);
62+
x.map_or_else(
63+
|err| err,
64+
|n| {
65+
let tmp = n;
66+
y
67+
},
68+
);
69+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
error: unused "map closure" when calling `Result::map_or_else` value
2+
--> $DIR/unnecessary_result_map_or_else.rs:6:5
3+
|
4+
LL | x.map_or_else(|err| err, |n| n);
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `unwrap_or_else`: `x.unwrap_or_else(|err| err)`
6+
|
7+
= note: `-D clippy::unnecessary-result-map-or-else` implied by `-D warnings`
8+
= help: to override `-D warnings` add `#[allow(clippy::unnecessary_result_map_or_else)]`
9+
10+
error: unused "map closure" when calling `Result::map_or_else` value
11+
--> $DIR/unnecessary_result_map_or_else.rs:10:5
12+
|
13+
LL | x.map_or_else(|err: ()| err, |n: ()| n);
14+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `unwrap_or_else`: `x.unwrap_or_else(|err: ()| err)`
15+
16+
error: unused "map closure" when calling `Result::map_or_else` value
17+
--> $DIR/unnecessary_result_map_or_else.rs:15:19
18+
|
19+
LL | let y: &str = x.map_or_else(|err| err, |n| n);
20+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `unwrap_or_else`: `x.unwrap_or_else(|err| err)`
21+
22+
error: unused "map closure" when calling `Result::map_or_else` value
23+
--> $DIR/unnecessary_result_map_or_else.rs:19:5
24+
|
25+
LL | / x.map_or_else(
26+
LL | |
27+
LL | | |err| err,
28+
LL | | |n| {
29+
... |
30+
LL | | },
31+
LL | | );
32+
| |_____^ help: consider using `unwrap_or_else`: `x.unwrap_or_else(|err| err)`
33+
34+
error: aborting due to 4 previous errors
35+

0 commit comments

Comments
 (0)