Skip to content

Commit c6aeb28

Browse files
committed
Auto merge of rust-lang#11865 - yuxqiu:map_unwrap_or_default, r=Jarcho
feat: add `manual_is_variant_and` lint changelog: add a new lint [`manual_is_variant_and`]. - Replace `option.map(f).unwrap_or_default()` and `result.map(f).unwrap_or_default()` with `option.is_some_and(f)` and `result.is_ok_and(f)` where `f` is a function or closure that returns `bool`. - MSRV is set to 1.70.0 for this lint; when `is_some_and` and `is_ok_and` was stabilised --- For example, for the following code: ```rust let opt = Some(0); opt.map(|x| x > 1).unwrap_or_default(); ``` It suggests to instead write: ```rust let opt = Some(0); opt.is_some_and(|x| x > 1) ```
2 parents b19b5f2 + c4a80f2 commit c6aeb28

11 files changed

+290
-6
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -5294,6 +5294,7 @@ Released 2018-09-13
52945294
[`manual_is_ascii_check`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_ascii_check
52955295
[`manual_is_finite`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_finite
52965296
[`manual_is_infinite`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_infinite
5297+
[`manual_is_variant_and`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_variant_and
52975298
[`manual_let_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_let_else
52985299
[`manual_main_separator_str`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_main_separator_str
52995300
[`manual_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_map

clippy_config/src/msrvs.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ macro_rules! msrv_aliases {
1717
// names may refer to stabilized feature flags or library items
1818
msrv_aliases! {
1919
1,71,0 { TUPLE_ARRAY_CONVERSIONS, BUILD_HASHER_HASH_ONE }
20-
1,70,0 { OPTION_IS_SOME_AND, BINARY_HEAP_RETAIN }
20+
1,70,0 { OPTION_RESULT_IS_VARIANT_AND, BINARY_HEAP_RETAIN }
2121
1,68,0 { PATH_MAIN_SEPARATOR_STR }
2222
1,65,0 { LET_ELSE, POINTER_CAST_CONSTNESS }
2323
1,62,0 { BOOL_THEN_SOME, DEFAULT_ENUM_ATTRIBUTE }

clippy_lints/src/declared_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
385385
crate::methods::JOIN_ABSOLUTE_PATHS_INFO,
386386
crate::methods::MANUAL_FILTER_MAP_INFO,
387387
crate::methods::MANUAL_FIND_MAP_INFO,
388+
crate::methods::MANUAL_IS_VARIANT_AND_INFO,
388389
crate::methods::MANUAL_NEXT_BACK_INFO,
389390
crate::methods::MANUAL_OK_OR_INFO,
390391
crate::methods::MANUAL_SATURATING_ARITHMETIC_INFO,

clippy_lints/src/lines_filter_map_ok.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,7 @@ fn should_lint(cx: &LateContext<'_>, args: &[Expr<'_>], method_str: &str) -> boo
9696
ExprKind::Path(qpath) => cx
9797
.qpath_res(qpath, fm_arg.hir_id)
9898
.opt_def_id()
99-
.map(|did| match_def_path(cx, did, &paths::CORE_RESULT_OK_METHOD))
100-
.unwrap_or_default(),
99+
.is_some_and(|did| match_def_path(cx, did, &paths::CORE_RESULT_OK_METHOD)),
101100
// Detect `|x| x.ok()`
102101
ExprKind::Closure(Closure { body, .. }) => {
103102
if let Body {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
use clippy_config::msrvs::{Msrv, OPTION_RESULT_IS_VARIANT_AND};
2+
use clippy_utils::diagnostics::span_lint_and_sugg;
3+
use clippy_utils::source::snippet;
4+
use clippy_utils::ty::is_type_diagnostic_item;
5+
use rustc_errors::Applicability;
6+
use rustc_lint::LateContext;
7+
use rustc_span::{sym, Span};
8+
9+
use super::MANUAL_IS_VARIANT_AND;
10+
11+
pub(super) fn check<'tcx>(
12+
cx: &LateContext<'_>,
13+
expr: &'tcx rustc_hir::Expr<'_>,
14+
map_recv: &'tcx rustc_hir::Expr<'_>,
15+
map_arg: &'tcx rustc_hir::Expr<'_>,
16+
map_span: Span,
17+
msrv: &Msrv,
18+
) {
19+
// Don't lint if:
20+
21+
// 1. the `expr` is generated by a macro
22+
if expr.span.from_expansion() {
23+
return;
24+
}
25+
26+
// 2. the caller of `map()` is neither `Option` nor `Result`
27+
let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(map_recv), sym::Option);
28+
let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(map_recv), sym::Result);
29+
if !is_option && !is_result {
30+
return;
31+
}
32+
33+
// 3. the caller of `unwrap_or_default` is neither `Option<bool>` nor `Result<bool, _>`
34+
if !cx.typeck_results().expr_ty(expr).is_bool() {
35+
return;
36+
}
37+
38+
// 4. msrv doesn't meet `OPTION_RESULT_IS_VARIANT_AND`
39+
if !msrv.meets(OPTION_RESULT_IS_VARIANT_AND) {
40+
return;
41+
}
42+
43+
let lint_msg = if is_option {
44+
"called `map(<f>).unwrap_or_default()` on an `Option` value"
45+
} else {
46+
"called `map(<f>).unwrap_or_default()` on a `Result` value"
47+
};
48+
let suggestion = if is_option { "is_some_and" } else { "is_ok_and" };
49+
50+
span_lint_and_sugg(
51+
cx,
52+
MANUAL_IS_VARIANT_AND,
53+
expr.span.with_lo(map_span.lo()),
54+
lint_msg,
55+
"use",
56+
format!("{}({})", suggestion, snippet(cx, map_arg.span, "..")),
57+
Applicability::MachineApplicable,
58+
);
59+
}

clippy_lints/src/methods/mod.rs

+35-1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ mod iter_skip_zero;
5151
mod iter_with_drain;
5252
mod iterator_step_by_zero;
5353
mod join_absolute_paths;
54+
mod manual_is_variant_and;
5455
mod manual_next_back;
5556
mod manual_ok_or;
5657
mod manual_saturating_arithmetic;
@@ -3829,6 +3830,32 @@ declare_clippy_lint! {
38293830
"filtering an iterator over `Result`s for `Ok` can be achieved with `flatten`"
38303831
}
38313832

3833+
declare_clippy_lint! {
3834+
/// Checks for usage of `option.map(f).unwrap_or_default()` and `result.map(f).unwrap_or_default()` where f is a function or closure that returns the `bool` type.
3835+
///
3836+
/// ### Why is this bad?
3837+
/// Readability. These can be written more concisely as `option.is_some_and(f)` and `result.is_ok_and(f)`.
3838+
///
3839+
/// ### Example
3840+
/// ```no_run
3841+
/// # let option = Some(1);
3842+
/// # let result: Result<usize, ()> = Ok(1);
3843+
/// option.map(|a| a > 10).unwrap_or_default();
3844+
/// result.map(|a| a > 10).unwrap_or_default();
3845+
/// ```
3846+
/// Use instead:
3847+
/// ```no_run
3848+
/// # let option = Some(1);
3849+
/// # let result: Result<usize, ()> = Ok(1);
3850+
/// option.is_some_and(|a| a > 10);
3851+
/// result.is_ok_and(|a| a > 10);
3852+
/// ```
3853+
#[clippy::version = "1.76.0"]
3854+
pub MANUAL_IS_VARIANT_AND,
3855+
pedantic,
3856+
"using `.map(f).unwrap_or_default()`, which is more succinctly expressed as `is_some_and(f)` or `is_ok_and(f)`"
3857+
}
3858+
38323859
pub struct Methods {
38333860
avoid_breaking_exported_api: bool,
38343861
msrv: Msrv,
@@ -3983,6 +4010,7 @@ impl_lint_pass!(Methods => [
39834010
RESULT_FILTER_MAP,
39844011
ITER_FILTER_IS_SOME,
39854012
ITER_FILTER_IS_OK,
4013+
MANUAL_IS_VARIANT_AND,
39864014
]);
39874015

39884016
/// Extracts a method call name, args, and `Span` of the method name.
@@ -4664,7 +4692,13 @@ impl Methods {
46644692
}
46654693
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
46664694
},
4667-
("unwrap_or_default" | "unwrap_unchecked" | "unwrap_err_unchecked", []) => {
4695+
("unwrap_or_default", []) => {
4696+
if let Some(("map", m_recv, [arg], span, _)) = method_call(recv) {
4697+
manual_is_variant_and::check(cx, expr, m_recv, arg, span, &self.msrv);
4698+
}
4699+
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
4700+
},
4701+
("unwrap_unchecked" | "unwrap_err_unchecked", []) => {
46684702
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
46694703
},
46704704
("unwrap_or_else", [u_arg]) => {

clippy_lints/src/methods/option_map_unwrap_or.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ pub(super) fn check<'tcx>(
7272
}
7373

7474
// is_some_and is stabilised && `unwrap_or` argument is false; suggest `is_some_and` instead
75-
let suggest_is_some_and = msrv.meets(msrvs::OPTION_IS_SOME_AND)
75+
let suggest_is_some_and = msrv.meets(msrvs::OPTION_RESULT_IS_VARIANT_AND)
7676
&& matches!(&unwrap_arg.kind, ExprKind::Lit(lit)
7777
if matches!(lit.node, rustc_ast::LitKind::Bool(false)));
7878

clippy_lints/src/methods/path_ends_with_ext.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ pub(super) fn check(
3333
&& path.chars().all(char::is_alphanumeric)
3434
{
3535
let mut sugg = snippet(cx, recv.span, "..").into_owned();
36-
if msrv.meets(msrvs::OPTION_IS_SOME_AND) {
36+
if msrv.meets(msrvs::OPTION_RESULT_IS_VARIANT_AND) {
3737
let _ = write!(sugg, r#".extension().is_some_and(|ext| ext == "{path}")"#);
3838
} else {
3939
let _ = write!(sugg, r#".extension().map_or(false, |ext| ext == "{path}")"#);

tests/ui/manual_is_variant_and.fixed

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//@aux-build:option_helpers.rs
2+
#![warn(clippy::manual_is_variant_and)]
3+
4+
#[macro_use]
5+
extern crate option_helpers;
6+
7+
#[rustfmt::skip]
8+
fn option_methods() {
9+
let opt = Some(1);
10+
11+
// Check for `option.map(_).unwrap_or_default()` use.
12+
// Single line case.
13+
let _ = opt.is_some_and(|x| x > 1);
14+
// Multi-line cases.
15+
let _ = opt.is_some_and(|x| {
16+
x > 1
17+
});
18+
let _ = opt.is_some_and(|x| x > 1);
19+
let _ = opt
20+
.is_some_and(|x| x > 1);
21+
22+
// won't fix because the return type of the closure is not `bool`
23+
let _ = opt.map(|x| x + 1).unwrap_or_default();
24+
25+
let opt2 = Some('a');
26+
let _ = opt2.is_some_and(char::is_alphanumeric); // should lint
27+
let _ = opt_map!(opt2, |x| x == 'a').unwrap_or_default(); // should not lint
28+
}
29+
30+
#[rustfmt::skip]
31+
fn result_methods() {
32+
let res: Result<i32, ()> = Ok(1);
33+
34+
// multi line cases
35+
let _ = res.is_ok_and(|x| {
36+
x > 1
37+
});
38+
let _ = res.is_ok_and(|x| x > 1);
39+
40+
// won't fix because the return type of the closure is not `bool`
41+
let _ = res.map(|x| x + 1).unwrap_or_default();
42+
43+
let res2: Result<char, ()> = Ok('a');
44+
let _ = res2.is_ok_and(char::is_alphanumeric); // should lint
45+
let _ = opt_map!(res2, |x| x == 'a').unwrap_or_default(); // should not lint
46+
}
47+
48+
fn main() {
49+
option_methods();
50+
result_methods();
51+
}

tests/ui/manual_is_variant_and.rs

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//@aux-build:option_helpers.rs
2+
#![warn(clippy::manual_is_variant_and)]
3+
4+
#[macro_use]
5+
extern crate option_helpers;
6+
7+
#[rustfmt::skip]
8+
fn option_methods() {
9+
let opt = Some(1);
10+
11+
// Check for `option.map(_).unwrap_or_default()` use.
12+
// Single line case.
13+
let _ = opt.map(|x| x > 1)
14+
// Should lint even though this call is on a separate line.
15+
.unwrap_or_default();
16+
// Multi-line cases.
17+
let _ = opt.map(|x| {
18+
x > 1
19+
}
20+
).unwrap_or_default();
21+
let _ = opt.map(|x| x > 1).unwrap_or_default();
22+
let _ = opt
23+
.map(|x| x > 1)
24+
.unwrap_or_default();
25+
26+
// won't fix because the return type of the closure is not `bool`
27+
let _ = opt.map(|x| x + 1).unwrap_or_default();
28+
29+
let opt2 = Some('a');
30+
let _ = opt2.map(char::is_alphanumeric).unwrap_or_default(); // should lint
31+
let _ = opt_map!(opt2, |x| x == 'a').unwrap_or_default(); // should not lint
32+
}
33+
34+
#[rustfmt::skip]
35+
fn result_methods() {
36+
let res: Result<i32, ()> = Ok(1);
37+
38+
// multi line cases
39+
let _ = res.map(|x| {
40+
x > 1
41+
}
42+
).unwrap_or_default();
43+
let _ = res.map(|x| x > 1)
44+
.unwrap_or_default();
45+
46+
// won't fix because the return type of the closure is not `bool`
47+
let _ = res.map(|x| x + 1).unwrap_or_default();
48+
49+
let res2: Result<char, ()> = Ok('a');
50+
let _ = res2.map(char::is_alphanumeric).unwrap_or_default(); // should lint
51+
let _ = opt_map!(res2, |x| x == 'a').unwrap_or_default(); // should not lint
52+
}
53+
54+
fn main() {
55+
option_methods();
56+
result_methods();
57+
}

tests/ui/manual_is_variant_and.stderr

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
error: called `map(<f>).unwrap_or_default()` on an `Option` value
2+
--> $DIR/manual_is_variant_and.rs:13:17
3+
|
4+
LL | let _ = opt.map(|x| x > 1)
5+
| _________________^
6+
LL | | // Should lint even though this call is on a separate line.
7+
LL | | .unwrap_or_default();
8+
| |____________________________^ help: use: `is_some_and(|x| x > 1)`
9+
|
10+
= note: `-D clippy::manual-is-variant-and` implied by `-D warnings`
11+
= help: to override `-D warnings` add `#[allow(clippy::manual_is_variant_and)]`
12+
13+
error: called `map(<f>).unwrap_or_default()` on an `Option` value
14+
--> $DIR/manual_is_variant_and.rs:17:17
15+
|
16+
LL | let _ = opt.map(|x| {
17+
| _________________^
18+
LL | | x > 1
19+
LL | | }
20+
LL | | ).unwrap_or_default();
21+
| |_________________________^
22+
|
23+
help: use
24+
|
25+
LL ~ let _ = opt.is_some_and(|x| {
26+
LL + x > 1
27+
LL ~ });
28+
|
29+
30+
error: called `map(<f>).unwrap_or_default()` on an `Option` value
31+
--> $DIR/manual_is_variant_and.rs:21:17
32+
|
33+
LL | let _ = opt.map(|x| x > 1).unwrap_or_default();
34+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `is_some_and(|x| x > 1)`
35+
36+
error: called `map(<f>).unwrap_or_default()` on an `Option` value
37+
--> $DIR/manual_is_variant_and.rs:23:10
38+
|
39+
LL | .map(|x| x > 1)
40+
| __________^
41+
LL | | .unwrap_or_default();
42+
| |____________________________^ help: use: `is_some_and(|x| x > 1)`
43+
44+
error: called `map(<f>).unwrap_or_default()` on an `Option` value
45+
--> $DIR/manual_is_variant_and.rs:30:18
46+
|
47+
LL | let _ = opt2.map(char::is_alphanumeric).unwrap_or_default(); // should lint
48+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `is_some_and(char::is_alphanumeric)`
49+
50+
error: called `map(<f>).unwrap_or_default()` on a `Result` value
51+
--> $DIR/manual_is_variant_and.rs:39:17
52+
|
53+
LL | let _ = res.map(|x| {
54+
| _________________^
55+
LL | | x > 1
56+
LL | | }
57+
LL | | ).unwrap_or_default();
58+
| |_________________________^
59+
|
60+
help: use
61+
|
62+
LL ~ let _ = res.is_ok_and(|x| {
63+
LL + x > 1
64+
LL ~ });
65+
|
66+
67+
error: called `map(<f>).unwrap_or_default()` on a `Result` value
68+
--> $DIR/manual_is_variant_and.rs:43:17
69+
|
70+
LL | let _ = res.map(|x| x > 1)
71+
| _________________^
72+
LL | | .unwrap_or_default();
73+
| |____________________________^ help: use: `is_ok_and(|x| x > 1)`
74+
75+
error: called `map(<f>).unwrap_or_default()` on a `Result` value
76+
--> $DIR/manual_is_variant_and.rs:50:18
77+
|
78+
LL | let _ = res2.map(char::is_alphanumeric).unwrap_or_default(); // should lint
79+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `is_ok_and(char::is_alphanumeric)`
80+
81+
error: aborting due to 8 previous errors
82+

0 commit comments

Comments
 (0)