| 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); |
| }, |
| ); |
| } |