| 1 | use std::borrow::Cow; |
| 2 | |
| 3 | use clippy_utils::diagnostics::span_lint_and_then; |
| 4 | use clippy_utils::eager_or_lazy::switch_to_eager_eval; |
| 5 | use clippy_utils::msrvs::{self, Msrv}; |
| 6 | use clippy_utils::sugg::{Sugg, make_binop}; |
| 7 | use clippy_utils::ty::{get_type_diagnostic_name, implements_trait, is_copy}; |
| 8 | use clippy_utils::visitors::is_local_used; |
| 9 | use clippy_utils::{get_parent_expr, is_from_proc_macro, path_to_local_id}; |
| 10 | use rustc_ast::LitKind::Bool; |
| 11 | use rustc_errors::Applicability; |
| 12 | use rustc_hir::{BinOpKind, Expr, ExprKind, PatKind}; |
| 13 | use rustc_lint::LateContext; |
| 14 | use rustc_span::{Span, sym}; |
| 15 | |
| 16 | use super::UNNECESSARY_MAP_OR; |
| 17 | |
| 18 | pub(super) enum Variant { |
| 19 | Ok, |
| 20 | Some, |
| 21 | } |
| 22 | impl Variant { |
| 23 | pub fn variant_name(&self) -> &'static str { |
| 24 | match self { |
| 25 | Variant::Ok => "Ok" , |
| 26 | Variant::Some => "Some" , |
| 27 | } |
| 28 | } |
| 29 | |
| 30 | pub fn method_name(&self) -> &'static str { |
| 31 | match self { |
| 32 | Variant::Ok => "is_ok_and" , |
| 33 | Variant::Some => "is_some_and" , |
| 34 | } |
| 35 | } |
| 36 | } |
| 37 | |
| 38 | pub(super) fn check<'a>( |
| 39 | cx: &LateContext<'a>, |
| 40 | expr: &Expr<'a>, |
| 41 | recv: &Expr<'_>, |
| 42 | def: &Expr<'_>, |
| 43 | map: &Expr<'_>, |
| 44 | method_span: Span, |
| 45 | msrv: Msrv, |
| 46 | ) { |
| 47 | let ExprKind::Lit(def_kind) = def.kind else { |
| 48 | return; |
| 49 | }; |
| 50 | |
| 51 | let recv_ty = cx.typeck_results().expr_ty_adjusted(recv); |
| 52 | |
| 53 | let Bool(def_bool) = def_kind.node else { |
| 54 | return; |
| 55 | }; |
| 56 | |
| 57 | let variant = match get_type_diagnostic_name(cx, recv_ty) { |
| 58 | Some(sym::Option) => Variant::Some, |
| 59 | Some(sym::Result) => Variant::Ok, |
| 60 | Some(_) | None => return, |
| 61 | }; |
| 62 | |
| 63 | let ext_def_span = def.span.until(map.span); |
| 64 | |
| 65 | let (sugg, method, applicability) = if let ExprKind::Closure(map_closure) = map.kind |
| 66 | && let closure_body = cx.tcx.hir_body(map_closure.body) |
| 67 | && let closure_body_value = closure_body.value.peel_blocks() |
| 68 | && let ExprKind::Binary(op, l, r) = closure_body_value.kind |
| 69 | && let Some(param) = closure_body.params.first() |
| 70 | && let PatKind::Binding(_, hir_id, _, _) = param.pat.kind |
| 71 | // checking that map_or is one of the following: |
| 72 | // .map_or(false, |x| x == y) |
| 73 | // .map_or(false, |x| y == x) - swapped comparison |
| 74 | // .map_or(true, |x| x != y) |
| 75 | // .map_or(true, |x| y != x) - swapped comparison |
| 76 | && ((BinOpKind::Eq == op.node && !def_bool) || (BinOpKind::Ne == op.node && def_bool)) |
| 77 | && let non_binding_location = if path_to_local_id(l, hir_id) { r } else { l } |
| 78 | && switch_to_eager_eval(cx, non_binding_location) |
| 79 | // xor, because if its both then thats a strange edge case and |
| 80 | // we can just ignore it, since by default clippy will error on this |
| 81 | && (path_to_local_id(l, hir_id) ^ path_to_local_id(r, hir_id)) |
| 82 | && !is_local_used(cx, non_binding_location, hir_id) |
| 83 | && let typeck_results = cx.typeck_results() |
| 84 | && let l_ty = typeck_results.expr_ty(l) |
| 85 | && l_ty == typeck_results.expr_ty(r) |
| 86 | && let Some(partial_eq) = cx.tcx.get_diagnostic_item(sym::PartialEq) |
| 87 | && implements_trait(cx, recv_ty, partial_eq, &[recv_ty.into()]) |
| 88 | && is_copy(cx, l_ty) |
| 89 | { |
| 90 | let wrap = variant.variant_name(); |
| 91 | |
| 92 | // we may need to add parens around the suggestion |
| 93 | // in case the parent expression has additional method calls, |
| 94 | // since for example `Some(5).map_or(false, |x| x == 5).then(|| 1)` |
| 95 | // being converted to `Some(5) == Some(5).then(|| 1)` isnt |
| 96 | // the same thing |
| 97 | |
| 98 | let inner_non_binding = Sugg::NonParen(Cow::Owned(format!( |
| 99 | " {wrap}( {})" , |
| 100 | Sugg::hir(cx, non_binding_location, "" ) |
| 101 | ))); |
| 102 | |
| 103 | let mut app = Applicability::MachineApplicable; |
| 104 | let binop = make_binop( |
| 105 | op.node, |
| 106 | &Sugg::hir_with_applicability(cx, recv, ".." , &mut app), |
| 107 | &inner_non_binding, |
| 108 | ); |
| 109 | |
| 110 | let sugg = if let Some(parent_expr) = get_parent_expr(cx, expr) { |
| 111 | match parent_expr.kind { |
| 112 | ExprKind::Binary(..) | ExprKind::Unary(..) | ExprKind::Cast(..) => binop.maybe_par(), |
| 113 | ExprKind::MethodCall(_, receiver, _, _) if receiver.hir_id == expr.hir_id => binop.maybe_par(), |
| 114 | _ => binop, |
| 115 | } |
| 116 | } else { |
| 117 | binop |
| 118 | } |
| 119 | .into_string(); |
| 120 | |
| 121 | (vec![(expr.span, sugg)], "a standard comparison" , app) |
| 122 | } else if !def_bool && msrv.meets(cx, msrvs::OPTION_RESULT_IS_VARIANT_AND) { |
| 123 | let suggested_name = variant.method_name(); |
| 124 | ( |
| 125 | vec![(method_span, suggested_name.into()), (ext_def_span, String::default())], |
| 126 | suggested_name, |
| 127 | Applicability::MachineApplicable, |
| 128 | ) |
| 129 | } else if def_bool && matches!(variant, Variant::Some) && msrv.meets(cx, msrvs::IS_NONE_OR) { |
| 130 | ( |
| 131 | vec![(method_span, "is_none_or" .into()), (ext_def_span, String::default())], |
| 132 | "is_none_or" , |
| 133 | Applicability::MachineApplicable, |
| 134 | ) |
| 135 | } else { |
| 136 | return; |
| 137 | }; |
| 138 | |
| 139 | if is_from_proc_macro(cx, expr) { |
| 140 | return; |
| 141 | } |
| 142 | |
| 143 | span_lint_and_then( |
| 144 | cx, |
| 145 | UNNECESSARY_MAP_OR, |
| 146 | expr.span, |
| 147 | "this `map_or` can be simplified" , |
| 148 | |diag| { |
| 149 | diag.multipart_suggestion_verbose(format!("use {method} instead" ), sugg, applicability); |
| 150 | }, |
| 151 | ); |
| 152 | } |
| 153 | |