Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit a644930

Browse files
committed
Auto merge of rust-lang#6360 - mlegner:mod_one, r=llogiq
Trigger modulo_one lint also on -1. Fixes rust-lang#6321. changelog: trigger `modulo_one` lint also on `-1`
2 parents 84ba08f + b822632 commit a644930

File tree

3 files changed

+142
-67
lines changed

3 files changed

+142
-67
lines changed

clippy_lints/src/misc.rs

Lines changed: 83 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use crate::utils::sugg::Sugg;
1818
use crate::utils::{
1919
get_item_name, get_parent_expr, higher, implements_trait, in_constant, is_integer_const, iter_input_pats,
2020
last_path_segment, match_qpath, match_trait_method, paths, snippet, snippet_opt, span_lint, span_lint_and_sugg,
21-
span_lint_and_then, span_lint_hir_and_then, SpanlessEq,
21+
span_lint_and_then, span_lint_hir_and_then, unsext, SpanlessEq,
2222
};
2323

2424
declare_clippy_lint! {
@@ -139,23 +139,26 @@ declare_clippy_lint! {
139139
}
140140

141141
declare_clippy_lint! {
142-
/// **What it does:** Checks for getting the remainder of a division by one.
142+
/// **What it does:** Checks for getting the remainder of a division by one or minus
143+
/// one.
143144
///
144-
/// **Why is this bad?** The result can only ever be zero. No one will write
145-
/// such code deliberately, unless trying to win an Underhanded Rust
146-
/// Contest. Even for that contest, it's probably a bad idea. Use something more
147-
/// underhanded.
145+
/// **Why is this bad?** The result for a divisor of one can only ever be zero; for
146+
/// minus one it can cause panic/overflow (if the left operand is the minimal value of
147+
/// the respective integer type) or results in zero. No one will write such code
148+
/// deliberately, unless trying to win an Underhanded Rust Contest. Even for that
149+
/// contest, it's probably a bad idea. Use something more underhanded.
148150
///
149151
/// **Known problems:** None.
150152
///
151153
/// **Example:**
152154
/// ```rust
153155
/// # let x = 1;
154156
/// let a = x % 1;
157+
/// let a = x % -1;
155158
/// ```
156159
pub MODULO_ONE,
157160
correctness,
158-
"taking a number modulo 1, which always returns 0"
161+
"taking a number modulo +/-1, which can either panic/overflow or always returns 0"
159162
}
160163

161164
declare_clippy_lint! {
@@ -378,60 +381,8 @@ impl<'tcx> LateLintPass<'tcx> for MiscLints {
378381
return;
379382
},
380383
ExprKind::Binary(ref cmp, ref left, ref right) => {
381-
let op = cmp.node;
382-
if op.is_comparison() {
383-
check_nan(cx, left, expr);
384-
check_nan(cx, right, expr);
385-
check_to_owned(cx, left, right, true);
386-
check_to_owned(cx, right, left, false);
387-
}
388-
if (op == BinOpKind::Eq || op == BinOpKind::Ne) && (is_float(cx, left) || is_float(cx, right)) {
389-
if is_allowed(cx, left) || is_allowed(cx, right) {
390-
return;
391-
}
392-
393-
// Allow comparing the results of signum()
394-
if is_signum(cx, left) && is_signum(cx, right) {
395-
return;
396-
}
397-
398-
if let Some(name) = get_item_name(cx, expr) {
399-
let name = name.as_str();
400-
if name == "eq"
401-
|| name == "ne"
402-
|| name == "is_nan"
403-
|| name.starts_with("eq_")
404-
|| name.ends_with("_eq")
405-
{
406-
return;
407-
}
408-
}
409-
let is_comparing_arrays = is_array(cx, left) || is_array(cx, right);
410-
let (lint, msg) = get_lint_and_message(
411-
is_named_constant(cx, left) || is_named_constant(cx, right),
412-
is_comparing_arrays,
413-
);
414-
span_lint_and_then(cx, lint, expr.span, msg, |diag| {
415-
let lhs = Sugg::hir(cx, left, "..");
416-
let rhs = Sugg::hir(cx, right, "..");
417-
418-
if !is_comparing_arrays {
419-
diag.span_suggestion(
420-
expr.span,
421-
"consider comparing them within some margin of error",
422-
format!(
423-
"({}).abs() {} error_margin",
424-
lhs - rhs,
425-
if op == BinOpKind::Eq { '<' } else { '>' }
426-
),
427-
Applicability::HasPlaceholders, // snippet
428-
);
429-
}
430-
diag.note("`f32::EPSILON` and `f64::EPSILON` are available for the `error_margin`");
431-
});
432-
} else if op == BinOpKind::Rem && is_integer_const(cx, right, 1) {
433-
span_lint(cx, MODULO_ONE, expr.span, "any number modulo 1 will be 0");
434-
}
384+
check_binary(cx, expr, cmp, left, right);
385+
return;
435386
},
436387
_ => {},
437388
}
@@ -744,3 +695,74 @@ fn check_cast(cx: &LateContext<'_>, span: Span, e: &Expr<'_>, ty: &hir::Ty<'_>)
744695
}
745696
}
746697
}
698+
699+
fn check_binary(
700+
cx: &LateContext<'a>,
701+
expr: &Expr<'_>,
702+
cmp: &rustc_span::source_map::Spanned<rustc_hir::BinOpKind>,
703+
left: &'a Expr<'_>,
704+
right: &'a Expr<'_>,
705+
) {
706+
let op = cmp.node;
707+
if op.is_comparison() {
708+
check_nan(cx, left, expr);
709+
check_nan(cx, right, expr);
710+
check_to_owned(cx, left, right, true);
711+
check_to_owned(cx, right, left, false);
712+
}
713+
if (op == BinOpKind::Eq || op == BinOpKind::Ne) && (is_float(cx, left) || is_float(cx, right)) {
714+
if is_allowed(cx, left) || is_allowed(cx, right) {
715+
return;
716+
}
717+
718+
// Allow comparing the results of signum()
719+
if is_signum(cx, left) && is_signum(cx, right) {
720+
return;
721+
}
722+
723+
if let Some(name) = get_item_name(cx, expr) {
724+
let name = name.as_str();
725+
if name == "eq" || name == "ne" || name == "is_nan" || name.starts_with("eq_") || name.ends_with("_eq") {
726+
return;
727+
}
728+
}
729+
let is_comparing_arrays = is_array(cx, left) || is_array(cx, right);
730+
let (lint, msg) = get_lint_and_message(
731+
is_named_constant(cx, left) || is_named_constant(cx, right),
732+
is_comparing_arrays,
733+
);
734+
span_lint_and_then(cx, lint, expr.span, msg, |diag| {
735+
let lhs = Sugg::hir(cx, left, "..");
736+
let rhs = Sugg::hir(cx, right, "..");
737+
738+
if !is_comparing_arrays {
739+
diag.span_suggestion(
740+
expr.span,
741+
"consider comparing them within some margin of error",
742+
format!(
743+
"({}).abs() {} error_margin",
744+
lhs - rhs,
745+
if op == BinOpKind::Eq { '<' } else { '>' }
746+
),
747+
Applicability::HasPlaceholders, // snippet
748+
);
749+
}
750+
diag.note("`f32::EPSILON` and `f64::EPSILON` are available for the `error_margin`");
751+
});
752+
} else if op == BinOpKind::Rem {
753+
if is_integer_const(cx, right, 1) {
754+
span_lint(cx, MODULO_ONE, expr.span, "any number modulo 1 will be 0");
755+
}
756+
757+
if let ty::Int(ity) = cx.typeck_results().expr_ty(right).kind() {
758+
if is_integer_const(cx, right, unsext(cx.tcx, -1, *ity)) {
759+
span_lint(
760+
cx,
761+
MODULO_ONE,
762+
expr.span,
763+
"any number modulo -1 will panic/overflow or result in 0",
764+
);
765+
}
766+
};
767+
}
768+
}

tests/ui/modulo_one.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,22 @@
22
#![allow(clippy::no_effect, clippy::unnecessary_operation)]
33

44
static STATIC_ONE: usize = 2 - 1;
5+
static STATIC_NEG_ONE: i64 = 1 - 2;
56

67
fn main() {
78
10 % 1;
9+
10 % -1;
810
10 % 2;
11+
i32::MIN % (-1); // also caught by rustc
912

1013
const ONE: u32 = 1 * 1;
14+
const NEG_ONE: i64 = 1 - 2;
15+
const INT_MIN: i64 = i64::MIN;
1116

1217
2 % ONE;
13-
5 % STATIC_ONE;
18+
5 % STATIC_ONE; // NOT caught by lint
19+
2 % NEG_ONE;
20+
5 % STATIC_NEG_ONE; // NOT caught by lint
21+
INT_MIN % NEG_ONE; // also caught by rustc
22+
INT_MIN % STATIC_NEG_ONE; // ONLY caught by rustc
1423
}

tests/ui/modulo_one.stderr

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,74 @@
1+
error: this arithmetic operation will overflow
2+
--> $DIR/modulo_one.rs:11:5
3+
|
4+
LL | i32::MIN % (-1); // also caught by rustc
5+
| ^^^^^^^^^^^^^^^ attempt to compute the remainder of `i32::MIN % -1_i32`, which would overflow
6+
|
7+
= note: `#[deny(arithmetic_overflow)]` on by default
8+
9+
error: this arithmetic operation will overflow
10+
--> $DIR/modulo_one.rs:21:5
11+
|
12+
LL | INT_MIN % NEG_ONE; // also caught by rustc
13+
| ^^^^^^^^^^^^^^^^^ attempt to compute the remainder of `i64::MIN % -1_i64`, which would overflow
14+
15+
error: this arithmetic operation will overflow
16+
--> $DIR/modulo_one.rs:22:5
17+
|
18+
LL | INT_MIN % STATIC_NEG_ONE; // ONLY caught by rustc
19+
| ^^^^^^^^^^^^^^^^^^^^^^^^ attempt to compute the remainder of `i64::MIN % -1_i64`, which would overflow
20+
121
error: any number modulo 1 will be 0
2-
--> $DIR/modulo_one.rs:7:5
22+
--> $DIR/modulo_one.rs:8:5
323
|
424
LL | 10 % 1;
525
| ^^^^^^
626
|
727
= note: `-D clippy::modulo-one` implied by `-D warnings`
828

29+
error: any number modulo -1 will panic/overflow or result in 0
30+
--> $DIR/modulo_one.rs:9:5
31+
|
32+
LL | 10 % -1;
33+
| ^^^^^^^
34+
35+
error: any number modulo -1 will panic/overflow or result in 0
36+
--> $DIR/modulo_one.rs:11:5
37+
|
38+
LL | i32::MIN % (-1); // also caught by rustc
39+
| ^^^^^^^^^^^^^^^
40+
941
error: the operation is ineffective. Consider reducing it to `1`
10-
--> $DIR/modulo_one.rs:10:22
42+
--> $DIR/modulo_one.rs:13:22
1143
|
1244
LL | const ONE: u32 = 1 * 1;
1345
| ^^^^^
1446
|
1547
= note: `-D clippy::identity-op` implied by `-D warnings`
1648

1749
error: the operation is ineffective. Consider reducing it to `1`
18-
--> $DIR/modulo_one.rs:10:22
50+
--> $DIR/modulo_one.rs:13:22
1951
|
2052
LL | const ONE: u32 = 1 * 1;
2153
| ^^^^^
2254

2355
error: any number modulo 1 will be 0
24-
--> $DIR/modulo_one.rs:12:5
56+
--> $DIR/modulo_one.rs:17:5
2557
|
2658
LL | 2 % ONE;
2759
| ^^^^^^^
2860

29-
error: aborting due to 4 previous errors
61+
error: any number modulo -1 will panic/overflow or result in 0
62+
--> $DIR/modulo_one.rs:19:5
63+
|
64+
LL | 2 % NEG_ONE;
65+
| ^^^^^^^^^^^
66+
67+
error: any number modulo -1 will panic/overflow or result in 0
68+
--> $DIR/modulo_one.rs:21:5
69+
|
70+
LL | INT_MIN % NEG_ONE; // also caught by rustc
71+
| ^^^^^^^^^^^^^^^^^
72+
73+
error: aborting due to 11 previous errors
3074

0 commit comments

Comments
 (0)