|
| 1 | +use clippy_utils::consts::is_zero_integer_const; |
| 2 | +use clippy_utils::diagnostics::span_lint_and_then; |
| 3 | +use clippy_utils::sugg::Sugg; |
| 4 | +use rustc_errors::Applicability; |
| 5 | +use rustc_hir::{BinOpKind, Expr, ExprKind}; |
| 6 | +use rustc_lint::LateContext; |
| 7 | +use rustc_middle::ty; |
| 8 | + |
| 9 | +use super::UNSIGNED_SUBTRACTION_GT_ZERO; |
| 10 | + |
| 11 | +pub(super) fn check<'tcx>( |
| 12 | + cx: &LateContext<'tcx>, |
| 13 | + expr: &'tcx Expr<'tcx>, |
| 14 | + op: BinOpKind, |
| 15 | + lhs_expr: &'tcx Expr<'tcx>, |
| 16 | + rhs_expr: &'tcx Expr<'tcx>, |
| 17 | +) { |
| 18 | + // Avoid linting macro-generated code to reduce noise |
| 19 | + if expr.span.from_expansion() { |
| 20 | + return; |
| 21 | + } |
| 22 | + |
| 23 | + // Only consider strict relational comparisons where one side is zero and the other is a subtraction |
| 24 | + let sub_expr = match op { |
| 25 | + // x > 0 |
| 26 | + BinOpKind::Gt if is_zero_integer_const(cx, rhs_expr, expr.span.ctxt()) => lhs_expr, |
| 27 | + // 0 < x |
| 28 | + BinOpKind::Lt if is_zero_integer_const(cx, lhs_expr, expr.span.ctxt()) => rhs_expr, |
| 29 | + |
| 30 | + _ => return, |
| 31 | + }; |
| 32 | + |
| 33 | + // Ensure the compared expression is a subtraction |
| 34 | + let (lhs, rhs) = match sub_expr.kind { |
| 35 | + ExprKind::Binary(sub_op, lhs, rhs) if sub_op.node == BinOpKind::Sub => (lhs, rhs), |
| 36 | + _ => return, |
| 37 | + }; |
| 38 | + |
| 39 | + // Subtraction result type must be an unsigned primitive |
| 40 | + if !matches!(cx.typeck_results().expr_ty(sub_expr).peel_refs().kind(), ty::Uint(_)) { |
| 41 | + return; |
| 42 | + } |
| 43 | + |
| 44 | + // Suggest `a > b` preserving user formatting with parentheses as needed |
| 45 | + let mut app = Applicability::MaybeIncorrect; |
| 46 | + let (left_sugg, right_sugg) = ( |
| 47 | + Sugg::hir_with_applicability(cx, lhs, "_", &mut app).maybe_paren(), |
| 48 | + Sugg::hir_with_applicability(cx, rhs, "_", &mut app).maybe_paren(), |
| 49 | + ); |
| 50 | + let replacement = format!("{left_sugg} > {right_sugg}"); |
| 51 | + let neq_suggestion = format!("{left_sugg} != {right_sugg}"); |
| 52 | + |
| 53 | + span_lint_and_then( |
| 54 | + cx, |
| 55 | + UNSIGNED_SUBTRACTION_GT_ZERO, |
| 56 | + expr.span, |
| 57 | + "suspicious comparison of unsigned subtraction to zero", |
| 58 | + |diag| { |
| 59 | + diag.help(format!("`{left_sugg} - {right_sugg} > 0` will panic in debug mode when `{left_sugg} < {right_sugg}` and wrap in release mode; `{left_sugg} > {right_sugg}` is clearer and will never panic")); |
| 60 | + diag.help(format!("if you meant inequality, use `{neq_suggestion}`")); |
| 61 | + diag.span_suggestion(expr.span, "try", replacement, app); |
| 62 | + }, |
| 63 | + ); |
| 64 | +} |
0 commit comments