|  | 
| 1 | 1 | use clippy_utils::diagnostics::span_lint_and_sugg; | 
| 2 |  | -use clippy_utils::get_parent_expr; | 
| 3 | 2 | use clippy_utils::msrvs::{self, Msrv}; | 
| 4 |  | -use clippy_utils::source::{snippet, snippet_opt}; | 
|  | 3 | +use clippy_utils::source::{snippet, snippet_with_applicability}; | 
|  | 4 | +use clippy_utils::sugg::Sugg; | 
| 5 | 5 | use clippy_utils::ty::is_type_diagnostic_item; | 
|  | 6 | +use clippy_utils::{get_parent_expr, sym}; | 
|  | 7 | +use rustc_ast::LitKind; | 
| 6 | 8 | use rustc_errors::Applicability; | 
| 7 | 9 | use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res}; | 
| 8 |  | -use rustc_hir::{BinOpKind, Expr, ExprKind, QPath}; | 
|  | 10 | +use rustc_hir::{BinOpKind, Closure, Expr, ExprKind, QPath}; | 
| 9 | 11 | use rustc_lint::LateContext; | 
| 10 | 12 | use rustc_middle::ty; | 
| 11 |  | -use rustc_span::{BytePos, Span, sym}; | 
|  | 13 | +use rustc_span::Span; | 
| 12 | 14 | 
 | 
| 13 | 15 | use super::MANUAL_IS_VARIANT_AND; | 
| 14 | 16 | 
 | 
| @@ -62,54 +64,147 @@ pub(super) fn check( | 
| 62 | 64 |     ); | 
| 63 | 65 | } | 
| 64 | 66 | 
 | 
| 65 |  | -fn emit_lint(cx: &LateContext<'_>, op: BinOpKind, parent: &Expr<'_>, method_span: Span, is_option: bool) { | 
| 66 |  | -    if let Some(before_map_snippet) = snippet_opt(cx, parent.span.with_hi(method_span.lo())) | 
| 67 |  | -        && let Some(after_map_snippet) = snippet_opt(cx, method_span.with_lo(method_span.lo() + BytePos(3))) | 
|  | 67 | +#[derive(Clone, Copy, PartialEq)] | 
|  | 68 | +enum Family { | 
|  | 69 | +    Option, | 
|  | 70 | +    Result, | 
|  | 71 | +} | 
|  | 72 | + | 
|  | 73 | +#[derive(Clone, Copy, PartialEq)] | 
|  | 74 | +enum Op { | 
|  | 75 | +    Eq, | 
|  | 76 | +    Ne, | 
|  | 77 | +} | 
|  | 78 | + | 
|  | 79 | +impl TryFrom<BinOpKind> for Op { | 
|  | 80 | +    type Error = (); | 
|  | 81 | +    fn try_from(op: BinOpKind) -> Result<Self, Self::Error> { | 
|  | 82 | +        match op { | 
|  | 83 | +            BinOpKind::Eq => Ok(Self::Eq), | 
|  | 84 | +            BinOpKind::Ne => Ok(Self::Ne), | 
|  | 85 | +            _ => Err(()), | 
|  | 86 | +        } | 
|  | 87 | +    } | 
|  | 88 | +} | 
|  | 89 | + | 
|  | 90 | +/// Represents the argument of the `.map()` function, as a closure or as a path | 
|  | 91 | +/// in case η-reduction is used. | 
|  | 92 | +enum MapFunc<'hir> { | 
|  | 93 | +    Closure(&'hir Closure<'hir>), | 
|  | 94 | +    Path(&'hir Expr<'hir>), | 
|  | 95 | +} | 
|  | 96 | + | 
|  | 97 | +impl<'hir> TryFrom<&'hir Expr<'hir>> for MapFunc<'hir> { | 
|  | 98 | +    type Error = (); | 
|  | 99 | + | 
|  | 100 | +    fn try_from(expr: &'hir Expr<'hir>) -> Result<Self, Self::Error> { | 
|  | 101 | +        match expr.kind { | 
|  | 102 | +            ExprKind::Closure(closure) => Ok(Self::Closure(closure)), | 
|  | 103 | +            ExprKind::Path(_) => Ok(Self::Path(expr)), | 
|  | 104 | +            _ => Err(()), | 
|  | 105 | +        } | 
|  | 106 | +    } | 
|  | 107 | +} | 
|  | 108 | + | 
|  | 109 | +impl<'hir> MapFunc<'hir> { | 
|  | 110 | +    /// Build a suggestion suitable for use in a `.map()`-like function. η-expansion will be applied | 
|  | 111 | +    /// as needed. | 
|  | 112 | +    fn sugg(self, cx: &LateContext<'hir>, invert: bool, app: &mut Applicability) -> String { | 
|  | 113 | +        match self { | 
|  | 114 | +            Self::Closure(closure) => { | 
|  | 115 | +                let body = Sugg::hir_with_applicability(cx, cx.tcx.hir_body(closure.body).value, "..", app); | 
|  | 116 | +                format!( | 
|  | 117 | +                    "{} {}", | 
|  | 118 | +                    snippet_with_applicability(cx, closure.fn_decl_span, "|..|", app), | 
|  | 119 | +                    if invert { !body } else { body } | 
|  | 120 | +                ) | 
|  | 121 | +            }, | 
|  | 122 | +            Self::Path(expr) => { | 
|  | 123 | +                let path = snippet_with_applicability(cx, expr.span, "_", app); | 
|  | 124 | +                if invert { | 
|  | 125 | +                    format!("|x| !{path}(x)") | 
|  | 126 | +                } else { | 
|  | 127 | +                    path.to_string() | 
|  | 128 | +                } | 
|  | 129 | +            }, | 
|  | 130 | +        } | 
|  | 131 | +    } | 
|  | 132 | +} | 
|  | 133 | + | 
|  | 134 | +fn emit_lint<'tcx>( | 
|  | 135 | +    cx: &LateContext<'tcx>, | 
|  | 136 | +    span: Span, | 
|  | 137 | +    op: Op, | 
|  | 138 | +    family: Family, | 
|  | 139 | +    in_some_or_ok: bool, | 
|  | 140 | +    map_func: MapFunc<'tcx>, | 
|  | 141 | +    recv: &Expr<'_>, | 
|  | 142 | +) { | 
|  | 143 | +    let mut app = Applicability::MachineApplicable; | 
|  | 144 | +    let recv = snippet_with_applicability(cx, recv.span, "_", &mut app); | 
|  | 145 | + | 
|  | 146 | +    let (invert_expr, method, invert_body) = match (family, op, in_some_or_ok) { | 
|  | 147 | +        (Family::Option, Op::Eq, bool_cst) => (false, "is_some_and", !bool_cst), | 
|  | 148 | +        (Family::Option, Op::Ne, bool_cst) => (false, "is_none_or", bool_cst), | 
|  | 149 | +        (Family::Result, Op::Eq, bool_cst) => (false, "is_ok_and", !bool_cst), | 
|  | 150 | +        (Family::Result, Op::Ne, bool_cst) => (true, "is_ok_and", !bool_cst), | 
|  | 151 | +    }; | 
| 68 | 152 |     { | 
| 69 | 153 |         span_lint_and_sugg( | 
| 70 | 154 |             cx, | 
| 71 | 155 |             MANUAL_IS_VARIANT_AND, | 
| 72 |  | -            parent.span, | 
|  | 156 | +            span, | 
| 73 | 157 |             format!( | 
| 74 | 158 |                 "called `.map() {}= {}()`", | 
| 75 |  | -                if op == BinOpKind::Eq { '=' } else { '!' }, | 
| 76 |  | -                if is_option { "Some" } else { "Ok" }, | 
|  | 159 | +                if op == Op::Eq { '=' } else { '!' }, | 
|  | 160 | +                if family == Family::Option { "Some" } else { "Ok" }, | 
| 77 | 161 |             ), | 
| 78 | 162 |             "use", | 
| 79 |  | -            if is_option && op == BinOpKind::Ne { | 
| 80 |  | -                format!("{before_map_snippet}is_none_or{after_map_snippet}",) | 
| 81 |  | -            } else { | 
| 82 |  | -                format!( | 
| 83 |  | -                    "{}{before_map_snippet}{}{after_map_snippet}", | 
| 84 |  | -                    if op == BinOpKind::Eq { "" } else { "!" }, | 
| 85 |  | -                    if is_option { "is_some_and" } else { "is_ok_and" }, | 
| 86 |  | -                ) | 
| 87 |  | -            }, | 
| 88 |  | -            Applicability::MachineApplicable, | 
|  | 163 | +            format!( | 
|  | 164 | +                "{inversion}{recv}.{method}({body})", | 
|  | 165 | +                inversion = if invert_expr { "!" } else { "" }, | 
|  | 166 | +                body = map_func.sugg(cx, invert_body, &mut app), | 
|  | 167 | +            ), | 
|  | 168 | +            app, | 
| 89 | 169 |         ); | 
| 90 |  | -    } | 
|  | 170 | +    }; | 
| 91 | 171 | } | 
| 92 | 172 | 
 | 
| 93 | 173 | pub(super) fn check_map(cx: &LateContext<'_>, expr: &Expr<'_>) { | 
| 94 | 174 |     if let Some(parent_expr) = get_parent_expr(cx, expr) | 
| 95 | 175 |         && let ExprKind::Binary(op, left, right) = parent_expr.kind | 
| 96 |  | -        && matches!(op.node, BinOpKind::Eq | BinOpKind::Ne) | 
| 97 | 176 |         && op.span.eq_ctxt(expr.span) | 
|  | 177 | +        && let Ok(op) = Op::try_from(op.node) | 
| 98 | 178 |     { | 
| 99 | 179 |         // Check `left` and `right` expression in any order, and for `Option` and `Result` | 
| 100 | 180 |         for (expr1, expr2) in [(left, right), (right, left)] { | 
| 101 | 181 |             for item in [sym::Option, sym::Result] { | 
| 102 |  | -                if let ExprKind::Call(call, ..) = expr1.kind | 
|  | 182 | +                if let ExprKind::Call(call, [arg]) = expr1.kind | 
|  | 183 | +                    && let ExprKind::Lit(lit) = arg.kind | 
|  | 184 | +                    && let LitKind::Bool(bool_cst) = lit.node | 
| 103 | 185 |                     && let ExprKind::Path(QPath::Resolved(_, path)) = call.kind | 
| 104 | 186 |                     && let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Fn), _) = path.res | 
| 105 | 187 |                     && let ty = cx.typeck_results().expr_ty(expr1) | 
| 106 | 188 |                     && let ty::Adt(adt, args) = ty.kind() | 
| 107 | 189 |                     && cx.tcx.is_diagnostic_item(item, adt.did()) | 
| 108 | 190 |                     && args.type_at(0).is_bool() | 
| 109 |  | -                    && let ExprKind::MethodCall(_, recv, _, span) = expr2.kind | 
|  | 191 | +                    && let ExprKind::MethodCall(_, recv, [map_expr], _) = expr2.kind | 
| 110 | 192 |                     && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), item) | 
|  | 193 | +                    && let Ok(map_func) = MapFunc::try_from(map_expr) | 
| 111 | 194 |                 { | 
| 112 |  | -                    return emit_lint(cx, op.node, parent_expr, span, item == sym::Option); | 
|  | 195 | +                    return emit_lint( | 
|  | 196 | +                        cx, | 
|  | 197 | +                        parent_expr.span, | 
|  | 198 | +                        op, | 
|  | 199 | +                        if item == sym::Option { | 
|  | 200 | +                            Family::Option | 
|  | 201 | +                        } else { | 
|  | 202 | +                            Family::Result | 
|  | 203 | +                        }, | 
|  | 204 | +                        bool_cst, | 
|  | 205 | +                        map_func, | 
|  | 206 | +                        recv, | 
|  | 207 | +                    ); | 
| 113 | 208 |                 } | 
| 114 | 209 |             } | 
| 115 | 210 |         } | 
|  | 
0 commit comments