Skip to content

Commit e8bea03

Browse files
authored
Suggest is_ok/is_err for boolean Result mappings (#17537)
*[View all comments](https://triagebot.infra.rust-lang.org/gh-comments/rust-lang/rust-clippy/pull/17537)* changelog: [`unnecessary_map_or`]: suggest `Result::is_ok` and `Result::is_err` for boolean `map_or` and `map_or_else` branches Fixes #5718 ## Summary - recognize `Result::map_or` and `Result::map_or_else` calls whose branches return opposite boolean literals without using their arguments - suggest `is_ok()` when the `Ok` branch is `true`, and `is_err()` when the `Err` branch is `true` - keep rustfix machine-applicable when drop order is insignificant, while downgrading the suggestion and explaining the difference when the result or its temporaries need ordered drop - extend the lint documentation and cover single-line, multiline, rustfix, negative, and significant-drop cases ## Validation - `TESTNAME=unnecessary_map_or cargo uitest` - `cargo dev fmt --check` - `cargo test`
2 parents 21baba9 + 6136fbd commit e8bea03

7 files changed

Lines changed: 331 additions & 12 deletions

clippy_lints/src/methods/mod.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4453,37 +4453,41 @@ declare_clippy_lint! {
44534453

44544454
declare_clippy_lint! {
44554455
/// ### What it does
4456-
/// Converts some constructs mapping an Enum value for equality comparison.
4456+
/// Converts some constructs mapping an enum value for equality or variant checks.
44574457
///
44584458
/// ### Why is this bad?
44594459
/// Calls such as `opt.map_or(false, |val| val == 5)` are needlessly long and cumbersome,
44604460
/// and can be reduced to, for example, `opt == Some(5)` assuming `opt` implements `PartialEq`.
44614461
/// Also, calls such as `opt.map_or(true, |val| val == 5)` can be reduced to
44624462
/// `opt.is_none_or(|val| val == 5)`.
4463+
/// Calls that map the two variants of a `Result` to opposite boolean constants can be
4464+
/// reduced to `is_ok()` or `is_err()`.
44634465
/// This lint offers readability and conciseness improvements.
44644466
///
44654467
/// ### Example
44664468
/// ```no_run
4467-
/// pub fn a(x: Option<i32>) -> (bool, bool) {
4469+
/// pub fn a(x: Option<i32>, result: Result<i32, i32>) -> (bool, bool, bool) {
44684470
/// (
44694471
/// x.map_or(false, |n| n == 5),
44704472
/// x.map_or(true, |n| n > 5),
4473+
/// result.map_or_else(|_| false, |_| true),
44714474
/// )
44724475
/// }
44734476
/// ```
44744477
/// Use instead:
44754478
/// ```no_run
4476-
/// pub fn a(x: Option<i32>) -> (bool, bool) {
4479+
/// pub fn a(x: Option<i32>, result: Result<i32, i32>) -> (bool, bool, bool) {
44774480
/// (
44784481
/// x == Some(5),
44794482
/// x.is_none_or(|n| n > 5),
4483+
/// result.is_ok(),
44804484
/// )
44814485
/// }
44824486
/// ```
44834487
#[clippy::version = "1.84.0"]
44844488
pub UNNECESSARY_MAP_OR,
44854489
style,
4486-
"reduce unnecessary calls to `.map_or(bool, …)`"
4490+
"reduce unnecessary calls to `.map_or(bool, …)` and `.map_or_else(…, …)`"
44874491
}
44884492

44894493
declare_clippy_lint! {
@@ -5664,6 +5668,7 @@ impl Methods {
56645668
(sym::map_or_else, [def, map]) => {
56655669
result_map_or_else_none::check(cx, expr, recv, def, map);
56665670
unnecessary_map_or_else::check(cx, expr, recv, def, map, call_span);
5671+
unnecessary_map_or::check_map_or_else(cx, expr, recv, def, map);
56675672
},
56685673
(sym::next, []) => {
56695674
if let Some((name2, recv2, args2, _, _)) = method_call(recv) {

clippy_lints/src/methods/unnecessary_map_or.rs

Lines changed: 111 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
use std::borrow::Cow;
22

3+
use clippy_utils::consts::{ConstEvalCtxt, Constant};
34
use clippy_utils::diagnostics::span_lint_and_then;
45
use clippy_utils::eager_or_lazy::switch_to_eager_eval;
56
use clippy_utils::msrvs::{self, Msrv};
67
use clippy_utils::res::{MaybeDef as _, MaybeResPath as _};
78
use clippy_utils::sugg::{Sugg, make_binop};
8-
use clippy_utils::ty::{implements_trait, is_copy};
9-
use clippy_utils::visitors::is_local_used;
9+
use clippy_utils::ty::{implements_trait, is_copy, needs_ordered_drop};
10+
use clippy_utils::visitors::{any_temporaries_need_ordered_drop, is_local_used};
1011
use clippy_utils::{get_parent_expr, is_from_proc_macro};
1112
use rustc_ast::LitKind;
1213
use rustc_errors::Applicability;
@@ -36,15 +37,103 @@ impl Variant {
3637
}
3738
}
3839

39-
pub(super) fn check<'a>(
40-
cx: &LateContext<'a>,
41-
expr: &Expr<'a>,
42-
recv: &Expr<'_>,
43-
def: &Expr<'_>,
44-
map: &Expr<'_>,
40+
/// Evaluates `expr` and returns its value if it is a constant boolean.
41+
fn bool_constant(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<bool> {
42+
let Some(Constant::Bool(value)) = ConstEvalCtxt::new(cx).eval(expr) else {
43+
return None;
44+
};
45+
Some(value)
46+
}
47+
48+
/// Returns the constant boolean produced by a one-parameter closure.
49+
fn closure_bool_constant(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<bool> {
50+
let ExprKind::Closure(closure) = expr.kind else {
51+
return None;
52+
};
53+
let body = cx.tcx.hir_body(closure.body);
54+
let [_] = body.params else {
55+
return None;
56+
};
57+
bool_constant(cx, body.value)
58+
}
59+
60+
/// Checks whether a `Result::{map_or, map_or_else}` call is a variant query.
61+
///
62+
/// `expr` is the complete method call, `recv` is its `Result` receiver, `def` is the default
63+
/// argument, and `map` is the mapping closure. `check_if_bool` accounts for the eager default in
64+
/// `map_or` and the closure default in `map_or_else`.
65+
fn check_result_variant_query<'tcx>(
66+
cx: &LateContext<'tcx>,
67+
expr: &'tcx Expr<'tcx>,
68+
recv: &'tcx Expr<'tcx>,
69+
def: &'tcx Expr<'tcx>,
70+
map: &'tcx Expr<'tcx>,
71+
check_if_bool: impl FnOnce(&LateContext<'tcx>, &'tcx Expr<'tcx>) -> Option<bool>,
72+
) -> bool {
73+
let ExprKind::MethodCall(path, _, _, call_span) = expr.kind else {
74+
return false;
75+
};
76+
let recv_ty = cx.typeck_results().expr_ty_adjusted(recv);
77+
if recv_ty.opt_diag_name(cx) != Some(sym::Result) {
78+
return false;
79+
}
80+
81+
let def_bool = check_if_bool(cx, def);
82+
let Some((def_bool, map_bool)) = def_bool.zip(closure_bool_constant(cx, map)) else {
83+
return false;
84+
};
85+
if def_bool == map_bool || is_from_proc_macro(cx, expr) {
86+
return false;
87+
}
88+
89+
let suggested_name = if map_bool { "is_ok" } else { "is_err" };
90+
let changes_drop_order = needs_ordered_drop(cx, recv_ty) || any_temporaries_need_ordered_drop(cx, recv);
91+
let applicability = if changes_drop_order {
92+
Applicability::MaybeIncorrect
93+
} else {
94+
Applicability::MachineApplicable
95+
};
96+
97+
span_lint_and_then(
98+
cx,
99+
UNNECESSARY_MAP_OR,
100+
path.ident.span,
101+
format!("this `{}` can be simplified", path.ident.name),
102+
|diag| {
103+
diag.span_suggestion(
104+
call_span,
105+
format!("use `{suggested_name}` instead"),
106+
format!("{suggested_name}()"),
107+
applicability,
108+
);
109+
if changes_drop_order {
110+
diag.note("this will change drop order of the result, as well as all temporaries");
111+
diag.note("add `#[allow(clippy::unnecessary_map_or)]` if this is important");
112+
}
113+
},
114+
);
115+
true
116+
}
117+
118+
/// Checks a `map_or` call for both `Result` variant queries and the existing `Option`/`Result`
119+
/// simplifications.
120+
///
121+
/// `expr` is the complete method call, `recv` is its receiver, `def` is the eager default, and
122+
/// `map` is the mapping closure. `method_span` identifies `map_or` in diagnostics, while `msrv`
123+
/// controls which replacement methods can be suggested.
124+
pub(super) fn check<'tcx>(
125+
cx: &LateContext<'tcx>,
126+
expr: &'tcx Expr<'tcx>,
127+
recv: &'tcx Expr<'tcx>,
128+
def: &'tcx Expr<'tcx>,
129+
map: &'tcx Expr<'tcx>,
45130
method_span: Span,
46131
msrv: Msrv,
47132
) {
133+
if check_result_variant_query(cx, expr, recv, def, map, bool_constant) {
134+
return;
135+
}
136+
48137
let ExprKind::Lit(def_kind) = def.kind else {
49138
return;
50139
};
@@ -158,3 +247,17 @@ pub(super) fn check<'a>(
158247
},
159248
);
160249
}
250+
251+
/// Checks a `map_or_else` call for a `Result` variant query.
252+
///
253+
/// `expr` is the complete method call, `recv` is its `Result` receiver, `def` is the lazy default
254+
/// closure, and `map` is the mapping closure.
255+
pub(super) fn check_map_or_else<'tcx>(
256+
cx: &LateContext<'tcx>,
257+
expr: &'tcx Expr<'tcx>,
258+
recv: &'tcx Expr<'tcx>,
259+
def: &'tcx Expr<'tcx>,
260+
map: &'tcx Expr<'tcx>,
261+
) {
262+
check_result_variant_query(cx, expr, recv, def, map, closure_bool_constant);
263+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//@aux-build:proc_macros.rs
2+
#![warn(clippy::unnecessary_map_or)]
3+
4+
#[macro_use]
5+
extern crate proc_macros;
6+
7+
const TRUE: bool = true;
8+
const FALSE: bool = false;
9+
10+
fn main() {
11+
let result = Ok::<i32, i32>(1);
12+
13+
let _ = result.is_ok();
14+
//~^ unnecessary_map_or
15+
let _ = result.is_err();
16+
//~^ unnecessary_map_or
17+
let _ = result.is_ok();
18+
//~^ unnecessary_map_or
19+
let _ = result.is_ok();
20+
//~^ unnecessary_map_or
21+
22+
let _ = result.is_ok();
23+
//~^ unnecessary_map_or
24+
let _ = result.is_err();
25+
//~^ unnecessary_map_or
26+
let _ = result.is_ok();
27+
//~^ unnecessary_map_or
28+
let _ = result.is_ok();
29+
//~^ unnecessary_map_or
30+
31+
// Calls in a closure body may have side effects. The lint does not inspect the callee body.
32+
let _ = result.map_or_else(
33+
|_| {
34+
std::hint::black_box(());
35+
false
36+
},
37+
|_| true,
38+
);
39+
let _ = result.map_or_else(|error| error > 0, |_| true);
40+
let _ = result.map_or_else(|_| false, |value| value > 0);
41+
42+
external! {
43+
let _ = Ok::<i32, i32>(1).map_or(false, |_| true);
44+
let _ = Ok::<i32, i32>(1).map_or_else(|_| false, |_| true);
45+
}
46+
47+
with_span! {
48+
let _ = Ok::<i32, i32>(1).map_or(false, |_| true);
49+
let _ = Ok::<i32, i32>(1).map_or_else(|_| false, |_| true);
50+
}
51+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//@aux-build:proc_macros.rs
2+
#![warn(clippy::unnecessary_map_or)]
3+
4+
#[macro_use]
5+
extern crate proc_macros;
6+
7+
const TRUE: bool = true;
8+
const FALSE: bool = false;
9+
10+
fn main() {
11+
let result = Ok::<i32, i32>(1);
12+
13+
let _ = result.map_or(false, |_| true);
14+
//~^ unnecessary_map_or
15+
let _ = result.map_or(true, |_| false);
16+
//~^ unnecessary_map_or
17+
let _ = result.map_or(false, |_: i32| true);
18+
//~^ unnecessary_map_or
19+
let _ = result.map_or(!true, |_| TRUE);
20+
//~^ unnecessary_map_or
21+
22+
let _ = result.map_or_else(|_| false, |_| true);
23+
//~^ unnecessary_map_or
24+
let _ = result.map_or_else(|_| true, |_| false);
25+
//~^ unnecessary_map_or
26+
let _ = result.map_or_else(|_: i32| false, |_: i32| true);
27+
//~^ unnecessary_map_or
28+
let _ = result.map_or_else(|_| FALSE, |_| !false);
29+
//~^ unnecessary_map_or
30+
31+
// Calls in a closure body may have side effects. The lint does not inspect the callee body.
32+
let _ = result.map_or_else(
33+
|_| {
34+
std::hint::black_box(());
35+
false
36+
},
37+
|_| true,
38+
);
39+
let _ = result.map_or_else(|error| error > 0, |_| true);
40+
let _ = result.map_or_else(|_| false, |value| value > 0);
41+
42+
external! {
43+
let _ = Ok::<i32, i32>(1).map_or(false, |_| true);
44+
let _ = Ok::<i32, i32>(1).map_or_else(|_| false, |_| true);
45+
}
46+
47+
with_span! {
48+
let _ = Ok::<i32, i32>(1).map_or(false, |_| true);
49+
let _ = Ok::<i32, i32>(1).map_or_else(|_| false, |_| true);
50+
}
51+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
error: this `map_or` can be simplified
2+
--> tests/ui/unnecessary_map_or_result_bool.rs:13:20
3+
|
4+
LL | let _ = result.map_or(false, |_| true);
5+
| ^^^^^^-----------------
6+
| |
7+
| help: use `is_ok` instead: `is_ok()`
8+
|
9+
= note: `-D clippy::unnecessary-map-or` implied by `-D warnings`
10+
= help: to override `-D warnings` add `#[allow(clippy::unnecessary_map_or)]`
11+
12+
error: this `map_or` can be simplified
13+
--> tests/ui/unnecessary_map_or_result_bool.rs:15:20
14+
|
15+
LL | let _ = result.map_or(true, |_| false);
16+
| ^^^^^^-----------------
17+
| |
18+
| help: use `is_err` instead: `is_err()`
19+
20+
error: this `map_or` can be simplified
21+
--> tests/ui/unnecessary_map_or_result_bool.rs:17:20
22+
|
23+
LL | let _ = result.map_or(false, |_: i32| true);
24+
| ^^^^^^----------------------
25+
| |
26+
| help: use `is_ok` instead: `is_ok()`
27+
28+
error: this `map_or` can be simplified
29+
--> tests/ui/unnecessary_map_or_result_bool.rs:19:20
30+
|
31+
LL | let _ = result.map_or(!true, |_| TRUE);
32+
| ^^^^^^-----------------
33+
| |
34+
| help: use `is_ok` instead: `is_ok()`
35+
36+
error: this `map_or_else` can be simplified
37+
--> tests/ui/unnecessary_map_or_result_bool.rs:22:20
38+
|
39+
LL | let _ = result.map_or_else(|_| false, |_| true);
40+
| ^^^^^^^^^^^---------------------
41+
| |
42+
| help: use `is_ok` instead: `is_ok()`
43+
44+
error: this `map_or_else` can be simplified
45+
--> tests/ui/unnecessary_map_or_result_bool.rs:24:20
46+
|
47+
LL | let _ = result.map_or_else(|_| true, |_| false);
48+
| ^^^^^^^^^^^---------------------
49+
| |
50+
| help: use `is_err` instead: `is_err()`
51+
52+
error: this `map_or_else` can be simplified
53+
--> tests/ui/unnecessary_map_or_result_bool.rs:26:20
54+
|
55+
LL | let _ = result.map_or_else(|_: i32| false, |_: i32| true);
56+
| ^^^^^^^^^^^-------------------------------
57+
| |
58+
| help: use `is_ok` instead: `is_ok()`
59+
60+
error: this `map_or_else` can be simplified
61+
--> tests/ui/unnecessary_map_or_result_bool.rs:28:20
62+
|
63+
LL | let _ = result.map_or_else(|_| FALSE, |_| !false);
64+
| ^^^^^^^^^^^-----------------------
65+
| |
66+
| help: use `is_ok` instead: `is_ok()`
67+
68+
error: aborting due to 8 previous errors
69+
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//@no-rustfix: `is_ok` and `is_err` can change significant drop order
2+
#![warn(clippy::unnecessary_map_or)]
3+
4+
fn main() {
5+
let mutex = std::sync::Mutex::new(());
6+
7+
let result = Ok::<_, ()>(mutex.lock().unwrap());
8+
let _ = result.map_or(false, |_| true);
9+
//~^ unnecessary_map_or
10+
11+
let result = Err::<(), _>(mutex.lock().unwrap());
12+
let _ = result.map_or_else(|_| true, |_| false);
13+
//~^ unnecessary_map_or
14+
}

0 commit comments

Comments
 (0)