|
| 1 | +use clippy_config::msrvs::{self, Msrv}; |
| 2 | +use clippy_utils::diagnostics::span_lint_and_sugg; |
| 3 | +use clippy_utils::eager_or_lazy::switch_to_eager_eval; |
| 4 | +use clippy_utils::path_to_local_id; |
| 5 | +use clippy_utils::source::snippet; |
| 6 | +use clippy_utils::ty::is_type_diagnostic_item; |
| 7 | +use clippy_utils::visitors::is_local_used; |
| 8 | +use rustc_ast::LitKind::Bool; |
| 9 | +use rustc_errors::Applicability; |
| 10 | +use rustc_hir::{BinOpKind, Expr, ExprKind, PatKind}; |
| 11 | +use rustc_lint::LateContext; |
| 12 | +use rustc_span::sym; |
| 13 | + |
| 14 | +use super::UNNECESSARY_MAP_OR; |
| 15 | + |
| 16 | +// Only checking map_or for now |
| 17 | +pub(super) fn check( |
| 18 | + cx: &LateContext<'_>, |
| 19 | + expr: &Expr<'_>, |
| 20 | + recv: &Expr<'_>, |
| 21 | + def: &Expr<'_>, |
| 22 | + map: &Expr<'_>, |
| 23 | + msrv: &Msrv, |
| 24 | +) { |
| 25 | + if let ExprKind::Lit(def_kind) = def.kind |
| 26 | + && let typeck_results = cx.typeck_results() |
| 27 | + && let recv_ty = typeck_results.expr_ty(recv) |
| 28 | + && (is_type_diagnostic_item(cx, recv_ty, sym::Option) || is_type_diagnostic_item(cx, recv_ty, sym::Result)) |
| 29 | + && let Bool(def_bool) = def_kind.node |
| 30 | + { |
| 31 | + let (sugg, method) = if let ExprKind::Closure(map_closure) = map.kind |
| 32 | + && let closure_body = cx.tcx.hir().body(map_closure.body) |
| 33 | + && let closure_body_value = closure_body.value.peel_blocks() |
| 34 | + && let ExprKind::Binary(op, l, r) = closure_body_value.kind |
| 35 | + && let Some(param) = closure_body.params.first() |
| 36 | + && let PatKind::Binding(_, hir_id, _, _) = param.pat.kind |
| 37 | + // checking that map_or is either: |
| 38 | + // .map_or(false, |x| x == y) OR .map_or(true, |x| x != y) |
| 39 | + && ((BinOpKind::Eq == op.node && !def_bool) || (BinOpKind::Ne == op.node && def_bool)) |
| 40 | + && let non_binding_location = if path_to_local_id(l, hir_id) { r } else { l } |
| 41 | + && switch_to_eager_eval(cx, non_binding_location) |
| 42 | + // xor, because if its both then thats a strange edge case and |
| 43 | + // we can just ignore it, since by default clippy will error on this |
| 44 | + && (path_to_local_id(l, hir_id) ^ path_to_local_id(r, hir_id)) |
| 45 | + && !is_local_used(cx, non_binding_location, hir_id) |
| 46 | + && typeck_results.expr_ty(l) == typeck_results.expr_ty(r) |
| 47 | + { |
| 48 | + let wrap = if is_type_diagnostic_item(cx, recv_ty, sym::Option) { |
| 49 | + "Some" |
| 50 | + } else { |
| 51 | + "Ok" |
| 52 | + }; |
| 53 | + let comparator = if BinOpKind::Eq == op.node { "==" } else { "!=" }; |
| 54 | + |
| 55 | + ( |
| 56 | + format!( |
| 57 | + "{} {} {}({})", |
| 58 | + snippet(cx, recv.span, ".."), |
| 59 | + comparator, |
| 60 | + wrap, |
| 61 | + snippet(cx, non_binding_location.span.source_callsite(), "..") |
| 62 | + ), |
| 63 | + "standard comparison", |
| 64 | + ) |
| 65 | + } else if def_bool == false && msrv.meets(msrvs::OPTION_IS_SOME_AND) { |
| 66 | + let sugg = if is_type_diagnostic_item(cx, recv_ty, sym::Option) { |
| 67 | + "is_some_and" |
| 68 | + } else { |
| 69 | + "is_ok_and" |
| 70 | + }; |
| 71 | + |
| 72 | + ( |
| 73 | + format!( |
| 74 | + "{}.{}({})", |
| 75 | + snippet(cx, recv.span.source_callsite(), ".."), |
| 76 | + sugg, |
| 77 | + snippet(cx, map.span.source_callsite(), "..") |
| 78 | + ), |
| 79 | + sugg, |
| 80 | + ) |
| 81 | + } else { |
| 82 | + return; |
| 83 | + }; |
| 84 | + |
| 85 | + span_lint_and_sugg( |
| 86 | + cx, |
| 87 | + UNNECESSARY_MAP_OR, |
| 88 | + expr.span, |
| 89 | + &format!("`map_or` is redundant, use {} instead", method), |
| 90 | + "try", |
| 91 | + sugg, |
| 92 | + Applicability::Unspecified, |
| 93 | + ) |
| 94 | + } |
| 95 | +} |
0 commit comments