Skip to content

Commit 8603c5a

Browse files
committed
Add manual_div_ceil lint
1 parent 9628130 commit 8603c5a

File tree

7 files changed

+201
-0
lines changed

7 files changed

+201
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5515,6 +5515,7 @@ Released 2018-09-13
55155515
[`manual_bits`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_bits
55165516
[`manual_c_str_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_c_str_literals
55175517
[`manual_clamp`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_clamp
5518+
[`manual_div_ceil`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_div_ceil
55185519
[`manual_filter`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_filter
55195520
[`manual_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_filter_map
55205521
[`manual_find`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_find

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
303303
crate::manual_async_fn::MANUAL_ASYNC_FN_INFO,
304304
crate::manual_bits::MANUAL_BITS_INFO,
305305
crate::manual_clamp::MANUAL_CLAMP_INFO,
306+
crate::manual_div_ceil::MANUAL_DIV_CEIL_INFO,
306307
crate::manual_float_methods::MANUAL_IS_FINITE_INFO,
307308
crate::manual_float_methods::MANUAL_IS_INFINITE_INFO,
308309
crate::manual_hash_one::MANUAL_HASH_ONE_INFO,

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ mod manual_assert;
202202
mod manual_async_fn;
203203
mod manual_bits;
204204
mod manual_clamp;
205+
mod manual_div_ceil;
205206
mod manual_float_methods;
206207
mod manual_hash_one;
207208
mod manual_is_ascii_check;
@@ -1171,6 +1172,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
11711172
});
11721173
store.register_late_pass(move |_| Box::new(string_patterns::StringPatterns::new(msrv())));
11731174
store.register_early_pass(|| Box::new(field_scoped_visibility_modifiers::FieldScopedVisibilityModifiers));
1175+
store.register_late_pass(|_| Box::new(manual_div_ceil::ManualDivCeil));
11741176
// add lints here, do not remove this comment, it's used in `new_lint`
11751177
}
11761178

clippy_lints/src/manual_div_ceil.rs

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::source::snippet_with_applicability;
3+
use clippy_utils::SpanlessEq;
4+
use rustc_ast::{BinOpKind, LitKind};
5+
use rustc_data_structures::packed::Pu128;
6+
use rustc_errors::Applicability;
7+
use rustc_hir::{Expr, ExprKind, QPath};
8+
use rustc_lint::{LateContext, LateLintPass};
9+
use rustc_middle::ty::{self};
10+
use rustc_session::declare_lint_pass;
11+
12+
declare_clippy_lint! {
13+
/// ### What it does
14+
/// Checks for an expression like `(x + (y - 1)) / y` which is a common manual reimplementation
15+
/// of `x.div_ceil(y)`.
16+
///
17+
/// ### Why is this bad?
18+
/// It's simpler and more readable.
19+
///
20+
/// ### Example
21+
/// ```no_run
22+
/// let x: i32 = 7;
23+
/// ley y: i32 = 4;
24+
/// let div = (x + (y - 1)) / y;
25+
/// ```
26+
/// Use instead:
27+
/// ```no_run
28+
/// let x: i32 = 7;
29+
/// let y: i32 = 4;
30+
/// let div = x.div_ceil(y);
31+
/// ```
32+
#[clippy::version = "1.81.0"]
33+
pub MANUAL_DIV_CEIL,
34+
complexity,
35+
"manually reimplementing `div_ceil`"
36+
}
37+
38+
declare_lint_pass!(ManualDivCeil => [MANUAL_DIV_CEIL]);
39+
40+
impl LateLintPass<'_> for ManualDivCeil {
41+
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
42+
let mut applicability = Applicability::MachineApplicable;
43+
44+
if let ExprKind::Binary(div_op, div_lhs, div_rhs) = expr.kind
45+
&& check_int_ty(cx, div_lhs)
46+
&& check_int_ty(cx, div_rhs)
47+
&& div_op.node == BinOpKind::Div
48+
{
49+
// (x + (y - 1)) / y
50+
if let ExprKind::Binary(add_op, add_lhs, add_rhs) = div_lhs.kind
51+
&& add_op.node == BinOpKind::Add
52+
&& let ExprKind::Binary(sub_op, sub_lhs, sub_rhs) = add_rhs.kind
53+
&& sub_op.node == BinOpKind::Sub
54+
&& check_literal(sub_rhs)
55+
&& check_eq_path_segment(cx, sub_lhs, div_rhs)
56+
{
57+
build_suggestion(cx, expr, add_lhs, div_rhs, &mut applicability);
58+
return;
59+
}
60+
61+
// ((y - 1) + x) / y
62+
if let ExprKind::Binary(add_op, add_lhs, add_rhs) = div_lhs.kind
63+
&& add_op.node == BinOpKind::Add
64+
&& let ExprKind::Binary(sub_op, sub_lhs, sub_rhs) = add_lhs.kind
65+
&& sub_op.node == BinOpKind::Sub
66+
&& check_literal(sub_rhs)
67+
&& check_eq_path_segment(cx, sub_lhs, div_rhs)
68+
{
69+
build_suggestion(cx, expr, add_rhs, div_rhs, &mut applicability);
70+
return;
71+
}
72+
73+
// (x + y - 1) / y
74+
if let ExprKind::Binary(sub_op, sub_lhs, sub_rhs) = div_lhs.kind
75+
&& sub_op.node == BinOpKind::Sub
76+
&& let ExprKind::Binary(add_op, add_lhs, add_rhs) = sub_lhs.kind
77+
&& add_op.node == BinOpKind::Add
78+
&& check_literal(sub_rhs)
79+
&& check_eq_path_segment(cx, add_rhs, div_rhs)
80+
{
81+
build_suggestion(cx, expr, add_lhs, div_rhs, &mut applicability);
82+
}
83+
}
84+
}
85+
}
86+
87+
fn check_int_ty(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
88+
let expr_ty = cx.typeck_results().expr_ty(expr);
89+
matches!(expr_ty.peel_refs().kind(), ty::Int(_) | ty::Uint(_))
90+
}
91+
92+
fn check_literal(expr: &Expr<'_>) -> bool {
93+
if let ExprKind::Lit(lit) = expr.kind
94+
&& let LitKind::Int(Pu128(1), _) = lit.node
95+
{
96+
return true;
97+
}
98+
false
99+
}
100+
101+
fn check_eq_path_segment(cx: &LateContext<'_>, lhs: &Expr<'_>, rhs: &Expr<'_>) -> bool {
102+
if let ExprKind::Path(QPath::Resolved(_, lhs_path)) = lhs.kind
103+
&& let ExprKind::Path(QPath::Resolved(_, rhs_path)) = rhs.kind
104+
&& SpanlessEq::new(cx).eq_path_segment(&lhs_path.segments[0], &rhs_path.segments[0])
105+
{
106+
return true;
107+
}
108+
false
109+
}
110+
111+
fn build_suggestion(
112+
cx: &LateContext<'_>,
113+
expr: &Expr<'_>,
114+
lhs: &Expr<'_>,
115+
rhs: &Expr<'_>,
116+
applicability: &mut Applicability,
117+
) {
118+
let dividend_snippet = snippet_with_applicability(cx, lhs.span.source_callsite(), "..", applicability);
119+
let divisor_snippet = snippet_with_applicability(cx, rhs.span.source_callsite(), "..", applicability);
120+
121+
let sugg = format!("{dividend_snippet}.div_ceil({divisor_snippet})");
122+
123+
span_lint_and_sugg(
124+
cx,
125+
MANUAL_DIV_CEIL,
126+
expr.span,
127+
"manually reimplementing `div_ceil`",
128+
"consider using `.div_ceil()`",
129+
sugg,
130+
*applicability,
131+
);
132+
}

tests/ui/manual_div_ceil.fixed

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#![warn(clippy::manual_div_ceil)]
2+
#![feature(int_roundings)]
3+
4+
fn main() {
5+
let x: i32 = 7;
6+
let y: i32 = 4;
7+
8+
// Lint.
9+
let _ = x.div_ceil(y);
10+
let _ = x.div_ceil(y);
11+
let _ = x.div_ceil(y);
12+
13+
let _ = (7 + (4 - 1)) / 4;
14+
15+
// No lint.
16+
let _ = (x + (y - 2)) / y;
17+
let _ = (x + (y + 1)) / y;
18+
19+
let z: i32 = 3;
20+
let _ = (x + (y - 1)) / z;
21+
}

tests/ui/manual_div_ceil.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#![warn(clippy::manual_div_ceil)]
2+
#![feature(int_roundings)]
3+
4+
fn main() {
5+
let x: i32 = 7;
6+
let y: i32 = 4;
7+
8+
// Lint.
9+
let _ = (x + (y - 1)) / y;
10+
let _ = ((y - 1) + x) / y;
11+
let _ = (x + y - 1) / y;
12+
13+
let _ = (7 + (4 - 1)) / 4;
14+
15+
// No lint.
16+
let _ = (x + (y - 2)) / y;
17+
let _ = (x + (y + 1)) / y;
18+
19+
let z: i32 = 3;
20+
let _ = (x + (y - 1)) / z;
21+
}

tests/ui/manual_div_ceil.stderr

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
error: manually reimplementing `div_ceil`
2+
--> tests/ui/manual_div_ceil.rs:9:13
3+
|
4+
LL | let _ = (x + (y - 1)) / y;
5+
| ^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `x.div_ceil(y)`
6+
|
7+
= note: `-D clippy::manual-div-ceil` implied by `-D warnings`
8+
= help: to override `-D warnings` add `#[allow(clippy::manual_div_ceil)]`
9+
10+
error: manually reimplementing `div_ceil`
11+
--> tests/ui/manual_div_ceil.rs:10:13
12+
|
13+
LL | let _ = ((y - 1) + x) / y;
14+
| ^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `x.div_ceil(y)`
15+
16+
error: manually reimplementing `div_ceil`
17+
--> tests/ui/manual_div_ceil.rs:11:13
18+
|
19+
LL | let _ = (x + y - 1) / y;
20+
| ^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `x.div_ceil(y)`
21+
22+
error: aborting due to 3 previous errors
23+

0 commit comments

Comments
 (0)