1use std::borrow::Cow;
2
3use clippy_utils::diagnostics::span_lint_and_then;
4use clippy_utils::eager_or_lazy::switch_to_eager_eval;
5use clippy_utils::msrvs::{self, Msrv};
6use clippy_utils::sugg::{Sugg, make_binop};
7use clippy_utils::ty::{get_type_diagnostic_name, implements_trait, is_copy};
8use clippy_utils::visitors::is_local_used;
9use clippy_utils::{get_parent_expr, is_from_proc_macro, path_to_local_id};
10use rustc_ast::LitKind::Bool;
11use rustc_errors::Applicability;
12use rustc_hir::{BinOpKind, Expr, ExprKind, PatKind};
13use rustc_lint::LateContext;
14use rustc_span::{Span, sym};
15
16use super::UNNECESSARY_MAP_OR;
17
18pub(super) enum Variant {
19 Ok,
20 Some,
21}
22impl 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
38pub(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