| 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; | |
| 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 LitKind::Bool(def_bool) = def_kind.node else { | |
| return; | |
| }; | |
| let typeck = cx.typeck_results(); | |
| let recv_ty = typeck.expr_ty_adjusted(recv); | |
| 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): (_, Cow<'_, _>, _) = if typeck.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 [param] = closure_body.params | |
| && 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 it's 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 l_ty = typeck.expr_ty(l) | |
| && l_ty == typeck.expr_ty(r) | |
| && let Some(partial_eq) = cx.tcx.lang_items().eq_trait() | |
| && 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 mut app = Applicability::MachineApplicable; | |
| let inner_non_binding = Sugg::NonParen(Cow::Owned(format!( | |
| "{wrap}({})", | |
| Sugg::hir_with_applicability(cx, non_binding_location, "", &mut app) | |
| ))); | |
| 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".into(), 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::new())], | |
| format!("`{suggested_name}`").into(), | |
| 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::new())], | |
| "`is_none_or`".into(), | |
| 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(format!("use {method} instead"), sugg, applicability); | |
| }, | |
| ); | |
| } |