blob: 0c01be4b18756ed689e810a6ec520e4f5e843c67 [file] [log] [blame] [edit]
use std::borrow::Cow;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::eager_or_lazy::switch_to_eager_eval;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::res::{MaybeDef, MaybeResPath};
use clippy_utils::sugg::{Sugg, make_binop};
use clippy_utils::ty::{implements_trait, is_copy};
use clippy_utils::visitors::is_local_used;
use clippy_utils::{get_parent_expr, is_from_proc_macro};
use rustc_ast::LitKind::Bool;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, PatKind};
use rustc_lint::LateContext;
use rustc_span::{Span, sym};
use super::UNNECESSARY_MAP_OR;
pub(super) enum Variant {
Ok,
Some,
}
impl Variant {
pub fn variant_name(&self) -> &'static str {
match self {
Variant::Ok => "Ok",
Variant::Some => "Some",
}
}
pub fn method_name(&self) -> &'static str {
match self {
Variant::Ok => "is_ok_and",
Variant::Some => "is_some_and",
}
}
}
pub(super) fn check<'a>(
cx: &LateContext<'a>,
expr: &Expr<'a>,
recv: &Expr<'_>,
def: &Expr<'_>,
map: &Expr<'_>,
method_span: Span,
msrv: Msrv,
) {
let ExprKind::Lit(def_kind) = def.kind else {
return;
};
let recv_ty = cx.typeck_results().expr_ty_adjusted(recv);
let Bool(def_bool) = def_kind.node else {
return;
};
let variant = match recv_ty.opt_diag_name(cx) {
Some(sym::Option) => Variant::Some,
Some(sym::Result) => Variant::Ok,
Some(_) | None => return,
};
let ext_def_span = def.span.until(map.span);
let (sugg, method, applicability) = if cx.typeck_results().expr_adjustments(recv).is_empty()
&& let ExprKind::Closure(map_closure) = map.kind
&& let closure_body = cx.tcx.hir_body(map_closure.body)
&& let closure_body_value = closure_body.value.peel_blocks()
&& let ExprKind::Binary(op, l, r) = closure_body_value.kind
&& let Some(param) = closure_body.params.first()
&& let PatKind::Binding(_, hir_id, _, _) = param.pat.kind
// checking that map_or is one of the following:
// .map_or(false, |x| x == y)
// .map_or(false, |x| y == x) - swapped comparison
// .map_or(true, |x| x != y)
// .map_or(true, |x| y != x) - swapped comparison
&& ((BinOpKind::Eq == op.node && !def_bool) || (BinOpKind::Ne == op.node && def_bool))
&& let non_binding_location = if l.res_local_id() == Some(hir_id) { r } else { l }
&& switch_to_eager_eval(cx, non_binding_location)
// if its both then that's a strange edge case and
// we can just ignore it, since by default clippy will error on this
&& (l.res_local_id() == Some(hir_id)) != (r.res_local_id() == Some(hir_id))
&& !is_local_used(cx, non_binding_location, hir_id)
&& let typeck_results = cx.typeck_results()
&& let l_ty = typeck_results.expr_ty(l)
&& l_ty == typeck_results.expr_ty(r)
&& let Some(partial_eq) = cx.tcx.get_diagnostic_item(sym::PartialEq)
&& implements_trait(cx, recv_ty, partial_eq, &[recv_ty.into()])
&& is_copy(cx, l_ty)
{
let wrap = variant.variant_name();
// we may need to add parens around the suggestion
// in case the parent expression has additional method calls,
// since for example `Some(5).map_or(false, |x| x == 5).then(|| 1)`
// being converted to `Some(5) == Some(5).then(|| 1)` isn't
// the same thing
let inner_non_binding = Sugg::NonParen(Cow::Owned(format!(
"{wrap}({})",
Sugg::hir(cx, non_binding_location, "")
)));
let mut app = Applicability::MachineApplicable;
let binop = make_binop(
op.node,
&Sugg::hir_with_applicability(cx, recv, "..", &mut app),
&inner_non_binding,
);
let sugg = if let Some(parent_expr) = get_parent_expr(cx, expr) {
if parent_expr.span.eq_ctxt(expr.span) {
match parent_expr.kind {
ExprKind::Binary(..) | ExprKind::Unary(..) | ExprKind::Cast(..) => binop.maybe_paren(),
ExprKind::MethodCall(_, receiver, _, _) if receiver.hir_id == expr.hir_id => binop.maybe_paren(),
_ => binop,
}
} else {
// if our parent expr is created by a macro, then it should be the one taking care of
// parenthesising us if necessary
binop
}
} else {
binop
}
.into_string();
(vec![(expr.span, sugg)], "a standard comparison", app)
} else if !def_bool && msrv.meets(cx, msrvs::OPTION_RESULT_IS_VARIANT_AND) {
let suggested_name = variant.method_name();
(
vec![(method_span, suggested_name.into()), (ext_def_span, String::default())],
suggested_name,
Applicability::MachineApplicable,
)
} else if def_bool && matches!(variant, Variant::Some) && msrv.meets(cx, msrvs::IS_NONE_OR) {
(
vec![(method_span, "is_none_or".into()), (ext_def_span, String::default())],
"is_none_or",
Applicability::MachineApplicable,
)
} else {
return;
};
if is_from_proc_macro(cx, expr) {
return;
}
span_lint_and_then(
cx,
UNNECESSARY_MAP_OR,
expr.span,
"this `map_or` can be simplified",
|diag| {
diag.multipart_suggestion_verbose(format!("use {method} instead"), sugg, applicability);
},
);
}