Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6293,6 +6293,7 @@ Released 2018-09-13
[`filter_map_identity`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_map_identity
[`filter_map_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_map_next
[`filter_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_next
[`filter_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_some
[`find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#find_map
[`flat_map_identity`]: https://rust-lang.github.io/rust-clippy/master/index.html#flat_map_identity
[`flat_map_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#flat_map_option
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/declared_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
crate::methods::FILTER_MAP_IDENTITY_INFO,
crate::methods::FILTER_MAP_NEXT_INFO,
crate::methods::FILTER_NEXT_INFO,
crate::methods::FILTER_SOME_INFO,
crate::methods::FLAT_MAP_IDENTITY_INFO,
crate::methods::FLAT_MAP_OPTION_INFO,
crate::methods::FORMAT_COLLECT_INFO,
Expand Down
61 changes: 61 additions & 0 deletions clippy_lints/src/methods/filter_some.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::pat_is_wild;
use clippy_utils::res::{MaybeDef, MaybeResPath};
use clippy_utils::source::{indent_of, reindent_multiline, snippet_with_applicability, snippet_with_context};
use rustc_ast::util::parser::ExprPrecedence;
use rustc_errors::Applicability;
use rustc_hir::LangItem::OptionSome;
use rustc_hir::{Body, Expr, ExprKind};
use rustc_lint::LateContext;

use super::FILTER_SOME;

pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'tcx>,
recv: &'tcx Expr<'tcx>,
arg: &'tcx Expr<'tcx>,
msrv: Msrv,
) {
if !msrv.meets(cx, msrvs::BOOL_THEN_SOME) {
return;
}
let mut applicability = Applicability::MaybeIncorrect;
let value = match recv.kind {
ExprKind::Call(f, [value]) if f.basic_res().ctor_parent(cx).is_lang_item(cx, OptionSome) => value,
_ => return,
};
let condition = if 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)
{
condition
} else {
return;
};
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 { "" },
);
span_lint_and_then(cx, FILTER_SOME, expr.span, "use of `Some(_).filter(_)`", |diag| {
diag.span_suggestion(
expr.span,
"consider using `then_some` instead",
reindent_multiline(&sugg, true, indent_of(cx, expr.span)),
applicability,
);
diag.note(
"this change will alter the order in which the condition and the \
value are evaluated",
);
});
}
26 changes: 26 additions & 0 deletions clippy_lints/src/methods/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ mod filter_map_bool_then;
mod filter_map_identity;
mod filter_map_next;
mod filter_next;
mod filter_some;
mod flat_map_identity;
mod flat_map_option;
mod format_collect;
Expand Down Expand Up @@ -4665,6 +4666,29 @@ declare_clippy_lint! {
"making no use of the \"map closure\" when calling `.map_or_else(|| 2 * k, |n| n)`"
}

declare_clippy_lint! {
/// ### What it does
/// Checks for usage of `Some(_).filter(_)`.
///
/// ### Why is this bad?
/// Readability, this can be written more concisely as `_.then_some(_)`.
///
/// ### Example
/// ```no_run
/// let x = false;
/// Some(0).filter(|_| x);
/// ```
/// Use instead:
/// ```no_run
/// let x = false;
/// x.then_some(0);
/// ```
#[clippy::version = "1.92.0"]
pub FILTER_SOME,
complexity,
"using `Some(x).filter`, which is more succinctly expressed as `.then(x)`"
}

#[expect(clippy::struct_excessive_bools)]
pub struct Methods {
avoid_breaking_exported_api: bool,
Expand Down Expand Up @@ -4847,6 +4871,7 @@ impl_lint_pass!(Methods => [
IP_CONSTANT,
REDUNDANT_ITER_CLONED,
UNNECESSARY_OPTION_MAP_OR_ELSE,
FILTER_SOME,
]);

/// Extracts a method call name, args, and `Span` of the method name.
Expand Down Expand Up @@ -5146,6 +5171,7 @@ impl Methods {
// use the sourcemap to get the span of the closure
iter_filter::check(cx, expr, arg, span);
}
filter_some::check(cx, expr, recv, arg, self.msrv);
},
(sym::find, [arg]) => {
if let Some((sym::cloned, recv2, [], _span2, _)) = method_call(recv) {
Expand Down
51 changes: 51 additions & 0 deletions tests/ui/filter_some.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#![warn(clippy::filter_some)]

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);
//~^ filter_some
}

fn main() {
let _ = false.then_some(0);
//~^ filter_some
// The condition contains an operator. The program should add parentheses.
let _ = (1 == 0).then_some(0);
//~^ filter_some
let _ = match 0 {
//~^ filter_some
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));
//~^ filter_some
let _ = vec![false].is_empty().then_some(0);
//~^ filter_some
// 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);
//~^ filter_some
}
51 changes: 51 additions & 0 deletions tests/ui/filter_some.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#![warn(clippy::filter_some)]

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);
//~^ filter_some
}

fn main() {
let _ = Some(0).filter(|_| false);
//~^ filter_some
// The condition contains an operator. The program should add parentheses.
let _ = Some(0).filter(|_| 1 == 0);
//~^ filter_some
let _ = Some(0).filter(|_| match 0 {
//~^ filter_some
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));
//~^ filter_some
let _ = Some(0).filter(|_| vec![false].is_empty());
//~^ filter_some
// 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));
//~^ filter_some
}
75 changes: 75 additions & 0 deletions tests/ui/filter_some.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
error: use of `Some(_).filter(_)`
--> tests/ui/filter_some.rs:22:13
|
LL | let _ = Some(0).filter(|_| false);
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `then_some` instead: `false.then_some(0)`
|
= note: this change will alter the order in which the condition and the value are evaluated
= note: `-D clippy::filter-some` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::filter_some)]`

error: use of `Some(_).filter(_)`
--> tests/ui/filter_some.rs:27:13
|
LL | let _ = Some(0).filter(|_| false);
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `then_some` instead: `false.then_some(0)`
|
= note: this change will alter the order in which the condition and the value are evaluated

error: use of `Some(_).filter(_)`
--> tests/ui/filter_some.rs:30:13
|
LL | let _ = Some(0).filter(|_| 1 == 0);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `then_some` instead: `(1 == 0).then_some(0)`
|
= note: this change will alter the order in which the condition and the value are evaluated

error: use of `Some(_).filter(_)`
--> tests/ui/filter_some.rs:32: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 `then_some` instead
|
LL ~ let _ = match 0 {
LL +
LL + 0 => false,
LL + 1 => true,
LL + _ => true,
LL ~ }.then_some(0);
|

error: use of `Some(_).filter(_)`
--> tests/ui/filter_some.rs:43:24
|
LL | let _ = unchanged!(Some(0).filter(|_| false));
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `then_some` instead: `false.then_some(0)`
|
= note: this change will alter the order in which the condition and the value are evaluated

error: use of `Some(_).filter(_)`
--> tests/ui/filter_some.rs:45:13
|
LL | let _ = Some(0).filter(|_| vec![false].is_empty());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `then_some` instead: `vec![false].is_empty().then_some(0)`
|
= note: this change will alter the order in which the condition and the value are evaluated

error: use of `Some(_).filter(_)`
--> tests/ui/filter_some.rs:49:13
|
LL | let _ = Some(0).filter(|_| condition!(false));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `then_some` instead: `condition!(false).then_some(0)`
|
= note: this change will alter the order in which the condition and the value are evaluated

error: aborting due to 7 previous errors

2 changes: 1 addition & 1 deletion tests/ui/manual_filter.fixed
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#![warn(clippy::manual_filter)]
#![allow(unused_variables, dead_code, clippy::useless_vec)]
#![allow(unused_variables, dead_code, clippy::filter_some, clippy::useless_vec)]

fn main() {
Some(0).filter(|&x| x <= 0);
Expand Down
2 changes: 1 addition & 1 deletion tests/ui/manual_filter.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#![warn(clippy::manual_filter)]
#![allow(unused_variables, dead_code, clippy::useless_vec)]
#![allow(unused_variables, dead_code, clippy::filter_some, clippy::useless_vec)]

fn main() {
match Some(0) {
Expand Down
2 changes: 1 addition & 1 deletion tests/ui/option_filter_map.fixed
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#![warn(clippy::option_filter_map)]
#![allow(clippy::map_flatten, clippy::unnecessary_map_on_constructor)]
#![allow(clippy::filter_some, clippy::map_flatten, clippy::unnecessary_map_on_constructor)]

fn main() {
let _ = Some(Some(1)).flatten();
Expand Down
2 changes: 1 addition & 1 deletion tests/ui/option_filter_map.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#![warn(clippy::option_filter_map)]
#![allow(clippy::map_flatten, clippy::unnecessary_map_on_constructor)]
#![allow(clippy::filter_some, clippy::map_flatten, clippy::unnecessary_map_on_constructor)]

fn main() {
let _ = Some(Some(1)).filter(Option::is_some).map(Option::unwrap);
Expand Down