| use clippy_utils::diagnostics::span_lint_and_sugg; |
| use clippy_utils::msrvs::{self, Msrv}; |
| use clippy_utils::res::MaybeDef; |
| use clippy_utils::source::{snippet, snippet_with_applicability}; |
| use clippy_utils::sugg::Sugg; |
| use clippy_utils::{get_parent_expr, sym}; |
| use rustc_ast::LitKind; |
| use rustc_errors::Applicability; |
| use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res}; |
| use rustc_hir::{BinOpKind, Closure, Expr, ExprKind, QPath}; |
| use rustc_lint::LateContext; |
| use rustc_middle::ty; |
| use rustc_span::{Span, Symbol}; |
| |
| use super::MANUAL_IS_VARIANT_AND; |
| |
| pub(super) fn check( |
| cx: &LateContext<'_>, |
| expr: &Expr<'_>, |
| map_recv: &Expr<'_>, |
| map_arg: &Expr<'_>, |
| map_span: Span, |
| msrv: Msrv, |
| ) { |
| // Don't lint if: |
| |
| // 1. the `expr` is generated by a macro |
| if expr.span.from_expansion() { |
| return; |
| } |
| |
| // 2. the caller of `map()` is neither `Option` nor `Result` |
| let is_option = cx.typeck_results().expr_ty(map_recv).is_diag_item(cx, sym::Option); |
| let is_result = cx.typeck_results().expr_ty(map_recv).is_diag_item(cx, sym::Result); |
| if !is_option && !is_result { |
| return; |
| } |
| |
| // 3. the caller of `unwrap_or_default` is neither `Option<bool>` nor `Result<bool, _>` |
| if !cx.typeck_results().expr_ty(expr).is_bool() { |
| return; |
| } |
| |
| // 4. msrv doesn't meet `OPTION_RESULT_IS_VARIANT_AND` |
| if !msrv.meets(cx, msrvs::OPTION_RESULT_IS_VARIANT_AND) { |
| return; |
| } |
| |
| let lint_msg = if is_option { |
| "called `map(<f>).unwrap_or_default()` on an `Option` value" |
| } else { |
| "called `map(<f>).unwrap_or_default()` on a `Result` value" |
| }; |
| let suggestion = if is_option { "is_some_and" } else { "is_ok_and" }; |
| |
| span_lint_and_sugg( |
| cx, |
| MANUAL_IS_VARIANT_AND, |
| expr.span.with_lo(map_span.lo()), |
| lint_msg, |
| "use", |
| format!("{}({})", suggestion, snippet(cx, map_arg.span, "..")), |
| Applicability::MachineApplicable, |
| ); |
| } |
| |
| #[derive(Clone, Copy, PartialEq)] |
| enum Flavor { |
| Option, |
| Result, |
| } |
| |
| impl Flavor { |
| const fn symbol(self) -> Symbol { |
| match self { |
| Self::Option => sym::Option, |
| Self::Result => sym::Result, |
| } |
| } |
| |
| const fn positive(self) -> Symbol { |
| match self { |
| Self::Option => sym::Some, |
| Self::Result => sym::Ok, |
| } |
| } |
| } |
| |
| #[derive(Clone, Copy, PartialEq)] |
| enum Op { |
| Eq, |
| Ne, |
| } |
| |
| impl std::fmt::Display for Op { |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| match self { |
| Self::Eq => write!(f, "=="), |
| Self::Ne => write!(f, "!="), |
| } |
| } |
| } |
| |
| impl TryFrom<BinOpKind> for Op { |
| type Error = (); |
| fn try_from(op: BinOpKind) -> Result<Self, Self::Error> { |
| match op { |
| BinOpKind::Eq => Ok(Self::Eq), |
| BinOpKind::Ne => Ok(Self::Ne), |
| _ => Err(()), |
| } |
| } |
| } |
| |
| /// Represents the argument of the `.map()` function, as a closure or as a path |
| /// in case η-reduction is used. |
| enum MapFunc<'hir> { |
| Closure(&'hir Closure<'hir>), |
| Path(&'hir Expr<'hir>), |
| } |
| |
| impl<'hir> TryFrom<&'hir Expr<'hir>> for MapFunc<'hir> { |
| type Error = (); |
| |
| fn try_from(expr: &'hir Expr<'hir>) -> Result<Self, Self::Error> { |
| match expr.kind { |
| ExprKind::Closure(closure) => Ok(Self::Closure(closure)), |
| ExprKind::Path(_) => Ok(Self::Path(expr)), |
| _ => Err(()), |
| } |
| } |
| } |
| |
| impl<'hir> MapFunc<'hir> { |
| /// Build a suggestion suitable for use in a `.map()`-like function. η-expansion will be applied |
| /// as needed. |
| fn sugg(self, cx: &LateContext<'hir>, invert: bool, app: &mut Applicability) -> String { |
| match self { |
| Self::Closure(closure) => { |
| let body = Sugg::hir_with_applicability(cx, cx.tcx.hir_body(closure.body).value, "..", app); |
| format!( |
| "{} {}", |
| snippet_with_applicability(cx, closure.fn_decl_span, "|..|", app), |
| if invert { !body } else { body } |
| ) |
| }, |
| Self::Path(expr) => { |
| let path = snippet_with_applicability(cx, expr.span, "_", app); |
| if invert { |
| format!("|x| !{path}(x)") |
| } else { |
| path.to_string() |
| } |
| }, |
| } |
| } |
| } |
| |
| fn emit_lint<'tcx>( |
| cx: &LateContext<'tcx>, |
| span: Span, |
| op: Op, |
| flavor: Flavor, |
| in_some_or_ok: bool, |
| map_func: MapFunc<'tcx>, |
| recv: &Expr<'_>, |
| ) { |
| let mut app = Applicability::MachineApplicable; |
| let recv = snippet_with_applicability(cx, recv.span, "_", &mut app); |
| |
| let (invert_expr, method, invert_body) = match (flavor, op) { |
| (Flavor::Option, Op::Eq) => (false, "is_some_and", !in_some_or_ok), |
| (Flavor::Option, Op::Ne) => (false, "is_none_or", in_some_or_ok), |
| (Flavor::Result, Op::Eq) => (false, "is_ok_and", !in_some_or_ok), |
| (Flavor::Result, Op::Ne) => (true, "is_ok_and", !in_some_or_ok), |
| }; |
| span_lint_and_sugg( |
| cx, |
| MANUAL_IS_VARIANT_AND, |
| span, |
| format!("called `.map() {op} {pos}()`", pos = flavor.positive(),), |
| "use", |
| format!( |
| "{inversion}{recv}.{method}({body})", |
| inversion = if invert_expr { "!" } else { "" }, |
| body = map_func.sugg(cx, invert_body, &mut app), |
| ), |
| app, |
| ); |
| } |
| |
| pub(super) fn check_map(cx: &LateContext<'_>, expr: &Expr<'_>) { |
| if let Some(parent_expr) = get_parent_expr(cx, expr) |
| && let ExprKind::Binary(op, left, right) = parent_expr.kind |
| && op.span.eq_ctxt(expr.span) |
| && let Ok(op) = Op::try_from(op.node) |
| { |
| // Check `left` and `right` expression in any order, and for `Option` and `Result` |
| for (expr1, expr2) in [(left, right), (right, left)] { |
| for flavor in [Flavor::Option, Flavor::Result] { |
| if let ExprKind::Call(call, [arg]) = expr1.kind |
| && let ExprKind::Lit(lit) = arg.kind |
| && let LitKind::Bool(bool_cst) = lit.node |
| && let ExprKind::Path(QPath::Resolved(_, path)) = call.kind |
| && let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Fn), _) = path.res |
| && let ty = cx.typeck_results().expr_ty(expr1) |
| && let ty::Adt(adt, args) = ty.kind() |
| && cx.tcx.is_diagnostic_item(flavor.symbol(), adt.did()) |
| && args.type_at(0).is_bool() |
| && let ExprKind::MethodCall(_, recv, [map_expr], _) = expr2.kind |
| && cx.typeck_results().expr_ty(recv).is_diag_item(cx, flavor.symbol()) |
| && let Ok(map_func) = MapFunc::try_from(map_expr) |
| { |
| return emit_lint(cx, parent_expr.span, op, flavor, bool_cst, map_func, recv); |
| } |
| } |
| } |
| } |
| } |