Skip to content

Commit ffaeef3

Browse files
immersummchodzikiewicz
andcommitted
Add new lints: const_sized_chunks_exact, _mut, and const_sized_windows
Suggests using (nightly) `array_chunks`, `_mut`, and `array_windows` changelog: new lint: [`const_sized_chunks_exact`] changelog: new lint: [`const_sized_chunks_exact_mut`] changelog: new lint: [`const_sized_windows`] Co-authored-by: Michał Chodzikiewicz <[email protected]>
1 parent 62fd159 commit ffaeef3

14 files changed

+1227
-0
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5696,6 +5696,9 @@ Released 2018-09-13
56965696
[`comparison_to_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_to_empty
56975697
[`confusing_method_to_numeric_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#confusing_method_to_numeric_cast
56985698
[`const_is_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#const_is_empty
5699+
[`const_sized_chunks_exact`]: https://rust-lang.github.io/rust-clippy/master/index.html#const_sized_chunks_exact
5700+
[`const_sized_chunks_exact_mut`]: https://rust-lang.github.io/rust-clippy/master/index.html#const_sized_chunks_exact_mut
5701+
[`const_sized_windows`]: https://rust-lang.github.io/rust-clippy/master/index.html#const_sized_windows
56995702
[`const_static_lifetime`]: https://rust-lang.github.io/rust-clippy/master/index.html#const_static_lifetime
57005703
[`copy_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#copy_iterator
57015704
[`crate_in_macro_def`]: https://rust-lang.github.io/rust-clippy/master/index.html#crate_in_macro_def

clippy_lints/src/declared_lints.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,9 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
266266
crate::literal_representation::UNUSUAL_BYTE_GROUPINGS_INFO,
267267
crate::literal_string_with_formatting_args::LITERAL_STRING_WITH_FORMATTING_ARGS_INFO,
268268
crate::loops::CHAR_INDICES_AS_BYTE_INDICES_INFO,
269+
crate::loops::CONST_SIZED_CHUNKS_EXACT_INFO,
270+
crate::loops::CONST_SIZED_CHUNKS_EXACT_MUT_INFO,
271+
crate::loops::CONST_SIZED_WINDOWS_INFO,
269272
crate::loops::EMPTY_LOOP_INFO,
270273
crate::loops::EXPLICIT_COUNTER_LOOP_INFO,
271274
crate::loops::EXPLICIT_INTO_ITER_LOOP_INFO,
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
use clippy_utils::consts::{ConstEvalCtxt, Constant};
2+
use clippy_utils::diagnostics::span_lint_and_then;
3+
use clippy_utils::expr_or_init;
4+
use clippy_utils::source::snippet;
5+
use clippy_utils::ty::is_slice_like;
6+
use itertools::Itertools;
7+
use rustc_errors::Applicability;
8+
use rustc_hir::{Expr, ExprKind, Pat, PatKind};
9+
use rustc_lint::LateContext;
10+
11+
use super::{CONST_SIZED_CHUNKS_EXACT, CONST_SIZED_CHUNKS_EXACT_MUT, CONST_SIZED_WINDOWS};
12+
13+
/// Checks for the `/CONST_SIZED(_CHUNKS_EXACT(_MUT)?|_WINDOWS)/` lint.
14+
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, arg: &'tcx Expr<'_>) {
15+
if !arg.span.from_expansion()
16+
// The `for` loop pattern should be a binding.
17+
&& let PatKind::Binding(..) = pat.kind
18+
// The `for` loop argument expression must be a method call.
19+
&& let ExprKind::MethodCall(method, self_arg, [arg], span) = arg.kind
20+
// The receiver of the method call must be slice-like.
21+
&& is_slice_like(cx, cx.typeck_results().expr_ty(self_arg).peel_refs())
22+
// The parameter to the method call must be a constant.
23+
&& let Some(Constant::Int(n)) = ConstEvalCtxt::new(cx).eval(expr_or_init(cx, arg))
24+
// The number of elements should be limited.
25+
&& let Ok(n) = n.try_into() && n >= 1 && n <= 1 + 'z' as usize - 'a' as usize
26+
{
27+
let method = method.ident.name;
28+
let new_method;
29+
30+
let lint = match method {
31+
clippy_utils::sym::chunks_exact => {
32+
new_method = clippy_utils::sym::array_chunks;
33+
CONST_SIZED_CHUNKS_EXACT
34+
},
35+
clippy_utils::sym::chunks_exact_mut => {
36+
new_method = clippy_utils::sym::array_chunks_mut;
37+
CONST_SIZED_CHUNKS_EXACT_MUT
38+
},
39+
rustc_span::sym::windows => {
40+
new_method = clippy_utils::sym::array_windows;
41+
CONST_SIZED_WINDOWS
42+
},
43+
_ => return,
44+
};
45+
46+
let bindings = ('a'..).take(n).join(", ");
47+
let self_arg = snippet(cx, self_arg.span, "..");
48+
let arg = snippet(cx, arg.span, "_");
49+
let msg = format!("iterating over `{method}()` with constant parameter `{arg}`");
50+
51+
span_lint_and_then(cx, lint, span, msg, |diag| {
52+
diag.span_suggestion_verbose(
53+
span.with_lo(pat.span.lo()),
54+
format!("use `{new_method}::<{n}>()` instead"),
55+
format!("[{bindings}] in {self_arg}.{new_method}()"),
56+
Applicability::Unspecified,
57+
);
58+
});
59+
}
60+
}

clippy_lints/src/loops/mod.rs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
mod char_indices_as_byte_indices;
2+
mod const_sized_chunks;
23
mod empty_loop;
34
mod explicit_counter_loop;
45
mod explicit_into_iter_loop;
@@ -784,6 +785,107 @@ declare_clippy_lint! {
784785
"using the character position yielded by `.chars().enumerate()` in a context where a byte index is expected"
785786
}
786787

788+
declare_clippy_lint! {
789+
/// ### What it does
790+
/// Checks for usage of `.chunks_exact()` on slices where the `chunk_size` is a constant and suggests
791+
/// replacing it with its const generic equivalent, `.array_chunks()`, in a way that the value of the
792+
/// const parameter `N` can be inferred.
793+
///
794+
/// Specifically, in a for-loop, consuming the iterator over the (non-overlapping) chunks, the lint
795+
/// suggests destructuring the chunks to allow for `N`, the number of elements in a chunk, to be inferred.
796+
///
797+
/// ### Why is this bad?
798+
/// When `.chunks_exact()` is used, a bounds check is required before an element can be accessed.
799+
///
800+
/// ### Example
801+
/// ```no_run
802+
/// let numbers = Vec::from_iter(0..10);
803+
/// for chunk in numbers.chunks_exact(2) {
804+
/// println!("{n} is an odd number", n = chunk[1]);
805+
/// }
806+
/// ```
807+
/// Use instead:
808+
/// ```no_run
809+
/// #![feature(array_chunks)]
810+
/// let numbers = Vec::from_iter(0..10);
811+
/// for [_, n] in numbers.array_chunks() {
812+
/// println!("{n} is an odd number");
813+
/// }
814+
/// ```
815+
#[clippy::version = "1.89.0"]
816+
pub CONST_SIZED_CHUNKS_EXACT,
817+
nursery,
818+
"calling `.chunks_exact()` with a constant `chunk_size` where `.array_chunks()` could be used instead"
819+
}
820+
821+
declare_clippy_lint! {
822+
/// ### What it does
823+
/// Checks for usage of `.chunks_exact_mut()` on slices where the `chunk_size` is a constant and suggests
824+
/// replacing it with its const generic equivalent, `.array_chunks_mut()`, in a way that the value of the
825+
/// const parameter `N` can be inferred.
826+
///
827+
/// Specifically, in a for-loop, consuming the iterator over the (non-overlapping) chunks, the lint
828+
/// suggests destructuring the chunks to allow for `N`, the number of elements in a chunk, to be inferred.
829+
///
830+
/// ### Why is this bad?
831+
/// When `.chunks_exact_mut()` is used, a bounds check is required before an element can be accessed.
832+
///
833+
/// ### Example
834+
/// ```no_run
835+
/// let mut numbers = Vec::from_iter(0..10);
836+
/// for chunk in numbers.chunks_exact_mut(2) {
837+
/// chunk[1] += chunk[0];
838+
/// println!("{n} is an odd number", n = chunk[1]);
839+
/// }
840+
/// ```
841+
/// Use instead:
842+
/// ```no_run
843+
/// #![feature(array_chunks)]
844+
/// let mut numbers = Vec::from_iter(0..10);
845+
/// for [m, n] in numbers.array_chunks_mut() {
846+
/// *n += *m;
847+
/// println!("{n} is an odd number");
848+
/// }
849+
/// ```
850+
#[clippy::version = "1.89.0"]
851+
pub CONST_SIZED_CHUNKS_EXACT_MUT,
852+
nursery,
853+
"calling `.chunks_exact_mut()` with a constant `chunk_size` where `.array_chunks_mut()` could be used instead"
854+
}
855+
856+
declare_clippy_lint! {
857+
/// ### What it does
858+
/// Checks for usage of `.windows()` on slices where the `size` is a constant and suggests replacing it with
859+
/// its const generic equivalent, `.array_windows()`, in a way that the value of the const parameter `N` can
860+
/// be inferred.
861+
///
862+
/// Specifically, in a for-loop, consuming the windowed iterator over the overlapping chunks, the lint
863+
/// suggests destructuring the chunks to allow for `N`, the number of elements in a chunk, to be inferred.
864+
///
865+
/// ### Why is this bad?
866+
/// When `.windows()` is used, a bounds check is required before an element can be accessed.
867+
///
868+
/// ### Example
869+
/// ```no_run
870+
/// let numbers = Vec::from_iter(0..10);
871+
/// for chunk in numbers.windows(2) {
872+
/// println!("{n} is an odd number", n = chunk[0] + chunk[1]);
873+
/// }
874+
/// ```
875+
/// Use instead:
876+
/// ```no_run
877+
/// #![feature(array_windows)]
878+
/// let numbers = Vec::from_iter(0..10);
879+
/// for [m, n] in numbers.array_windows() {
880+
/// println!("{n} is an odd number", n = m + n);
881+
/// }
882+
/// ```
883+
#[clippy::version = "1.89.0"]
884+
pub CONST_SIZED_WINDOWS,
885+
nursery,
886+
"calling `.windows()` with a constant `size` where `.array_windows()` could be used instead"
887+
}
888+
787889
pub struct Loops {
788890
msrv: Msrv,
789891
enforce_iter_loop_reborrow: bool,
@@ -822,6 +924,9 @@ impl_lint_pass!(Loops => [
822924
INFINITE_LOOP,
823925
MANUAL_SLICE_FILL,
824926
CHAR_INDICES_AS_BYTE_INDICES,
927+
CONST_SIZED_CHUNKS_EXACT,
928+
CONST_SIZED_CHUNKS_EXACT_MUT,
929+
CONST_SIZED_WINDOWS,
825930
]);
826931

827932
impl<'tcx> LateLintPass<'tcx> for Loops {
@@ -906,6 +1011,7 @@ impl Loops {
9061011
manual_find::check(cx, pat, arg, body, span, expr);
9071012
unused_enumerate_index::check(cx, pat, arg, body);
9081013
char_indices_as_byte_indices::check(cx, pat, arg, body);
1014+
const_sized_chunks::check(cx, pat, arg);
9091015
}
9101016

9111017
fn check_for_loop_arg(&self, cx: &LateContext<'_>, _: &Pat<'_>, arg: &Expr<'_>) {

clippy_utils/src/sym.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ generate! {
8080
ambiguous_glob_reexports,
8181
append,
8282
arg,
83+
array_chunks,
84+
array_chunks_mut,
85+
array_windows,
8386
as_bytes,
8487
as_deref,
8588
as_deref_mut,
@@ -108,6 +111,8 @@ generate! {
108111
checked_pow,
109112
checked_rem_euclid,
110113
checked_sub,
114+
chunks_exact,
115+
chunks_exact_mut,
111116
clamp,
112117
clippy_utils,
113118
clone_into,
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#![warn(clippy::const_sized_chunks_exact)]
2+
#![feature(array_chunks)]
3+
#![allow(unused_variables)]
4+
#![allow(dead_code)]
5+
6+
fn test_binding() {
7+
let numbers = [0, 1, 2, 3, 4];
8+
9+
#[allow(clippy::needless_borrow)]
10+
for [a, b] in numbers.array_chunks() {
11+
//~^ const_sized_chunks_exact
12+
}
13+
14+
#[allow(unused_mut)]
15+
for [a, b] in numbers.array_chunks() {
16+
//~^ const_sized_chunks_exact
17+
}
18+
19+
for [a, b] in numbers.array_chunks() {
20+
//~^ const_sized_chunks_exact
21+
}
22+
}
23+
24+
fn test_slice_like() {
25+
let numbers = [2; 5];
26+
for [a, b] in numbers.array_chunks() {
27+
//~^ const_sized_chunks_exact
28+
}
29+
30+
let numbers = [0, 1, 2, 3, 4];
31+
for [a, b] in numbers.array_chunks() {
32+
//~^ const_sized_chunks_exact
33+
}
34+
35+
let numbers = &[0, 1, 2, 3, 4];
36+
for [a, b] in numbers.array_chunks() {
37+
//~^ const_sized_chunks_exact
38+
}
39+
40+
let numbers = &&[0, 1, 2, 3, 4];
41+
for [a, b] in numbers.array_chunks() {
42+
//~^ const_sized_chunks_exact
43+
}
44+
45+
let numbers = &&&[0, 1, 2, 3, 4];
46+
for [a, b] in numbers.array_chunks() {
47+
//~^ const_sized_chunks_exact
48+
}
49+
50+
let numbers = Vec::from_iter(0..5);
51+
for [a, b] in numbers.array_chunks() {
52+
//~^ const_sized_chunks_exact
53+
}
54+
55+
let numbers = &Vec::from_iter(0..5);
56+
for [a, b] in numbers.array_chunks() {
57+
//~^ const_sized_chunks_exact
58+
}
59+
}
60+
61+
fn test_const_eval() {
62+
const N: usize = 2;
63+
64+
let numbers = [2; 5];
65+
for [a, b] in numbers.array_chunks() {
66+
//~^ const_sized_chunks_exact
67+
}
68+
69+
for [a, b, c] in numbers.array_chunks() {
70+
//~^ const_sized_chunks_exact
71+
}
72+
}
73+
74+
fn test_chunk_size() {
75+
let numbers = Vec::from_iter(0..5);
76+
for chunk in numbers.chunks_exact(0) {}
77+
78+
for [a] in numbers.array_chunks() {
79+
//~^ const_sized_chunks_exact
80+
}
81+
82+
for [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z] in numbers.array_chunks() {
83+
//~^ const_sized_chunks_exact
84+
}
85+
86+
for chunk in numbers.chunks_exact(27) {}
87+
}
88+
89+
fn main() {}

0 commit comments

Comments
 (0)