Add a check for some followed by filter (#15745)

*[View all
comments](https://triagebot.infra.rust-lang.org/gh-comments/rust-lang/rust-clippy/pull/15745)*

Add a check for some followed by filter.

The program will suggest using the `then_some` method.

changelog: [`filter_some`]:
https://rust-lang.github.io/rust-clippy/master/index.html#filter_some
This commit is contained in:
Ada Alakbarova
2026-05-04 11:09:58 +00:00
committed by GitHub
12 changed files with 424 additions and 22 deletions
+1
View File
@@ -7238,6 +7238,7 @@ Released 2018-09-13
[`skip_while_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#skip_while_next
[`sliced_string_as_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#sliced_string_as_bytes
[`slow_vector_initialization`]: https://rust-lang.github.io/rust-clippy/master/index.html#slow_vector_initialization
[`some_filter`]: https://rust-lang.github.io/rust-clippy/master/index.html#some_filter
[`stable_sort_primitive`]: https://rust-lang.github.io/rust-clippy/master/index.html#stable_sort_primitive
[`std_instead_of_alloc`]: https://rust-lang.github.io/rust-clippy/master/index.html#std_instead_of_alloc
[`std_instead_of_core`]: https://rust-lang.github.io/rust-clippy/master/index.html#std_instead_of_core
+1
View File
@@ -474,6 +474,7 @@
crate::methods::SINGLE_CHAR_ADD_STR_INFO,
crate::methods::SKIP_WHILE_NEXT_INFO,
crate::methods::SLICED_STRING_AS_BYTES_INFO,
crate::methods::SOME_FILTER_INFO,
crate::methods::STABLE_SORT_PRIMITIVE_INFO,
crate::methods::STR_SPLIT_AT_NEWLINE_INFO,
crate::methods::STRING_EXTEND_CHARS_INFO,
+26
View File
@@ -111,6 +111,7 @@
mod single_char_add_str;
mod skip_while_next;
mod sliced_string_as_bytes;
mod some_filter;
mod stable_sort_primitive;
mod str_split;
mod str_splitn;
@@ -3577,6 +3578,29 @@
"slicing a string and immediately calling as_bytes is less efficient and can lead to panics"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for usage of `Some(x).filter(|_| predicate)`.
///
/// ### Why is this bad?
/// Readability, this can be written more concisely as `predicate.then_some(x)`.
///
/// ### Example
/// ```no_run
/// let x = false;
/// Some(0).filter(|_| x);
/// ```
/// Use instead:
/// ```no_run
/// let x = false;
/// x.then_some(0);
/// ```
#[clippy::version = "1.97.0"]
pub SOME_FILTER,
complexity,
"using `Some(x).filter(|_| predicate)`, which is more succinctly expressed as `predicate.then(x)`"
}
declare_clippy_lint! {
/// ### What it does
/// When sorting primitive values (integers, bools, chars, as well
@@ -4900,6 +4924,7 @@
SINGLE_CHAR_ADD_STR,
SKIP_WHILE_NEXT,
SLICED_STRING_AS_BYTES,
SOME_FILTER,
STABLE_SORT_PRIMITIVE,
STRING_EXTEND_CHARS,
STRING_LIT_CHARS_ANY,
@@ -5307,6 +5332,7 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
// use the sourcemap to get the span of the closure
iter_filter::check(cx, expr, arg, span);
}
some_filter::check(cx, expr, recv, arg, self.msrv);
},
(sym::find, [arg]) => {
if let Some((sym::cloned, recv2, [], _span2, _)) = method_call(recv) {
+65
View File
@@ -0,0 +1,65 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
use clippy_utils::{as_some_expr, pat_is_wild, peel_blocks, span_contains_comment};
use rustc_ast::util::parser::ExprPrecedence;
use rustc_errors::Applicability;
use rustc_hir::{Body, Expr, ExprKind};
use rustc_lint::LateContext;
use super::SOME_FILTER;
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'tcx>,
recv: &'tcx Expr<'tcx>,
arg: &'tcx Expr<'tcx>,
msrv: Msrv,
) {
let (condition, value) = if let Some(value) = as_some_expr(cx, recv)
&& let ExprKind::Closure(c) = arg.kind
&& let Body {
params: [p],
value: condition,
} = cx.tcx.hir_body(c.body)
&& pat_is_wild(cx, &p.pat.kind, arg)
&& msrv.meets(cx, msrvs::BOOL_THEN_SOME)
{
(condition, value)
} else {
return;
};
span_lint_and_then(
cx,
SOME_FILTER,
expr.span,
"use of `Some(x).filter(|_| predicate)`",
|diag| {
let condition = if span_contains_comment(cx, condition.span) {
condition
} else {
peel_blocks(condition)
};
let mut applicability = Applicability::MaybeIncorrect;
let (condition_text, condition_is_macro) =
snippet_with_context(cx, condition.span, arg.span.ctxt(), "_", &mut applicability);
let parentheses = !condition_is_macro && cx.precedence(condition) < ExprPrecedence::Unambiguous;
let value_text = snippet_with_applicability(cx, value.span, "_", &mut applicability);
let sugg = format!(
"{}{condition_text}{}.then_some({value_text})",
if parentheses { "(" } else { "" },
if parentheses { ")" } else { "" },
);
diag.span_suggestion_verbose(
expr.span,
"consider using `bool::then_some` instead",
sugg,
applicability,
);
diag.note(
"this change will alter the order in which the condition and \
the value are evaluated",
);
},
);
}
+1
View File
@@ -2,6 +2,7 @@
#![allow(
unused_variables,
clippy::question_mark,
clippy::some_filter,
clippy::useless_vec,
clippy::nonminimal_bool
)]
+1
View File
@@ -2,6 +2,7 @@
#![allow(
unused_variables,
clippy::question_mark,
clippy::some_filter,
clippy::useless_vec,
clippy::nonminimal_bool
)]
+20 -20
View File
@@ -1,5 +1,5 @@
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:10:5
--> tests/ui/manual_filter.rs:11:5
|
LL | / match Some(0) {
LL | |
@@ -14,7 +14,7 @@ LL | | };
= help: to override `-D warnings` add `#[allow(clippy::manual_filter)]`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:22:5
--> tests/ui/manual_filter.rs:23:5
|
LL | / match Some(1) {
LL | |
@@ -26,7 +26,7 @@ LL | | };
| |_____^ help: try: `Some(1).filter(|&x| x <= 0)`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:34:5
--> tests/ui/manual_filter.rs:35:5
|
LL | / match Some(2) {
LL | |
@@ -38,7 +38,7 @@ LL | | };
| |_____^ help: try: `Some(2).filter(|&x| x <= 0)`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:46:5
--> tests/ui/manual_filter.rs:47:5
|
LL | / match Some(3) {
LL | |
@@ -50,7 +50,7 @@ LL | | };
| |_____^ help: try: `Some(3).filter(|&x| x > 0)`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:59:5
--> tests/ui/manual_filter.rs:60:5
|
LL | / match y {
LL | |
@@ -62,7 +62,7 @@ LL | | };
| |_____^ help: try: `y.filter(|&x| x <= 0)`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:72:5
--> tests/ui/manual_filter.rs:73:5
|
LL | / match Some(5) {
LL | |
@@ -74,7 +74,7 @@ LL | | };
| |_____^ help: try: `Some(5).filter(|&x| x > 0)`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:84:5
--> tests/ui/manual_filter.rs:85:5
|
LL | / match Some(6) {
LL | |
@@ -86,7 +86,7 @@ LL | | };
| |_____^ help: try: `Some(6).as_ref().filter(|&x| x > &0)`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:97:5
--> tests/ui/manual_filter.rs:98:5
|
LL | / match Some(String::new()) {
LL | |
@@ -98,7 +98,7 @@ LL | | };
| |_____^ help: try: `Some(String::new()).filter(|x| external_cond)`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:109:5
--> tests/ui/manual_filter.rs:110:5
|
LL | / if let Some(x) = Some(7) {
LL | |
@@ -109,7 +109,7 @@ LL | | };
| |_____^ help: try: `Some(7).filter(|&x| external_cond)`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:116:5
--> tests/ui/manual_filter.rs:117:5
|
LL | / match &Some(8) {
LL | |
@@ -121,7 +121,7 @@ LL | | };
| |_____^ help: try: `Some(8).filter(|&x| x != 0)`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:128:5
--> tests/ui/manual_filter.rs:129:5
|
LL | / match Some(9) {
LL | |
@@ -133,7 +133,7 @@ LL | | };
| |_____^ help: try: `Some(9).filter(|&x| x > 10 && x < 100)`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:155:5
--> tests/ui/manual_filter.rs:156:5
|
LL | / match Some(11) {
LL | |
@@ -153,7 +153,7 @@ LL ~ });
|
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:200:13
--> tests/ui/manual_filter.rs:201:13
|
LL | let _ = match Some(14) {
| _____________^
@@ -166,7 +166,7 @@ LL | | };
| |_____^ help: try: `Some(14).filter(|&x| unsafe { f(x) })`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:211:13
--> tests/ui/manual_filter.rs:212:13
|
LL | let _ = match Some(15) {
| _____________^
@@ -177,7 +177,7 @@ LL | | };
| |_____^ help: try: `Some(15).filter(|&x| unsafe { f(x) })`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:220:12
--> tests/ui/manual_filter.rs:221:12
|
LL | } else if let Some(x) = Some(16) {
| ____________^
@@ -190,31 +190,31 @@ LL | | };
| |_____^ help: try: `{ Some(16).filter(|&x| x % 2 == 0) }`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:303:9
--> tests/ui/manual_filter.rs:304:9
|
LL | opt.and_then(|x| if x == 0 { None } else { Some(x) });
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter(|&x| x != 0)`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:307:9
--> tests/ui/manual_filter.rs:308:9
|
LL | opt.and_then(move |x| if x == y { Some(x) } else { None });
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter(move |&x| x == y)`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:311:10
--> tests/ui/manual_filter.rs:312:10
|
LL | opt1.and_then(|s| if s.len() > 2 { Some(s) } else { None });
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter(|s| s.len() > 2)`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:317:9
--> tests/ui/manual_filter.rs:318:9
|
LL | opt.and_then(|x| if unsafe { f(x as u32) } { Some(x) } else { None });
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter(|&x| unsafe { f(x as u32) })`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:319:9
--> tests/ui/manual_filter.rs:320:9
|
LL | opt.and_then(|x| unsafe { if f(x as u32) { Some(x) } else { None } });
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter(|&x| unsafe { f(x as u32) })`
+1 -1
View File
@@ -1,5 +1,5 @@
#![warn(clippy::option_filter_map)]
#![allow(clippy::map_flatten, clippy::unnecessary_map_on_constructor)]
#![allow(clippy::map_flatten, clippy::some_filter, clippy::unnecessary_map_on_constructor)]
fn main() {
let _ = Some(Some(1)).flatten();
+1 -1
View File
@@ -1,5 +1,5 @@
#![warn(clippy::option_filter_map)]
#![allow(clippy::map_flatten, clippy::unnecessary_map_on_constructor)]
#![allow(clippy::map_flatten, clippy::some_filter, clippy::unnecessary_map_on_constructor)]
fn main() {
let _ = Some(Some(1)).filter(Option::is_some).map(Option::unwrap);
+70
View File
@@ -0,0 +1,70 @@
#![warn(clippy::some_filter)]
#![allow(clippy::const_is_empty)]
macro_rules! unchanged {
($result:expr) => {
$result
};
}
macro_rules! condition {
($condition:expr) => {
$condition || false
};
}
#[clippy::msrv = "1.61"]
fn older() {
let _ = Some(0).filter(|_| false);
}
#[clippy::msrv = "1.62"]
fn newer() {
let _ = false.then_some(0);
//~^ some_filter
}
fn main() {
let _ = false.then_some(0);
//~^ some_filter
// The condition contains an operator. The program should add parentheses.
let _ = (1 == 0).then_some(0);
//~^ some_filter
let _ = match 0 {
//~^ some_filter
0 => false,
1 => true,
_ => true,
}.then_some(0);
// The argument to filter requires the value in the option. The program
// can't figure out how to change it. It should leave it alone for now.
let _ = Some(0).filter(|x| *x == 0);
// The expression is a macro argument. The program should change the macro
// argument. It should not expand the macro.
let _ = unchanged!(false.then_some(0));
//~^ some_filter
let _ = vec![false].is_empty().then_some(0);
//~^ some_filter
// The condition is a macro that expands to an expression containing an
// operator. The program should not add parentheses.
let _ = condition!(false).then_some(0);
//~^ some_filter
(1 == 0).then_some(String::from(
//~^ some_filter
"looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong",
));
{
//~^ some_filter
"looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong".is_empty()
}.then_some(5);
"looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong".is_empty().then_some(String::from(
//~^ some_filter
"looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong",
));
}
+74
View File
@@ -0,0 +1,74 @@
#![warn(clippy::some_filter)]
#![allow(clippy::const_is_empty)]
macro_rules! unchanged {
($result:expr) => {
$result
};
}
macro_rules! condition {
($condition:expr) => {
$condition || false
};
}
#[clippy::msrv = "1.61"]
fn older() {
let _ = Some(0).filter(|_| false);
}
#[clippy::msrv = "1.62"]
fn newer() {
let _ = Some(0).filter(|_| false);
//~^ some_filter
}
fn main() {
let _ = Some(0).filter(|_| false);
//~^ some_filter
// The condition contains an operator. The program should add parentheses.
let _ = Some(0).filter(|_| 1 == 0);
//~^ some_filter
let _ = Some(0).filter(|_| match 0 {
//~^ some_filter
0 => false,
1 => true,
_ => true,
});
// The argument to filter requires the value in the option. The program
// can't figure out how to change it. It should leave it alone for now.
let _ = Some(0).filter(|x| *x == 0);
// The expression is a macro argument. The program should change the macro
// argument. It should not expand the macro.
let _ = unchanged!(Some(0).filter(|_| false));
//~^ some_filter
let _ = Some(0).filter(|_| vec![false].is_empty());
//~^ some_filter
// The condition is a macro that expands to an expression containing an
// operator. The program should not add parentheses.
let _ = Some(0).filter(|_| condition!(false));
//~^ some_filter
Some(String::from(
//~^ some_filter
"looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong",
))
.filter(|_| 1 == 0);
Some(5).filter(|_| {
//~^ some_filter
"looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong".is_empty()
});
Some(String::from(
//~^ some_filter
"looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong",
))
.filter(|_| {
"looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong".is_empty()
});
}
+163
View File
@@ -0,0 +1,163 @@
error: use of `Some(x).filter(|_| predicate)`
--> tests/ui/some_filter.rs:23:13
|
LL | let _ = Some(0).filter(|_| false);
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this change will alter the order in which the condition and the value are evaluated
= note: `-D clippy::some-filter` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::some_filter)]`
help: consider using `bool::then_some` instead
|
LL - let _ = Some(0).filter(|_| false);
LL + let _ = false.then_some(0);
|
error: use of `Some(x).filter(|_| predicate)`
--> tests/ui/some_filter.rs:28:13
|
LL | let _ = Some(0).filter(|_| false);
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this change will alter the order in which the condition and the value are evaluated
help: consider using `bool::then_some` instead
|
LL - let _ = Some(0).filter(|_| false);
LL + let _ = false.then_some(0);
|
error: use of `Some(x).filter(|_| predicate)`
--> tests/ui/some_filter.rs:32:13
|
LL | let _ = Some(0).filter(|_| 1 == 0);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this change will alter the order in which the condition and the value are evaluated
help: consider using `bool::then_some` instead
|
LL - let _ = Some(0).filter(|_| 1 == 0);
LL + let _ = (1 == 0).then_some(0);
|
error: use of `Some(x).filter(|_| predicate)`
--> tests/ui/some_filter.rs:35:13
|
LL | let _ = Some(0).filter(|_| match 0 {
| _____________^
LL | |
LL | | 0 => false,
LL | | 1 => true,
LL | | _ => true,
LL | | });
| |______^
|
= note: this change will alter the order in which the condition and the value are evaluated
help: consider using `bool::then_some` instead
|
LL ~ let _ = match 0 {
LL +
LL + 0 => false,
LL + 1 => true,
LL + _ => true,
LL ~ }.then_some(0);
|
error: use of `Some(x).filter(|_| predicate)`
--> tests/ui/some_filter.rs:48:24
|
LL | let _ = unchanged!(Some(0).filter(|_| false));
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this change will alter the order in which the condition and the value are evaluated
help: consider using `bool::then_some` instead
|
LL - let _ = unchanged!(Some(0).filter(|_| false));
LL + let _ = unchanged!(false.then_some(0));
|
error: use of `Some(x).filter(|_| predicate)`
--> tests/ui/some_filter.rs:50:13
|
LL | let _ = Some(0).filter(|_| vec![false].is_empty());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this change will alter the order in which the condition and the value are evaluated
help: consider using `bool::then_some` instead
|
LL - let _ = Some(0).filter(|_| vec![false].is_empty());
LL + let _ = vec![false].is_empty().then_some(0);
|
error: use of `Some(x).filter(|_| predicate)`
--> tests/ui/some_filter.rs:55:13
|
LL | let _ = Some(0).filter(|_| condition!(false));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this change will alter the order in which the condition and the value are evaluated
help: consider using `bool::then_some` instead
|
LL - let _ = Some(0).filter(|_| condition!(false));
LL + let _ = condition!(false).then_some(0);
|
error: use of `Some(x).filter(|_| predicate)`
--> tests/ui/some_filter.rs:58:5
|
LL | / Some(String::from(
LL | |
LL | | "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong",
LL | | ))
LL | | .filter(|_| 1 == 0);
| |_______________________^
|
= note: this change will alter the order in which the condition and the value are evaluated
help: consider using `bool::then_some` instead
|
LL ~ (1 == 0).then_some(String::from(
LL +
LL + "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong",
LL ~ ));
|
error: use of `Some(x).filter(|_| predicate)`
--> tests/ui/some_filter.rs:63:5
|
LL | / Some(5).filter(|_| {
LL | |
LL | | "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong".is_empty()
LL | | });
| |______^
|
= note: this change will alter the order in which the condition and the value are evaluated
help: consider using `bool::then_some` instead
|
LL ~ {
LL +
LL + "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong".is_empty()
LL ~ }.then_some(5);
|
error: use of `Some(x).filter(|_| predicate)`
--> tests/ui/some_filter.rs:67:5
|
LL | / Some(String::from(
LL | |
LL | | "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong",
LL | | ))
LL | | .filter(|_| {
LL | | "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong".is_empty()
LL | | });
| |______^
|
= note: this change will alter the order in which the condition and the value are evaluated
help: consider using `bool::then_some` instead
|
LL ~ "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong".is_empty().then_some(String::from(
LL +
LL + "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong",
LL ~ ));
|
error: aborting due to 10 previous errors