Skip to content

Commit db80ef1

Browse files
committed
Added new lint for map_or(..., identity)
1 parent 6e33489 commit db80ef1

15 files changed

+210
-84
lines changed

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
420420
crate::methods::MAP_ERR_IGNORE_INFO,
421421
crate::methods::MAP_FLATTEN_INFO,
422422
crate::methods::MAP_IDENTITY_INFO,
423+
crate::methods::MAP_OR_IDENTITY_INFO,
423424
crate::methods::MAP_UNWRAP_OR_INFO,
424425
crate::methods::MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES_INFO,
425426
crate::methods::MUT_MUTEX_LOCK_INFO,
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::is_expr_identity_function;
3+
use clippy_utils::res::MaybeDef;
4+
use clippy_utils::source::snippet_with_applicability;
5+
use rustc_errors::Applicability;
6+
use rustc_hir::Expr;
7+
8+
use rustc_lint::LateContext;
9+
use rustc_span::Symbol;
10+
use rustc_span::symbol::sym;
11+
12+
use super::MAP_OR_IDENTITY;
13+
14+
/// Emit a lint for an unnecessary `map_or` over `Option` or `Result`
15+
///
16+
/// Note: `symbol` can only be `sym::Option` or `sym::Result`
17+
fn emit_lint(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, def_arg: &Expr<'_>, symbol: Symbol) {
18+
assert!(symbol == sym::Option || symbol == sym::Result);
19+
let msg = format!("unused \"map closure\" when calling `{symbol}::map_or` value");
20+
let mut applicability = Applicability::MachineApplicable;
21+
let self_snippet = snippet_with_applicability(cx, recv.span, "_", &mut applicability);
22+
let err_snippet = snippet_with_applicability(cx, def_arg.span, "..", &mut applicability);
23+
span_lint_and_sugg(
24+
cx,
25+
MAP_OR_IDENTITY,
26+
expr.span,
27+
msg,
28+
"consider using `unwrap_or`",
29+
format!("{self_snippet}.unwrap_or({err_snippet})"),
30+
applicability,
31+
);
32+
}
33+
34+
/// lint use of `_.map_or(err, |n| n)` for `Result`s and `Option`s.
35+
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, def_arg: &Expr<'_>, map_arg: &Expr<'_>) {
36+
// lint if the caller of `map_or()` is a `Result` or an `Option`
37+
if let Some(x @ (sym::Result | sym::Option)) = cx.typeck_results().expr_ty(recv).opt_diag_name(cx)
38+
&& is_expr_identity_function(cx, map_arg)
39+
{
40+
emit_lint(cx, expr, recv, def_arg, x);
41+
}
42+
}

clippy_lints/src/methods/mod.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ mod map_collect_result_unit;
7373
mod map_err_ignore;
7474
mod map_flatten;
7575
mod map_identity;
76+
mod map_or_identity;
7677
mod map_unwrap_or;
7778
mod map_with_unused_argument_over_ranges;
7879
mod mut_mutex_lock;
@@ -4714,6 +4715,29 @@ declare_clippy_lint! {
47144715
"filtering `std::io::Lines` with `filter_map()`, `flat_map()`, or `flatten()` might cause an infinite loop"
47154716
}
47164717

4718+
declare_clippy_lint! {
4719+
/// ### What it does
4720+
/// Checks the usage of `.map_or(...)` with an identity function for `Option` and `Result` types.
4721+
///
4722+
/// ### Why is this bad?
4723+
/// This can be written more concisely by using `unwrap_or()`.
4724+
///
4725+
/// ### Example
4726+
/// ```no_run
4727+
/// let opt = Some(1);
4728+
/// opt.map_or(42, |v| v);
4729+
/// ```
4730+
/// Use instead:
4731+
/// ```no_run
4732+
/// let opt = Some(1);
4733+
/// opt.unwrap_or(42);
4734+
/// ```
4735+
#[clippy::version = "1.93.0"]
4736+
pub MAP_OR_IDENTITY,
4737+
suspicious,
4738+
"using an identity function when mapping with `.map_or(|err| ..., |x| x)`"
4739+
}
4740+
47174741
#[expect(clippy::struct_excessive_bools)]
47184742
pub struct Methods {
47194743
avoid_breaking_exported_api: bool,
@@ -4897,6 +4921,7 @@ impl_lint_pass!(Methods => [
48974921
REDUNDANT_ITER_CLONED,
48984922
UNNECESSARY_OPTION_MAP_OR_ELSE,
48994923
LINES_FILTER_MAP_OK,
4924+
MAP_OR_IDENTITY,
49004925
]);
49014926

49024927
/// Extracts a method call name, args, and `Span` of the method name.
@@ -5368,6 +5393,7 @@ impl Methods {
53685393
option_map_or_none::check(cx, expr, recv, def, map);
53695394
manual_ok_or::check(cx, expr, recv, def, map);
53705395
unnecessary_map_or::check(cx, expr, recv, def, map, span, self.msrv);
5396+
map_or_identity::check(cx, expr, recv, def, map);
53715397
},
53725398
(sym::map_or_else, [def, map]) => {
53735399
result_map_or_else_none::check(cx, expr, recv, def, map);

tests/ui/manual_ok_or.fixed

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#![allow(clippy::redundant_closure)]
55
#![allow(dead_code)]
66
#![allow(unused_must_use)]
7+
#![allow(clippy::map_or_identity)]
78

89
fn main() {
910
// basic case

tests/ui/manual_ok_or.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#![allow(clippy::redundant_closure)]
55
#![allow(dead_code)]
66
#![allow(unused_must_use)]
7+
#![allow(clippy::map_or_identity)]
78

89
fn main() {
910
// basic case

tests/ui/manual_ok_or.stderr

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: this pattern reimplements `Option::ok_or`
2-
--> tests/ui/manual_ok_or.rs:11:5
2+
--> tests/ui/manual_ok_or.rs:12:5
33
|
44
LL | foo.map_or(Err("error"), |v| Ok(v));
55
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `foo.ok_or("error")`
@@ -8,19 +8,19 @@ LL | foo.map_or(Err("error"), |v| Ok(v));
88
= help: to override `-D warnings` add `#[allow(clippy::manual_ok_or)]`
99

1010
error: this pattern reimplements `Option::ok_or`
11-
--> tests/ui/manual_ok_or.rs:15:5
11+
--> tests/ui/manual_ok_or.rs:16:5
1212
|
1313
LL | foo.map_or(Err("error"), Ok);
1414
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `foo.ok_or("error")`
1515

1616
error: this pattern reimplements `Option::ok_or`
17-
--> tests/ui/manual_ok_or.rs:19:5
17+
--> tests/ui/manual_ok_or.rs:20:5
1818
|
1919
LL | None::<i32>.map_or(Err("error"), |v| Ok(v));
2020
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `None::<i32>.ok_or("error")`
2121

2222
error: this pattern reimplements `Option::ok_or`
23-
--> tests/ui/manual_ok_or.rs:24:5
23+
--> tests/ui/manual_ok_or.rs:25:5
2424
|
2525
LL | / foo.map_or(Err::<i32, &str>(
2626
LL | |

tests/ui/map_or_identity.fixed

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#![warn(clippy::map_or_identity)]
2+
3+
mod issue15801 {
4+
5+
fn foo(opt: Option<i32>, default: i32) -> i32 {
6+
opt.unwrap_or(default)
7+
//~^ map_or_identity
8+
}
9+
10+
fn bar(res: Result<i32, &str>, default: i32) -> i32 {
11+
res.unwrap_or(default)
12+
//~^ map_or_identity
13+
}
14+
}
15+
fn main() {
16+
// test code goes here
17+
}

tests/ui/map_or_identity.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#![warn(clippy::map_or_identity)]
2+
3+
mod issue15801 {
4+
5+
fn foo(opt: Option<i32>, default: i32) -> i32 {
6+
opt.map_or(default, |o| o)
7+
//~^ map_or_identity
8+
}
9+
10+
fn bar(res: Result<i32, &str>, default: i32) -> i32 {
11+
res.map_or(default, |o| o)
12+
//~^ map_or_identity
13+
}
14+
}
15+
fn main() {
16+
// test code goes here
17+
}

tests/ui/map_or_identity.stderr

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
error: unused "map closure" when calling `Option::map_or` value
2+
--> tests/ui/map_or_identity.rs:6:9
3+
|
4+
LL | opt.map_or(default, |o| o)
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `unwrap_or`: `opt.unwrap_or(default)`
6+
|
7+
= note: `-D clippy::map-or-identity` implied by `-D warnings`
8+
= help: to override `-D warnings` add `#[allow(clippy::map_or_identity)]`
9+
10+
error: unused "map closure" when calling `Result::map_or` value
11+
--> tests/ui/map_or_identity.rs:11:9
12+
|
13+
LL | res.map_or(default, |o| o)
14+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `unwrap_or`: `res.unwrap_or(default)`
15+
16+
error: aborting due to 2 previous errors
17+

tests/ui/option_if_let_else.fixed

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
clippy::manual_midpoint,
88
clippy::manual_unwrap_or_default,
99
clippy::manual_unwrap_or,
10-
clippy::unnecessary_option_map_or_else
10+
clippy::unnecessary_option_map_or_else,
11+
clippy::map_or_identity
1112
)]
1213

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

0 commit comments

Comments
 (0)