| use clippy_config::Conf; | |
| use clippy_utils::diagnostics::span_lint_and_then; | |
| use clippy_utils::higher::If; | |
| use clippy_utils::msrvs::{self, Msrv}; | |
| use clippy_utils::res::MaybeDef; | |
| use clippy_utils::sugg::Sugg; | |
| use clippy_utils::ty::peel_and_count_ty_refs; | |
| use clippy_utils::{eq_expr_value, peel_blocks, span_contains_comment, sym}; | |
| use rustc_errors::Applicability; | |
| use rustc_hir::{BinOpKind, Expr, ExprKind}; | |
| use rustc_lint::{LateContext, LateLintPass}; | |
| use rustc_middle::ty::{self, Ty}; | |
| use rustc_session::impl_lint_pass; | |
| declare_clippy_lint! { | |
| /// ### What it does | |
| /// Detects patterns like `if a > b { a - b } else { b - a }` and suggests using `a.abs_diff(b)`. | |
| /// | |
| /// ### Why is this bad? | |
| /// Using `abs_diff` is shorter, more readable, and avoids control flow. | |
| /// | |
| /// ### Examples | |
| /// ```no_run | |
| /// # let (a, b) = (5_usize, 3_usize); | |
| /// if a > b { | |
| /// a - b | |
| /// } else { | |
| /// b - a | |
| /// } | |
| /// # ; | |
| /// ``` | |
| /// Use instead: | |
| /// ```no_run | |
| /// # let (a, b) = (5_usize, 3_usize); | |
| /// a.abs_diff(b) | |
| /// # ; | |
| /// ``` | |
| #[clippy::version = "1.88.0"] | |
| pub MANUAL_ABS_DIFF, | |
| complexity, | |
| "using an if-else pattern instead of `abs_diff`" | |
| } | |
| impl_lint_pass!(ManualAbsDiff => [MANUAL_ABS_DIFF]); | |
| pub struct ManualAbsDiff { | |
| msrv: Msrv, | |
| } | |
| impl ManualAbsDiff { | |
| pub fn new(conf: &'static Conf) -> Self { | |
| Self { msrv: conf.msrv } | |
| } | |
| } | |
| impl<'tcx> LateLintPass<'tcx> for ManualAbsDiff { | |
| fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { | |
| if !expr.span.from_expansion() | |
| && let Some(if_expr) = If::hir(expr) | |
| && let Some(r#else) = if_expr.r#else | |
| && let ExprKind::Binary(op, rhs, lhs) = if_expr.cond.kind | |
| && let (BinOpKind::Gt | BinOpKind::Ge, mut a, mut b) | (BinOpKind::Lt | BinOpKind::Le, mut b, mut a) = | |
| (op.node, rhs, lhs) | |
| && let Some((ty, b_n_refs)) = self.are_ty_eligible(cx, a, b) | |
| && is_sub_expr(cx, if_expr.then, a, b, ty) | |
| && is_sub_expr(cx, r#else, b, a, ty) | |
| { | |
| span_lint_and_then( | |
| cx, | |
| MANUAL_ABS_DIFF, | |
| expr.span, | |
| "manual absolute difference pattern without using `abs_diff`", | |
| |diag| { | |
| if is_unsuffixed_numeral_lit(a) && !is_unsuffixed_numeral_lit(b) { | |
| (a, b) = (b, a); | |
| } | |
| let applicability = { | |
| if span_contains_comment(cx, if_expr.then.span) || span_contains_comment(cx, r#else.span) { | |
| Applicability::MaybeIncorrect | |
| } else { | |
| Applicability::MachineApplicable | |
| } | |
| }; | |
| let sugg = format!( | |
| "{}.abs_diff({}{})", | |
| Sugg::hir(cx, a, "..").maybe_paren(), | |
| "*".repeat(b_n_refs), | |
| Sugg::hir(cx, b, "..") | |
| ); | |
| diag.span_suggestion(expr.span, "replace with `abs_diff`", sugg, applicability); | |
| }, | |
| ); | |
| } | |
| } | |
| } | |
| impl ManualAbsDiff { | |
| /// Returns a type if `a` and `b` are both of it, and this lint can be applied to that | |
| /// type (currently, any primitive int, or a `Duration`) | |
| fn are_ty_eligible<'tcx>(&self, cx: &LateContext<'tcx>, a: &Expr<'_>, b: &Expr<'_>) -> Option<(Ty<'tcx>, usize)> { | |
| let is_int = |ty: Ty<'_>| matches!(ty.kind(), ty::Uint(_) | ty::Int(_)) && self.msrv.meets(cx, msrvs::ABS_DIFF); | |
| let is_duration = | |
| |ty: Ty<'_>| ty.is_diag_item(cx, sym::Duration) && self.msrv.meets(cx, msrvs::DURATION_ABS_DIFF); | |
| let a_ty = cx.typeck_results().expr_ty(a).peel_refs(); | |
| let (b_ty, b_n_refs, _) = peel_and_count_ty_refs(cx.typeck_results().expr_ty(b)); | |
| (a_ty == b_ty && (is_int(a_ty) || is_duration(a_ty))).then_some((a_ty, b_n_refs)) | |
| } | |
| } | |
| /// Checks if the given expression is a subtraction operation between two expected expressions, | |
| /// i.e. if `expr` is `{expected_a} - {expected_b}`. | |
| /// | |
| /// If `expected_ty` is a signed primitive integer, this function will only return `Some` if the | |
| /// subtraction expr is wrapped in a cast to the equivalent unsigned int. | |
| fn is_sub_expr( | |
| cx: &LateContext<'_>, | |
| expr: &Expr<'_>, | |
| expected_a: &Expr<'_>, | |
| expected_b: &Expr<'_>, | |
| expected_ty: Ty<'_>, | |
| ) -> bool { | |
| let expr = peel_blocks(expr).kind; | |
| if let ty::Int(ty) = expected_ty.kind() { | |
| let unsigned = Ty::new_uint(cx.tcx, ty.to_unsigned()); | |
| return if let ExprKind::Cast(expr, cast_ty) = expr | |
| && cx.typeck_results().node_type(cast_ty.hir_id) == unsigned | |
| { | |
| is_sub_expr(cx, expr, expected_a, expected_b, unsigned) | |
| } else { | |
| false | |
| }; | |
| } | |
| if let ExprKind::Binary(op, a, b) = expr | |
| && let BinOpKind::Sub = op.node | |
| && eq_expr_value(cx, a, expected_a) | |
| && eq_expr_value(cx, b, expected_b) | |
| { | |
| true | |
| } else { | |
| false | |
| } | |
| } | |
| fn is_unsuffixed_numeral_lit(expr: &Expr<'_>) -> bool { | |
| matches!(expr.kind, ExprKind::Lit(lit) if lit.node.is_numeric() && lit.node.is_unsuffixed()) | |
| } |