Skip to content

Commit 04c0a4f

Browse files
committed
New lint: manual_is_multiple_of
1 parent 03537df commit 04c0a4f

File tree

9 files changed

+194
-2
lines changed

9 files changed

+194
-2
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -5863,6 +5863,7 @@ Released 2018-09-13
58635863
[`manual_is_ascii_check`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_ascii_check
58645864
[`manual_is_finite`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_finite
58655865
[`manual_is_infinite`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_infinite
5866+
[`manual_is_multiple_of`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_multiple_of
58665867
[`manual_is_power_of_two`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_power_of_two
58675868
[`manual_is_variant_and`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_variant_and
58685869
[`manual_let_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_let_else

clippy_lints/src/casts/cast_sign_loss.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ fn pow_call_result_sign(cx: &LateContext<'_>, base: &Expr<'_>, exponent: &Expr<'
167167

168168
// Rust's integer pow() functions take an unsigned exponent.
169169
let exponent_val = get_const_unsigned_int_eval(cx, exponent, None);
170-
let exponent_is_even = exponent_val.map(|val| val % 2 == 0);
170+
let exponent_is_even = exponent_val.map(|val| val.is_multiple_of(2));
171171

172172
match (base_sign, exponent_is_even) {
173173
// Non-negative bases always return non-negative results, ignoring overflow.

clippy_lints/src/declared_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
583583
crate::operators::IMPOSSIBLE_COMPARISONS_INFO,
584584
crate::operators::INEFFECTIVE_BIT_MASK_INFO,
585585
crate::operators::INTEGER_DIVISION_INFO,
586+
crate::operators::MANUAL_IS_MULTIPLE_OF_INFO,
586587
crate::operators::MANUAL_MIDPOINT_INFO,
587588
crate::operators::MISREFACTORED_ASSIGN_OP_INFO,
588589
crate::operators::MODULO_ARITHMETIC_INFO,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
use clippy_utils::consts::is_zero_integer_const;
2+
use clippy_utils::diagnostics::span_lint_and_sugg;
3+
use clippy_utils::msrvs::{self, Msrv};
4+
use clippy_utils::sugg::Sugg;
5+
use rustc_ast::BinOpKind;
6+
use rustc_errors::Applicability;
7+
use rustc_hir::{Expr, ExprKind};
8+
use rustc_lint::LateContext;
9+
use rustc_middle::ty;
10+
11+
use super::MANUAL_IS_MULTIPLE_OF;
12+
13+
pub(super) fn check<'tcx>(
14+
cx: &LateContext<'tcx>,
15+
expr: &Expr<'_>,
16+
op: BinOpKind,
17+
lhs: &'tcx Expr<'tcx>,
18+
rhs: &'tcx Expr<'tcx>,
19+
msrv: Msrv,
20+
) {
21+
if msrv.meets(cx, msrvs::UNSIGNED_IS_MULTIPLE_OF)
22+
&& let Some(operand) = uint_compare_to_zero(cx, op, lhs, rhs)
23+
&& let ExprKind::Binary(operand_op, operand_left, operand_right) = operand.kind
24+
&& operand_op.node == BinOpKind::Rem
25+
{
26+
let mut app = Applicability::MachineApplicable;
27+
let divisor = Sugg::hir_with_applicability(cx, operand_right, "_", &mut app);
28+
span_lint_and_sugg(
29+
cx,
30+
MANUAL_IS_MULTIPLE_OF,
31+
expr.span,
32+
"manual implementation of `.is_multiple_of()`",
33+
"replace with",
34+
format!(
35+
"{}{}.is_multiple_of({divisor})",
36+
if op == BinOpKind::Eq { "" } else { "!" },
37+
Sugg::hir_with_applicability(cx, operand_left, "_", &mut app).maybe_paren()
38+
),
39+
app,
40+
);
41+
}
42+
}
43+
44+
// If we have a `x == 0`, `x != 0` or `x > 0` (or the reverted ones), return the non-zero operand
45+
fn uint_compare_to_zero<'tcx>(
46+
cx: &LateContext<'tcx>,
47+
op: BinOpKind,
48+
lhs: &'tcx Expr<'tcx>,
49+
rhs: &'tcx Expr<'tcx>,
50+
) -> Option<&'tcx Expr<'tcx>> {
51+
let operand = if matches!(lhs.kind, ExprKind::Binary(..))
52+
&& matches!(op, BinOpKind::Eq | BinOpKind::Ne | BinOpKind::Gt)
53+
&& is_zero_integer_const(cx, rhs)
54+
{
55+
lhs
56+
} else if matches!(rhs.kind, ExprKind::Binary(..))
57+
&& matches!(op, BinOpKind::Eq | BinOpKind::Ne | BinOpKind::Lt)
58+
&& is_zero_integer_const(cx, lhs)
59+
{
60+
rhs
61+
} else {
62+
return None;
63+
};
64+
65+
matches!(cx.typeck_results().expr_ty_adjusted(operand).kind(), ty::Uint(_)).then_some(operand)
66+
}

clippy_lints/src/operators/mod.rs

+33
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ mod float_cmp;
1111
mod float_equality_without_abs;
1212
mod identity_op;
1313
mod integer_division;
14+
mod manual_is_multiple_of;
1415
mod manual_midpoint;
1516
mod misrefactored_assign_op;
1617
mod modulo_arithmetic;
@@ -830,12 +831,42 @@ declare_clippy_lint! {
830831
"manual implementation of `midpoint` which can overflow"
831832
}
832833

834+
declare_clippy_lint! {
835+
/// ### What it does
836+
/// Checks for manual implementation of `.is_multiple_of()` on
837+
/// unsigned integer types.
838+
///
839+
/// ### Why is this bad?
840+
/// `a.is_multiple_of(b)` is a clearer way to check for divisibility
841+
/// of `a` by `b`. This expression can never panic.
842+
///
843+
/// ### Example
844+
/// ```no_run
845+
/// # let (a, b) = (3u64, 4u64);
846+
/// if a % b == 0 {
847+
/// println!("{a} is divisible by {b}");
848+
/// }
849+
/// ```
850+
/// Use instead:
851+
/// ```no_run
852+
/// # let (a, b) = (3u64, 4u64);
853+
/// if a.is_multiple_of(b) {
854+
/// println!("{a} is divisible by {b}");
855+
/// }
856+
/// ```
857+
#[clippy::version = "1.88.0"]
858+
pub MANUAL_IS_MULTIPLE_OF,
859+
complexity,
860+
"manual implementation of `.is_multiple_of()`"
861+
}
862+
833863
pub struct Operators {
834864
arithmetic_context: numeric_arithmetic::Context,
835865
verbose_bit_mask_threshold: u64,
836866
modulo_arithmetic_allow_comparison_to_zero: bool,
837867
msrv: Msrv,
838868
}
869+
839870
impl Operators {
840871
pub fn new(conf: &'static Conf) -> Self {
841872
Self {
@@ -874,6 +905,7 @@ impl_lint_pass!(Operators => [
874905
NEEDLESS_BITWISE_BOOL,
875906
SELF_ASSIGNMENT,
876907
MANUAL_MIDPOINT,
908+
MANUAL_IS_MULTIPLE_OF,
877909
]);
878910

879911
impl<'tcx> LateLintPass<'tcx> for Operators {
@@ -891,6 +923,7 @@ impl<'tcx> LateLintPass<'tcx> for Operators {
891923
identity_op::check(cx, e, op.node, lhs, rhs);
892924
needless_bitwise_bool::check(cx, e, op.node, lhs, rhs);
893925
manual_midpoint::check(cx, e, op.node, lhs, rhs, self.msrv);
926+
manual_is_multiple_of::check(cx, e, op.node, lhs, rhs, self.msrv);
894927
}
895928
self.arithmetic_context.check_binary(cx, e, op.node, lhs, rhs);
896929
bit_mask::check(cx, e, op.node, lhs, rhs);

clippy_utils/src/msrvs.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ macro_rules! msrv_aliases {
2323
// names may refer to stabilized feature flags or library items
2424
msrv_aliases! {
2525
1,88,0 { LET_CHAINS }
26-
1,87,0 { OS_STR_DISPLAY, INT_MIDPOINT }
26+
1,87,0 { OS_STR_DISPLAY, INT_MIDPOINT, UNSIGNED_IS_MULTIPLE_OF }
2727
1,85,0 { UINT_FLOAT_MIDPOINT }
2828
1,84,0 { CONST_OPTION_AS_SLICE, MANUAL_DANGLING_PTR }
2929
1,83,0 { CONST_EXTERN_FN, CONST_FLOAT_BITS_CONV, CONST_FLOAT_CLASSIFY, CONST_MUT_REFS, CONST_UNWRAP }

tests/ui/manual_is_multiple_of.fixed

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//@aux-build: proc_macros.rs
2+
#![warn(clippy::manual_is_multiple_of)]
3+
4+
fn main() {}
5+
6+
#[clippy::msrv = "1.87"]
7+
fn f(a: u64, b: u64) {
8+
let _ = a.is_multiple_of(b); //~ manual_is_multiple_of
9+
let _ = (a + 1).is_multiple_of(b + 1); //~ manual_is_multiple_of
10+
let _ = !a.is_multiple_of(b); //~ manual_is_multiple_of
11+
let _ = !(a + 1).is_multiple_of(b + 1); //~ manual_is_multiple_of
12+
13+
let _ = !a.is_multiple_of(b); //~ manual_is_multiple_of
14+
let _ = !a.is_multiple_of(b); //~ manual_is_multiple_of
15+
16+
proc_macros::external! {
17+
let a: u64 = 23424;
18+
let _ = a % 4096 == 0;
19+
}
20+
}
21+
22+
#[clippy::msrv = "1.86"]
23+
fn g(a: u64, b: u64) {
24+
let _ = a % b == 0;
25+
}

tests/ui/manual_is_multiple_of.rs

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//@aux-build: proc_macros.rs
2+
#![warn(clippy::manual_is_multiple_of)]
3+
4+
fn main() {}
5+
6+
#[clippy::msrv = "1.87"]
7+
fn f(a: u64, b: u64) {
8+
let _ = a % b == 0; //~ manual_is_multiple_of
9+
let _ = (a + 1) % (b + 1) == 0; //~ manual_is_multiple_of
10+
let _ = a % b != 0; //~ manual_is_multiple_of
11+
let _ = (a + 1) % (b + 1) != 0; //~ manual_is_multiple_of
12+
13+
let _ = a % b > 0; //~ manual_is_multiple_of
14+
let _ = 0 < a % b; //~ manual_is_multiple_of
15+
16+
proc_macros::external! {
17+
let a: u64 = 23424;
18+
let _ = a % 4096 == 0;
19+
}
20+
}
21+
22+
#[clippy::msrv = "1.86"]
23+
fn g(a: u64, b: u64) {
24+
let _ = a % b == 0;
25+
}

tests/ui/manual_is_multiple_of.stderr

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
error: manual implementation of `.is_multiple_of()`
2+
--> tests/ui/manual_is_multiple_of.rs:8:13
3+
|
4+
LL | let _ = a % b == 0;
5+
| ^^^^^^^^^^ help: replace with: `a.is_multiple_of(b)`
6+
|
7+
= note: `-D clippy::manual-is-multiple-of` implied by `-D warnings`
8+
= help: to override `-D warnings` add `#[allow(clippy::manual_is_multiple_of)]`
9+
10+
error: manual implementation of `.is_multiple_of()`
11+
--> tests/ui/manual_is_multiple_of.rs:9:13
12+
|
13+
LL | let _ = (a + 1) % (b + 1) == 0;
14+
| ^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `(a + 1).is_multiple_of(b + 1)`
15+
16+
error: manual implementation of `.is_multiple_of()`
17+
--> tests/ui/manual_is_multiple_of.rs:10:13
18+
|
19+
LL | let _ = a % b != 0;
20+
| ^^^^^^^^^^ help: replace with: `!a.is_multiple_of(b)`
21+
22+
error: manual implementation of `.is_multiple_of()`
23+
--> tests/ui/manual_is_multiple_of.rs:11:13
24+
|
25+
LL | let _ = (a + 1) % (b + 1) != 0;
26+
| ^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `!(a + 1).is_multiple_of(b + 1)`
27+
28+
error: manual implementation of `.is_multiple_of()`
29+
--> tests/ui/manual_is_multiple_of.rs:13:13
30+
|
31+
LL | let _ = a % b > 0;
32+
| ^^^^^^^^^ help: replace with: `!a.is_multiple_of(b)`
33+
34+
error: manual implementation of `.is_multiple_of()`
35+
--> tests/ui/manual_is_multiple_of.rs:14:13
36+
|
37+
LL | let _ = 0 < a % b;
38+
| ^^^^^^^^^ help: replace with: `!a.is_multiple_of(b)`
39+
40+
error: aborting due to 6 previous errors
41+

0 commit comments

Comments
 (0)