Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6759,6 +6759,7 @@ Released 2018-09-13
[`unsafe_removed_from_name`]: https://rust-lang.github.io/rust-clippy/master/index.html#unsafe_removed_from_name
[`unsafe_vector_initialization`]: https://rust-lang.github.io/rust-clippy/master/index.html#unsafe_vector_initialization
[`unseparated_literal_suffix`]: https://rust-lang.github.io/rust-clippy/master/index.html#unseparated_literal_suffix
[`unsigned_subtraction_gt_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#unsigned_subtraction_gt_zero
[`unsound_collection_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#unsound_collection_transmute
[`unstable_as_mut_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#unstable_as_mut_slice
[`unstable_as_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#unstable_as_slice
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/declared_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
crate::operators::OP_REF_INFO,
crate::operators::REDUNDANT_COMPARISONS_INFO,
crate::operators::SELF_ASSIGNMENT_INFO,
crate::operators::UNSIGNED_SUBTRACTION_GT_ZERO_INFO,
crate::operators::VERBOSE_BIT_MASK_INFO,
crate::option_env_unwrap::OPTION_ENV_UNWRAP_INFO,
crate::option_if_let_else::OPTION_IF_LET_ELSE_INFO,
Expand Down
31 changes: 31 additions & 0 deletions clippy_lints/src/operators/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ mod needless_bitwise_bool;
mod numeric_arithmetic;
mod op_ref;
mod self_assignment;
mod unsigned_subtraction_gt_zero;
mod verbose_bit_mask;

pub(crate) mod arithmetic_side_effects;
Expand Down Expand Up @@ -62,6 +63,34 @@ declare_clippy_lint! {
"a comparison with a maximum or minimum value that is always true or false"
}

declare_clippy_lint! {
/// ### What it does
/// Lints comparisons of an unsigned integer subtraction against zero, like `(a - b) > 0`.
///
/// ### Why is this bad?
/// If `a < b`, `a - b` will panic in debug builds and wrap in release builds, making the
/// comparison effectively behave like `a != b`. This is likely unintended; `a > b` is clearer
/// and avoids potential panics and wraparound.
///
/// ### Example
/// ```no_run
/// let (a, b): (u32, u32) = (1, 2);
/// if a - b > 0 {}
/// ```
///
/// Use instead:
/// ```no_run
/// let (a, b): (u32, u32) = (1, 2);
/// if a > b {}
/// // Or, if you meant inequality:
/// if a != b {}
/// ```
#[clippy::version = "1.92.0"]
pub UNSIGNED_SUBTRACTION_GT_ZERO,
correctness,
"suspicious comparison of unsigned subtraction to zero"
}

declare_clippy_lint! {
/// ### What it does
/// Checks any kind of arithmetic operation of any type.
Expand Down Expand Up @@ -906,6 +935,7 @@ impl_lint_pass!(Operators => [
SELF_ASSIGNMENT,
MANUAL_MIDPOINT,
MANUAL_IS_MULTIPLE_OF,
UNSIGNED_SUBTRACTION_GT_ZERO,
]);

impl<'tcx> LateLintPass<'tcx> for Operators {
Expand All @@ -932,6 +962,7 @@ impl<'tcx> LateLintPass<'tcx> for Operators {
const_comparisons::check(cx, op, lhs, rhs, e.span);
duration_subsec::check(cx, e, op.node, lhs, rhs);
float_equality_without_abs::check(cx, e, op.node, lhs, rhs);
unsigned_subtraction_gt_zero::check(cx, e, op.node, lhs, rhs);
integer_division::check(cx, e, op.node, lhs, rhs);
cmp_owned::check(cx, op.node, lhs, rhs);
float_cmp::check(cx, e, op.node, lhs, rhs);
Expand Down
61 changes: 61 additions & 0 deletions clippy_lints/src/operators/unsigned_subtraction_gt_zero.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use clippy_utils::consts::is_zero_integer_const;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::sugg::Sugg;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::LateContext;
use rustc_middle::ty;

use super::UNSIGNED_SUBTRACTION_GT_ZERO;

pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'tcx>,
op: BinOpKind,
lhs: &'tcx Expr<'tcx>,
rhs: &'tcx Expr<'tcx>,
) {
// Avoid linting macro-generated code to reduce noise
if expr.span.from_expansion() {
return;
}

// Only consider strict relational comparisons where one side is zero and the other is a subtraction
let sub_expr = match op {
BinOpKind::Gt if is_zero_integer_const(cx, rhs, expr.span.ctxt()) => lhs,
BinOpKind::Lt if is_zero_integer_const(cx, lhs, expr.span.ctxt()) => rhs,
_ => return,
};

// Ensure the compared expression is a subtraction
let (a, b) = match sub_expr.kind {
ExprKind::Binary(sub_op, a, b) if sub_op.node == BinOpKind::Sub => (a, b),
_ => return,
};

// Subtraction result type must be an unsigned primitive
if !matches!(cx.typeck_results().expr_ty(sub_expr).peel_refs().kind(), ty::Uint(_)) {
return;
}

// Suggest `a > b` preserving user formatting with parentheses as needed
let mut app = Applicability::MaybeIncorrect;
let (left_sugg, right_sugg) = (
Sugg::hir_with_applicability(cx, a, "_", &mut app).maybe_paren(),
Sugg::hir_with_applicability(cx, b, "_", &mut app).maybe_paren(),
);
let replacement = format!("{left_sugg} > {right_sugg}");
let neq_suggestion = format!("{left_sugg} != {right_sugg}");

span_lint_and_then(
cx,
UNSIGNED_SUBTRACTION_GT_ZERO,
expr.span,
"suspicious comparison of unsigned subtraction to zero",
|diag| {
diag.help("`a - b > 0` will panic in debug when a < b and wrap in release; `a > b` is clearer");
diag.help(format!("if you meant inequality, use {neq_suggestion}"));
diag.span_suggestion(expr.span, "try", replacement, app);
},
);
}
29 changes: 29 additions & 0 deletions tests/ui/unsigned_subtraction_gt_zero.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#![warn(clippy::unsigned_subtraction_gt_zero)]
#![allow(clippy::needless_if)]

fn main() {
let (a, b): (u32, u32) = (1, 2);
if a > b {}
//~^ unsigned_subtraction_gt_zero

if a > b {}
//~^ unsigned_subtraction_gt_zero

let (x, y): (usize, usize) = (10, 3);
if x > y {}
//~^ unsigned_subtraction_gt_zero

if x > y {}
//~^ unsigned_subtraction_gt_zero

// signed: should not lint
let (i, j): (i32, i32) = (1, 2);
if i - j > 0 {}

// float: should not lint
let (f, g): (f32, f32) = (1.0, 2.0);
if f - g > 0.0 {}

// using saturating_sub: should not lint
if a.saturating_sub(b) > 0 {}
}
29 changes: 29 additions & 0 deletions tests/ui/unsigned_subtraction_gt_zero.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#![warn(clippy::unsigned_subtraction_gt_zero)]
#![allow(clippy::needless_if)]

fn main() {
let (a, b): (u32, u32) = (1, 2);
if a - b > 0 {}
//~^ unsigned_subtraction_gt_zero

if 0 < a - b {}
//~^ unsigned_subtraction_gt_zero

let (x, y): (usize, usize) = (10, 3);
if x - y > 0 {}
//~^ unsigned_subtraction_gt_zero

if 0 < x - y {}
//~^ unsigned_subtraction_gt_zero

// signed: should not lint
let (i, j): (i32, i32) = (1, 2);
if i - j > 0 {}

// float: should not lint
let (f, g): (f32, f32) = (1.0, 2.0);
if f - g > 0.0 {}

// using saturating_sub: should not lint
if a.saturating_sub(b) > 0 {}
}
40 changes: 40 additions & 0 deletions tests/ui/unsigned_subtraction_gt_zero.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
error: suspicious comparison of unsigned subtraction to zero
--> tests/ui/unsigned_subtraction_gt_zero.rs:6:8
|
LL | if a - b > 0 {}
| ^^^^^^^^^ help: try: `a > b`
|
= help: `a - b > 0` will panic in debug when a < b and wrap in release; `a > b` is clearer
= help: if you meant inequality, use a != b
= note: `-D clippy::unsigned-subtraction-gt-zero` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::unsigned_subtraction_gt_zero)]`

error: suspicious comparison of unsigned subtraction to zero
--> tests/ui/unsigned_subtraction_gt_zero.rs:9:8
|
LL | if 0 < a - b {}
| ^^^^^^^^^ help: try: `a > b`
|
= help: `a - b > 0` will panic in debug when a < b and wrap in release; `a > b` is clearer
= help: if you meant inequality, use a != b

error: suspicious comparison of unsigned subtraction to zero
--> tests/ui/unsigned_subtraction_gt_zero.rs:13:8
|
LL | if x - y > 0 {}
| ^^^^^^^^^ help: try: `x > y`
|
= help: `a - b > 0` will panic in debug when a < b and wrap in release; `a > b` is clearer
= help: if you meant inequality, use x != y

error: suspicious comparison of unsigned subtraction to zero
--> tests/ui/unsigned_subtraction_gt_zero.rs:16:8
|
LL | if 0 < x - y {}
| ^^^^^^^^^ help: try: `x > y`
|
= help: `a - b > 0` will panic in debug when a < b and wrap in release; `a > b` is clearer
= help: if you meant inequality, use x != y

error: aborting due to 4 previous errors