Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -6135,6 +6135,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 @@ -375,6 +375,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::source::{indent_of, reindent_multiline, snippet};
use clippy_utils::{is_res_lang_ctor, pat_is_wild, path_res};
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>,
) {
let value = match recv.kind {
ExprKind::Call(f, [value]) if is_res_lang_ctor(cx, path_res(cx, f), 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)
{
Some(condition)
} else {
None
};
span_lint_and_then(cx, FILTER_SOME, expr.span, "`filter` for `Some`", |diag| {
let help = "consider using `then_some` instead";
if let Some(condition) = condition {
let parentheses = cx.precedence(condition) < ExprPrecedence::Unambiguous;
diag.span_suggestion(
expr.span,
help,
reindent_multiline(
&format!(
"{}{}{}.then_some({})",
if parentheses { "(" } else { "" },
snippet(cx, condition.span, ".."),
if parentheses { ")" } else { "" },
snippet(cx, value.span, "..")
),
true,
indent_of(cx, expr.span),
),
Applicability::MaybeIncorrect,
);
diag.note(
"this change will alter the order in which the condition and \
the value are evaluated",
);
} else {
diag.help(help);
}
});
}
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 @@ -4639,6 +4640,29 @@ declare_clippy_lint! {
"detects redundant calls to `Iterator::cloned`"
}

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.91.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 @@ -4820,6 +4844,7 @@ impl_lint_pass!(Methods => [
SWAP_WITH_TEMPORARY,
IP_CONSTANT,
REDUNDANT_ITER_CLONED,
FILTER_SOME,
]);

/// Extracts a method call name, args, and `Span` of the method name.
Expand Down Expand Up @@ -5191,6 +5216,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);
},
(sym::find, [arg]) => {
if let Some((sym::cloned, recv2, [], _span2, _)) = method_call(recv) {
Expand Down
8 changes: 8 additions & 0 deletions tests/ui/filter_some.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#![warn(clippy::filter_some)]

fn main() {
let _ = false.then_some(0);
//~^ filter_some
let _ = (1 == 0).then_some(0);
//~^ filter_some
}
8 changes: 8 additions & 0 deletions tests/ui/filter_some.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#![warn(clippy::filter_some)]

fn main() {
let _ = Some(0).filter(|_| false);
//~^ filter_some
let _ = Some(0).filter(|_| 1 == 0);
//~^ filter_some
}
20 changes: 20 additions & 0 deletions tests/ui/filter_some.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
error: `filter` for `Some`
--> tests/ui/filter_some.rs:4: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: `filter` for `Some`
--> tests/ui/filter_some.rs:6: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: aborting due to 2 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
Loading