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
91 changes: 86 additions & 5 deletions clippy_lints/src/methods/manual_is_variant_and.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::res::MaybeDef;
use clippy_utils::source::{snippet, snippet_with_applicability};
use clippy_utils::sugg::Sugg;
use clippy_utils::sugg::{DerefClosure, Sugg, deref_closure_args};
use clippy_utils::ty::is_copy;
use clippy_utils::{get_parent_expr, sym};
use rustc_ast::LitKind;
use rustc_errors::Applicability;
Expand All @@ -12,9 +13,9 @@ use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_span::{Span, Symbol};

use super::MANUAL_IS_VARIANT_AND;
use super::{MANUAL_IS_VARIANT_AND, method_call};

pub(super) fn check(
pub(super) fn check_map_unwrap_or_default(
cx: &LateContext<'_>,
expr: &Expr<'_>,
map_recv: &Expr<'_>,
Expand Down Expand Up @@ -189,7 +190,10 @@ fn emit_lint<'tcx>(
);
}

pub(super) fn check_map(cx: &LateContext<'_>, expr: &Expr<'_>) {
pub(super) fn check_map(cx: &LateContext<'_>, expr: &Expr<'_>, msrv: Msrv) {
if !msrv.meets(cx, msrvs::OPTION_RESULT_IS_VARIANT_AND) {
return;
}
if let Some(parent_expr) = get_parent_expr(cx, expr)
&& let ExprKind::Binary(op, left, right) = parent_expr.kind
&& op.span.eq_ctxt(expr.span)
Expand Down Expand Up @@ -217,3 +221,80 @@ pub(super) fn check_map(cx: &LateContext<'_>, expr: &Expr<'_>) {
}
}
}

/// Check for things like `.into_iter().all()`.
pub(super) fn check_iter_reduce(
cx: &LateContext<'_>,
expr: &Expr<'_>,
recv: &Expr<'_>,
arg: &Expr<'_>,
second_call: Span,
msrv: Msrv,
method: Symbol,
) {
// Find how the iterator is created.
let (mut referenced, first_recv, first_call) = match method_call(recv) {
Some((sym::into_iter, first_recv, [], _, first_call)) => (false, first_recv, first_call),
Some((sym::iter, first_recv, [], _, first_call)) => (true, first_recv, first_call),
_ => {
return;
},
};

// Find the method to suggest.
let recv_type = cx.typeck_results().expr_ty(first_recv);
let (replacement_method, since_version) = match recv_type.kind() {
ty::Adt(adt, _) if cx.tcx.is_diagnostic_item(sym::Result, adt.did()) && method == sym::any => {
(sym::is_ok_and, msrvs::OPTION_RESULT_IS_VARIANT_AND)
},
ty::Adt(adt, _) if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) => match method {
sym::all => (sym::is_none_or, msrvs::IS_NONE_OR),
sym::any => (sym::is_some_and, msrvs::OPTION_RESULT_IS_VARIANT_AND),
_ => panic!("The method is checked in the caller."),
},
_ => return,
};
if !msrv.meets(cx, since_version) {
return;
}

// The replacement methods pass the value to the closure. The original
// methods may pass the reference. If possible, the program modifies the
// closure to take the value.
let (replacement_closure, applicability) = if referenced
&& is_copy(cx, recv_type)
&& let Some(DerefClosure {
suggestion,
applicability,
}) = deref_closure_args(cx, arg)
{
referenced = false;
(Some((arg.span, suggestion)), applicability)
} else {
(None, Applicability::MachineApplicable)
};
// If it is not possible to modify the closure, the program suggests the
// `as_ref` method.
let replacement_call = if referenced {
format!("as_ref().{replacement_method}")
} else {
replacement_method.to_string()
};

let mut suggestion = vec![
// Remove the call to get the iterator.
(first_call.with_lo(first_recv.span.hi()), String::new()),
// Replace the call to reduce it.
(second_call, replacement_call),
];
suggestion.extend(replacement_closure);
span_lint_and_then(
cx,
MANUAL_IS_VARIANT_AND,
expr.span,
format!("use of `option::Iter::{method}`"),
|diag| {
diag.multipart_suggestion(format!("use `{replacement_method}` instead"), suggestion, applicability);
},
);
}
14 changes: 12 additions & 2 deletions clippy_lints/src/methods/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3915,6 +3915,10 @@ declare_clippy_lint! {
/// result.map(|a| a > 10) == Ok(true);
/// option.map(|a| a > 10) != Some(true);
/// result.map(|a| a > 10) != Ok(true);
///
/// option.into_iter().any(|a| a > 10);
/// result.into_iter().any(|a| a > 10);
/// option.into_iter().all(|a| a > 10);
/// ```
/// Use instead:
/// ```no_run
Expand All @@ -3927,6 +3931,10 @@ declare_clippy_lint! {
/// result.is_ok_and(|a| a > 10);
/// option.is_none_or(|a| a > 10);
/// !result.is_ok_and(|a| a > 10);
///
/// option.is_some_and(|a| a > 10);
/// result.is_ok_and(|a| a > 10);
/// option.is_none_or(|a| a > 10);
/// ```
#[clippy::version = "1.77.0"]
pub MANUAL_IS_VARIANT_AND,
Expand Down Expand Up @@ -5025,6 +5033,7 @@ impl Methods {
zst_offset::check(cx, expr, recv);
},
(sym::all, [arg]) => {
manual_is_variant_and::check_iter_reduce(cx, expr, recv, arg, span, self.msrv, name);
needless_character_iteration::check(cx, expr, recv, arg, true);
match method_call(recv) {
Some((sym::cloned, recv2, [], _, _)) => {
Expand Down Expand Up @@ -5054,6 +5063,7 @@ impl Methods {
}
},
(sym::any, [arg]) => {
manual_is_variant_and::check_iter_reduce(cx, expr, recv, arg, span, self.msrv, name);
needless_character_iteration::check(cx, expr, recv, arg, false);
match method_call(recv) {
Some((sym::cloned, recv2, [], _, _)) => iter_overeager_cloned::check(
Expand Down Expand Up @@ -5330,7 +5340,7 @@ impl Methods {
if name == sym::map {
map_clone::check(cx, expr, recv, m_arg, self.msrv);
map_with_unused_argument_over_ranges::check(cx, expr, recv, m_arg, self.msrv, span);
manual_is_variant_and::check_map(cx, expr);
manual_is_variant_and::check_map(cx, expr, self.msrv);
match method_call(recv) {
Some((map_name @ (sym::iter | sym::into_iter), recv2, _, _, _)) => {
iter_kv_map::check(cx, map_name, expr, recv2, m_arg, self.msrv);
Expand Down Expand Up @@ -5596,7 +5606,7 @@ impl Methods {
(sym::unwrap_or_default, []) => {
match method_call(recv) {
Some((sym::map, m_recv, [arg], span, _)) => {
manual_is_variant_and::check(cx, expr, m_recv, arg, span, self.msrv);
manual_is_variant_and::check_map_unwrap_or_default(cx, expr, m_recv, arg, span, self.msrv);
},
Some((then_method @ (sym::then | sym::then_some), t_recv, [t_arg], _, _)) => {
obfuscated_if_else::check(
Expand Down
26 changes: 7 additions & 19 deletions clippy_lints/src/methods/search_is_some.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::res::{MaybeDef, MaybeTypeckRes};
use clippy_utils::source::{snippet, snippet_with_applicability};
use clippy_utils::sugg::deref_closure_args;
use clippy_utils::{is_receiver_of_method_call, strip_pat_refs, sym};
use hir::ExprKind;
use clippy_utils::{is_receiver_of_method_call, sym};
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::PatKind;
use rustc_lint::LateContext;
use rustc_span::{Span, Symbol};

Expand Down Expand Up @@ -34,25 +32,15 @@ pub(super) fn check<'tcx>(
{
let msg = format!("called `{option_check_method}()` after searching an `Iterator` with `{search_method}`");
let search_snippet = snippet(cx, search_arg.span, "..");
// `find()` provides a reference to the item, but `any` does not, so we
// should fix item usages for suggestion
// suggest `any(|x| ..)` instead of `any(|&x| ..)` for `find(|&x| ..).is_some()`
// suggest `any(|..| *..)` instead of `any(|..| **..)` for `find(|..| **..).is_some()`
let mut applicability = Applicability::MachineApplicable;
let any_search_snippet = if search_method == sym::find
&& let ExprKind::Closure(&hir::Closure { body, .. }) = search_arg.kind
&& let closure_body = cx.tcx.hir_body(body)
&& let Some(closure_arg) = closure_body.params.first()
{
if let PatKind::Ref(..) = closure_arg.pat.kind {
Some(search_snippet.replacen('&', "", 1))
} else if let PatKind::Binding(..) = strip_pat_refs(closure_arg.pat).kind {
// `find()` provides a reference to the item, but `any` does not,
// so we should fix item usages for suggestion
if let Some(closure_sugg) = deref_closure_args(cx, search_arg) {
applicability = closure_sugg.applicability;
Some(closure_sugg.suggestion)
} else {
Some(search_snippet.to_string())
}
let any_search_snippet = if search_method == sym::find {
if let Some(closure_sugg) = deref_closure_args(cx, search_arg) {
applicability = closure_sugg.applicability;
Some(closure_sugg.suggestion)
} else {
None
}
Expand Down
30 changes: 21 additions & 9 deletions clippy_utils/src/sugg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@

use crate::source::{snippet, snippet_opt, snippet_with_applicability, snippet_with_context};
use crate::ty::expr_sig;
use crate::{get_parent_expr_for_hir, higher};
use crate::{get_parent_expr_for_hir, higher, strip_pat_refs};
use rustc_ast::util::parser::AssocOp;
use rustc_ast::{UnOp, ast};
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
use rustc_hir::{self as hir, Closure, ExprKind, HirId, MutTy, Node, TyKind};
use rustc_hir::{self as hir, Closure, ExprKind, HirId, MutTy, Node, PatKind, TyKind};
use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
use rustc_lint::{EarlyContext, LateContext, LintContext};
use rustc_middle::hir::place::ProjectionKind;
Expand Down Expand Up @@ -774,8 +774,22 @@ pub fn deref_closure_args(cx: &LateContext<'_>, closure: &hir::Expr<'_>) -> Opti
if let ExprKind::Closure(&Closure {
fn_decl, def_id, body, ..
}) = closure.kind
&& let closure_body = cx.tcx.hir_body(body)
&& let [closure_arg] = closure_body.params
{
let closure_body = cx.tcx.hir_body(body);
// suggest `any(|x| ..)` instead of `any(|&x| ..)` for `find(|&x| ..).is_some()`
if matches!(closure_arg.pat.kind, PatKind::Ref(..)) {
let mut applicability = Applicability::MachineApplicable;
let suggestion =
snippet_with_applicability(cx, closure.span, "..", &mut applicability).replacen('&', "", 1);
return Some(DerefClosure {
applicability,
suggestion,
});
}
if !matches!(strip_pat_refs(closure_arg.pat).kind, PatKind::Binding(..)) {
return None;
}
// is closure arg a type annotated double reference (i.e.: `|x: &&i32| ...`)
// a type annotation is present if param `kind` is different from `TyKind::Infer`
let closure_arg_is_type_annotated_double_ref = if let TyKind::Ref(_, MutTy { ty, .. }) = fn_decl.inputs[0].kind
Expand All @@ -800,12 +814,10 @@ pub fn deref_closure_args(cx: &LateContext<'_>, closure: &hir::Expr<'_>) -> Opti
.consume_body(closure_body)
.into_ok();

if !visitor.suggestion_start.is_empty() {
return Some(DerefClosure {
applicability: visitor.applicability,
suggestion: visitor.finish(),
});
}
return Some(DerefClosure {
applicability: visitor.applicability,
suggestion: visitor.finish(),
});
}
None
}
Expand Down
1 change: 1 addition & 0 deletions clippy_utils/src/sym.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ generate! {
is_none,
is_none_or,
is_ok,
is_ok_and,
is_partitioned,
is_some,
is_some_and,
Expand Down
Loading