Skip to content

Commit ea83c3c

Browse files
committed
Add manual_div_ceil
1 parent 3ed690f commit ea83c3c

12 files changed

+352
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5517,6 +5517,7 @@ Released 2018-09-13
55175517
[`manual_bits`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_bits
55185518
[`manual_c_str_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_c_str_literals
55195519
[`manual_clamp`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_clamp
5520+
[`manual_div_ceil`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_div_ceil
55205521
[`manual_filter`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_filter
55215522
[`manual_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_filter_map
55225523
[`manual_find`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_find

clippy_config/src/conf.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -862,7 +862,7 @@ fn calculate_dimensions(fields: &[&str]) -> (usize, Vec<usize>) {
862862
cmp::max(1, terminal_width / (SEPARATOR_WIDTH + max_field_width))
863863
});
864864

865-
let rows = (fields.len() + (columns - 1)) / columns;
865+
let rows = fields.len().div_ceil(columns);
866866

867867
let column_widths = (0..columns)
868868
.map(|column| {

clippy_config/src/msrvs.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ msrv_aliases! {
2020
1,81,0 { LINT_REASONS_STABILIZATION }
2121
1,77,0 { C_STR_LITERALS }
2222
1,76,0 { PTR_FROM_REF, OPTION_RESULT_INSPECT }
23+
1,73,0 { MANUAL_DIV_CEIL }
2324
1,71,0 { TUPLE_ARRAY_CONVERSIONS, BUILD_HASHER_HASH_ONE }
2425
1,70,0 { OPTION_RESULT_IS_VARIANT_AND, BINARY_HEAP_RETAIN }
2526
1,68,0 { PATH_MAIN_SEPARATOR_STR }

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
305305
crate::manual_async_fn::MANUAL_ASYNC_FN_INFO,
306306
crate::manual_bits::MANUAL_BITS_INFO,
307307
crate::manual_clamp::MANUAL_CLAMP_INFO,
308+
crate::manual_div_ceil::MANUAL_DIV_CEIL_INFO,
308309
crate::manual_float_methods::MANUAL_IS_FINITE_INFO,
309310
crate::manual_float_methods::MANUAL_IS_INFINITE_INFO,
310311
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
@@ -204,6 +204,7 @@ mod manual_assert;
204204
mod manual_async_fn;
205205
mod manual_bits;
206206
mod manual_clamp;
207+
mod manual_div_ceil;
207208
mod manual_float_methods;
208209
mod manual_hash_one;
209210
mod manual_is_ascii_check;
@@ -1177,6 +1178,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
11771178
store.register_late_pass(|_| Box::new(set_contains_or_insert::HashsetInsertAfterContains));
11781179
store.register_early_pass(|| Box::new(byte_char_slices::ByteCharSlice));
11791180
store.register_early_pass(|| Box::new(cfg_not_test::CfgNotTest));
1181+
store.register_late_pass(move |_| Box::new(manual_div_ceil::ManualDivCeil::new(msrv())));
11801182
// add lints here, do not remove this comment, it's used in `new_lint`
11811183
}
11821184

clippy_lints/src/manual_div_ceil.rs

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

tests/ui/manual_div_ceil.fixed

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#![warn(clippy::manual_div_ceil)]
2+
3+
fn main() {
4+
let x = 7_u32;
5+
let y = 4_u32;
6+
let z = 11_u32;
7+
8+
// Lint
9+
let _ = x.div_ceil(y); //~ ERROR: manually reimplementing `div_ceil`
10+
let _ = x.div_ceil(y); //~ ERROR: manually reimplementing `div_ceil`
11+
let _ = x.div_ceil(y); //~ ERROR: manually reimplementing `div_ceil`
12+
13+
let _ = 7_u32.div_ceil(4); //~ ERROR: manually reimplementing `div_ceil`
14+
let _ = (7_i32 as u32).div_ceil(4); //~ ERROR: manually reimplementing `div_ceil`
15+
16+
// No lint
17+
let _ = (x + (y - 2)) / y;
18+
let _ = (x + (y + 1)) / y;
19+
20+
let _ = (x + (y - 1)) / z;
21+
22+
let x_i = 7_i32;
23+
let y_i = 4_i32;
24+
let z_i = 11_i32;
25+
26+
// No lint because `int_roundings` feature is not enabled.
27+
let _ = (z as i32 + (y_i - 1)) / y_i;
28+
let _ = (7_u32 as i32 + (y_i - 1)) / y_i;
29+
let _ = (7_u32 as i32 + (4 - 1)) / 4;
30+
}

tests/ui/manual_div_ceil.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#![warn(clippy::manual_div_ceil)]
2+
3+
fn main() {
4+
let x = 7_u32;
5+
let y = 4_u32;
6+
let z = 11_u32;
7+
8+
// Lint
9+
let _ = (x + (y - 1)) / y; //~ ERROR: manually reimplementing `div_ceil`
10+
let _ = ((y - 1) + x) / y; //~ ERROR: manually reimplementing `div_ceil`
11+
let _ = (x + y - 1) / y; //~ ERROR: manually reimplementing `div_ceil`
12+
13+
let _ = (7_u32 + (4 - 1)) / 4; //~ ERROR: manually reimplementing `div_ceil`
14+
let _ = (7_i32 as u32 + (4 - 1)) / 4; //~ ERROR: manually reimplementing `div_ceil`
15+
16+
// No lint
17+
let _ = (x + (y - 2)) / y;
18+
let _ = (x + (y + 1)) / y;
19+
20+
let _ = (x + (y - 1)) / z;
21+
22+
let x_i = 7_i32;
23+
let y_i = 4_i32;
24+
let z_i = 11_i32;
25+
26+
// No lint because `int_roundings` feature is not enabled.
27+
let _ = (z as i32 + (y_i - 1)) / y_i;
28+
let _ = (7_u32 as i32 + (y_i - 1)) / y_i;
29+
let _ = (7_u32 as i32 + (4 - 1)) / 4;
30+
}

tests/ui/manual_div_ceil.stderr

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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: manually reimplementing `div_ceil`
23+
--> tests/ui/manual_div_ceil.rs:13:13
24+
|
25+
LL | let _ = (7_u32 + (4 - 1)) / 4;
26+
| ^^^^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `7_u32.div_ceil(4)`
27+
28+
error: manually reimplementing `div_ceil`
29+
--> tests/ui/manual_div_ceil.rs:14:13
30+
|
31+
LL | let _ = (7_i32 as u32 + (4 - 1)) / 4;
32+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `(7_i32 as u32).div_ceil(4)`
33+
34+
error: aborting due to 5 previous errors
35+
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#![warn(clippy::manual_div_ceil)]
2+
#![feature(int_roundings)]
3+
4+
fn main() {
5+
let x = 7_i32;
6+
let y = 4_i32;
7+
let z = 3_i32;
8+
let z_u: u32 = 11;
9+
10+
// Lint.
11+
let _ = x.div_ceil(y);
12+
let _ = x.div_ceil(y);
13+
let _ = x.div_ceil(y);
14+
15+
let _ = 7_i32.div_ceil(4);
16+
let _ = (7_i32 as u32).div_ceil(4);
17+
let _ = (7_u32 as i32).div_ceil(4);
18+
let _ = z_u.div_ceil(4);
19+
20+
// No lint.
21+
let _ = (x + (y - 2)) / y;
22+
let _ = (x + (y + 1)) / y;
23+
24+
let _ = (x + (y - 1)) / z;
25+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#![warn(clippy::manual_div_ceil)]
2+
#![feature(int_roundings)]
3+
4+
fn main() {
5+
let x = 7_i32;
6+
let y = 4_i32;
7+
let z = 3_i32;
8+
let z_u: u32 = 11;
9+
10+
// Lint.
11+
let _ = (x + (y - 1)) / y;
12+
let _ = ((y - 1) + x) / y;
13+
let _ = (x + y - 1) / y;
14+
15+
let _ = (7_i32 + (4 - 1)) / 4;
16+
let _ = (7_i32 as u32 + (4 - 1)) / 4;
17+
let _ = (7_u32 as i32 + (4 - 1)) / 4;
18+
let _ = (z_u + (4 - 1)) / 4;
19+
20+
// No lint.
21+
let _ = (x + (y - 2)) / y;
22+
let _ = (x + (y + 1)) / y;
23+
24+
let _ = (x + (y - 1)) / z;
25+
}

0 commit comments

Comments
 (0)