|  | 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::source::HasSession as _; | 
|  | 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}; | 
|  | 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; | 
|  | use rustc_span::sym; | 
|  |  | 
|  | 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 = { | 
|  | let source_map = cx.sess().source_map(); | 
|  | if span_contains_comment(source_map, if_expr.then.span) | 
|  | || span_contains_comment(source_map, 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()) | 
|  | } |