| use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; |
| use clippy_utils::msrvs::{self, Msrv}; |
| use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; |
| use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; |
| use clippy_utils::sugg::Sugg; |
| use clippy_utils::{SpanlessEq, get_parent_expr, sym}; |
| use rustc_ast::LitKind; |
| use rustc_errors::Applicability; |
| use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res}; |
| use rustc_hir::def_id::DefId; |
| 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; |
| |
| #[derive(Clone, Copy, PartialEq)] |
| enum Flavor { |
| Option, |
| Result, |
| } |
| |
| impl Flavor { |
| fn new(cx: &LateContext<'_>, def_id: DefId) -> Option<Self> { |
| match cx.tcx.get_diagnostic_name(def_id)? { |
| sym::Option => Some(Self::Option), |
| sym::Result => Some(Self::Result), |
| _ => None, |
| } |
| } |
| |
| const fn diag_sym(self) -> Symbol { |
| match self { |
| Self::Option => sym::Option, |
| Self::Result => sym::Result, |
| } |
| } |
| |
| const fn positive_variant_name(self) -> Symbol { |
| match self { |
| Self::Option => sym::Some, |
| Self::Result => sym::Ok, |
| } |
| } |
| } |
| |
| pub(super) fn check_map_unwrap_or_default( |
| 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 Some(flavor) = (cx.typeck_results()) |
| .expr_ty(map_recv) |
| .opt_def_id() |
| .and_then(|did| Flavor::new(cx, did)) |
| else { |
| 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_span = expr.span.with_lo(map_span.lo()); |
| let lint_msg = match flavor { |
| Flavor::Option => "called `map(<f>).unwrap_or_default()` on an `Option` value", |
| Flavor::Result => "called `map(<f>).unwrap_or_default()` on a `Result` value", |
| }; |
| |
| span_lint_and_then(cx, MANUAL_IS_VARIANT_AND, lint_span, lint_msg, |diag| { |
| let method = match flavor { |
| Flavor::Option => "is_some_and", |
| Flavor::Result => "is_ok_and", |
| }; |
| |
| let mut app = Applicability::MachineApplicable; |
| let map_arg_snippet = snippet_with_applicability(cx, map_arg.span, "_", &mut app); |
| |
| diag.span_suggestion(lint_span, "use", format!("{method}({map_arg_snippet})"), app); |
| }); |
| } |
| |
| #[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_variant_name()), |
| "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 |
| for (expr1, expr2) in [(left, right), (right, left)] { |
| 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 ExprKind::MethodCall(_, recv, [map_expr], _) = expr2.kind |
| && let ty = cx.typeck_results().expr_ty(expr1) |
| && let ty::Adt(adt, args) = ty.kind() |
| && let Some(flavor) = Flavor::new(cx, adt.did()) |
| && args.type_at(0).is_bool() |
| && cx.typeck_results().expr_ty(recv).is_diag_item(cx, flavor.diag_sym()) |
| && let Ok(map_func) = MapFunc::try_from(map_expr) |
| { |
| emit_lint(cx, parent_expr.span, op, flavor, bool_cst, map_func, recv); |
| return; |
| } |
| } |
| } |
| } |
| |
| pub(super) fn check_or<'tcx>( |
| cx: &LateContext<'tcx>, |
| expr: &'tcx Expr<'tcx>, |
| lhs: &'tcx Expr<'tcx>, |
| rhs: &'tcx Expr<'tcx>, |
| msrv: Msrv, |
| ) { |
| let (some_recv, some_arg) = if let ( |
| ExprKind::MethodCall(none_path, none_recv, [], _), |
| ExprKind::MethodCall(some_path, some_recv, [some_arg], _), |
| ) |
| | ( |
| ExprKind::MethodCall(some_path, some_recv, [some_arg], _), |
| ExprKind::MethodCall(none_path, none_recv, [], _), |
| ) = (lhs.kind, rhs.kind) |
| && none_path.ident.name == sym::is_none |
| && some_path.ident.name == sym::is_some_and |
| && cx |
| .typeck_results() |
| .expr_ty_adjusted(none_recv) |
| .peel_refs() |
| .is_diag_item(cx, sym::Option) |
| && cx |
| .typeck_results() |
| .expr_ty_adjusted(some_recv) |
| .peel_refs() |
| .is_diag_item(cx, sym::Option) |
| && SpanlessEq::new(cx).eq_expr(expr.span.ctxt(), none_recv, some_recv) |
| { |
| (some_recv, some_arg) |
| } else { |
| return; |
| }; |
| |
| if !msrv.meets(cx, msrvs::IS_NONE_OR) { |
| return; |
| } |
| |
| let Ok(map_func) = MapFunc::try_from(some_arg) else { |
| return; |
| }; |
| |
| span_lint_and_then( |
| cx, |
| MANUAL_IS_VARIANT_AND, |
| expr.span, |
| "manual implementation of `Option::is_none_or`", |
| |diag| { |
| let mut app = Applicability::MachineApplicable; |
| let (recv_snip, _) = snippet_with_context(cx, some_recv.span, expr.span.ctxt(), "_", &mut app); |
| let map_func_snip = map_func.sugg(cx, false, &mut app); |
| |
| diag.span_suggestion( |
| expr.span, |
| "use", |
| format!("{recv_snip}.is_none_or({map_func_snip})"), |
| app, |
| ); |
| }, |
| ); |
| } |
| |
| pub(super) fn check_is_some_is_none<'tcx>( |
| cx: &LateContext<'tcx>, |
| call_span: Span, |
| recv: &'tcx Expr<'tcx>, |
| arg: &'tcx Expr<'tcx>, |
| is_some: bool, |
| msrv: Msrv, |
| ) { |
| if cx |
| .typeck_results() |
| .expr_ty_adjusted(recv) |
| .peel_refs() |
| .is_diag_item(cx, sym::Option) |
| && (is_some || msrv.meets(cx, msrvs::IS_NONE_OR)) |
| && let Ok(map_func) = MapFunc::try_from(arg) |
| { |
| let method = if is_some { "is_some_and" } else { "is_none_or" }; |
| let lint_span = recv.span.to(call_span); |
| span_lint_and_then( |
| cx, |
| MANUAL_IS_VARIANT_AND, |
| lint_span, |
| format!("manual implementation of `Option::{method}`"), |
| |diag| { |
| let mut app = Applicability::MachineApplicable; |
| let (recv_snip, _) = snippet_with_context(cx, recv.span, lint_span.ctxt(), "_", &mut app); |
| let map_func_snip = map_func.sugg(cx, !is_some, &mut app); |
| |
| // We need to use `as_ref()` because `filter` takes a reference |
| diag.span_suggestion( |
| lint_span, |
| "use", |
| format!("{recv_snip}.as_ref().{method}({map_func_snip})"), |
| app, |
| ); |
| }, |
| ); |
| } |
| } |
| |
| /// Lint `result.ok().is_some_and(f)`, which is `result.is_ok_and(f)`. |
| pub(super) fn check_ok_is_some_and<'tcx>( |
| cx: &LateContext<'tcx>, |
| expr: &'tcx Expr<'tcx>, |
| is_some_and_recv: &'tcx Expr<'tcx>, |
| arg: &'tcx Expr<'tcx>, |
| ) { |
| // `is_some_and` and `is_ok_and` were both stabilized in the same release, so if the receiver |
| // already calls `is_some_and` then `is_ok_and` is necessarily available too: no MSRV check. |
| if !expr.span.from_expansion() |
| && let ExprKind::MethodCall(_, result_recv, [], _) = is_some_and_recv.kind |
| && cx |
| .ty_based_def(is_some_and_recv) |
| .is_diag_item(cx, sym::result_ok_method) |
| { |
| let mut app = Applicability::MachineApplicable; |
| let recv_snip = snippet_with_context(cx, result_recv.span, expr.span.ctxt(), "_", &mut app).0; |
| let arg_snip = snippet_with_context(cx, arg.span, expr.span.ctxt(), "_", &mut app).0; |
| span_lint_and_sugg( |
| cx, |
| MANUAL_IS_VARIANT_AND, |
| expr.span, |
| "called `.ok().is_some_and(..)` on a `Result` value", |
| "use `is_ok_and` instead", |
| format!("{recv_snip}.is_ok_and({arg_snip})"), |
| app, |
| ); |
| } |
| } |