Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
3d3d2be
Add lint to suggest as_chunks over chunks_exact with constant
rommeld Nov 1, 2025
4a4c955
Fix pass value instead of reference
rommeld Nov 1, 2025
d332ec5
Switched to symbols instead of using strings
rommeld Nov 1, 2025
610acda
Added suggestions and changes from review
rommeld Nov 4, 2025
632e37b
Updated CHANGELOG.md via
rommeld Nov 4, 2025
bc1d010
Reduced error message verbosity
rommeld Nov 4, 2025
1d99b91
Added check for types that can be adjusted as slices
rommeld Nov 4, 2025
2731771
Moved Rust version check and removed
rommeld Nov 4, 2025
7082eef
Added lint suggestion
rommeld Nov 7, 2025
dc9fa9d
Reduced highlighting to method call for better understanding
rommeld Nov 7, 2025
afaf92b
Moved method to lints which handle methods whose receivers and argume…
rommeld Nov 7, 2025
dfd8065
Changed suggestion to return iterator instead of tuple
rommeld Nov 7, 2025
3cfa99a
Moved version check after cheaper checks is slice and constant evalua…
rommeld Nov 7, 2025
94181ab
Changed suggestion to just consider method call
rommeld Nov 7, 2025
370d0ae
Changed linting suggestion span_lint_and_then to span_lint_and_sugg
rommeld Nov 8, 2025
24798af
Added skipping multi-line test from cargo dev fmt
rommeld Nov 8, 2025
5e05b1d
Improved lint by only suggesting fix when method not stored in variable
rommeld Nov 8, 2025
ae2cb12
Fixed missing curly brakets
rommeld Nov 8, 2025
43a7a72
Deleted comment from lint declaration
rommeld Nov 8, 2025
64aae82
Moved unfixable tests to separate file because ui_test otherwise woul…
rommeld Nov 8, 2025
83e0039
Swapped plain with so help function is visually attached to method
rommeld Nov 8, 2025
1e276d7
Fixed wrongly replaced comment in lint declaration
rommeld Nov 8, 2025
c50ccf8
Fixed one last typo in the lint declaration comment
rommeld Nov 8, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6252,6 +6252,7 @@ Released 2018-09-13
[`chars_last_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_last_cmp
[`chars_next_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_next_cmp
[`checked_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#checked_conversions
[`chunks_exact_with_const_size`]: https://rust-lang.github.io/rust-clippy/master/index.html#chunks_exact_with_const_size
[`clear_with_drain`]: https://rust-lang.github.io/rust-clippy/master/index.html#clear_with_drain
[`clone_double_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_double_ref
[`clone_on_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_on_copy
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/declared_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
crate::methods::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS_INFO,
crate::methods::CHARS_LAST_CMP_INFO,
crate::methods::CHARS_NEXT_CMP_INFO,
crate::methods::CHUNKS_EXACT_WITH_CONST_SIZE_INFO,
crate::methods::CLEAR_WITH_DRAIN_INFO,
crate::methods::CLONED_INSTEAD_OF_COPIED_INFO,
crate::methods::CLONE_ON_COPY_INFO,
Expand Down
50 changes: 50 additions & 0 deletions clippy_lints/src/methods/chunks_exact_with_const_size.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use clippy_utils::consts::{ConstEvalCtxt, Constant};
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet;
use clippy_utils::sym;
use rustc_hir::Expr;
use rustc_lint::LateContext;
use rustc_span::Symbol;

use super::CHUNKS_EXACT_WITH_CONST_SIZE;

pub(super) fn check(
cx: &LateContext<'_>,
expr: &Expr<'_>,
recv: &Expr<'_>,
arg: &Expr<'_>,
method_name: Symbol,
msrv: Msrv,
) {
// Check for Rust version
if !msrv.meets(cx, msrvs::AS_CHUNKS) {
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check is somewhat expensive, so it's best to perform it towards the end, e.g. after the constant check

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 2731771

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I meant after the constant check, but before the lint emission -- currently, the check doesn't actually do anything 😅

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


// Check receiver is slice or array type
let recv_ty = cx.typeck_results().expr_ty(recv).peel_refs();
if !recv_ty.is_slice() && !recv_ty.is_array() {
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You want to check for types that can be adjusted to slices, which includes e.g. Vec. For that, you can use TyCtxt::expr_ty_adjusted:

Suggested change
// Check receiver is slice or array type
let recv_ty = cx.typeck_results().expr_ty(recv).peel_refs();
if !recv_ty.is_slice() && !recv_ty.is_array() {
return;
}
// Check if receiver is slice-like
if !cx.typeck_results().expr_ty_adjusted(recv).peel_refs().is_slice() {
return;
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 1d99b91


// Check if argument is a constant
let constant_eval = ConstEvalCtxt::new(cx);
if let Some(Constant::Int(_)) = constant_eval.eval(arg) {
Comment on lines +27 to +28
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You want is_const_evaluable here.

// Emit the lint
let suggestion = if method_name == sym::chunks_exact_mut {
"as_chunks_mut"
} else {
"as_chunks"
};
let arg_str = snippet(cx, arg.span, "_");
span_lint_and_help(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be pretty effortlessly switched over to give suggestions, which are quite nice to have. https://doc.rust-lang.org/clippy/development/emitting_lints.html#suggestions-automatic-fixes should help you in that, but don't hesitate to ask if something's unclear

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 7082eef

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome, thank you!

cx,
CHUNKS_EXACT_WITH_CONST_SIZE,
expr.span,
"chunks_exact_with_const_size",
None,
format!("consider using `{suggestion}::<{arg_str}>()` for better ergonomics"),
);
}
}
31 changes: 31 additions & 0 deletions clippy_lints/src/methods/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ mod chars_last_cmp;
mod chars_last_cmp_with_unwrap;
mod chars_next_cmp;
mod chars_next_cmp_with_unwrap;
mod chunks_exact_with_const_size;
mod clear_with_drain;
mod clone_on_copy;
mod clone_on_ref_ptr;
Expand Down Expand Up @@ -2087,6 +2088,32 @@ declare_clippy_lint! {
"replace `.bytes().nth()` with `.as_bytes().get()`"
}

declare_clippy_lint! {
/// ### What it does
/// Checks for usage of `chunks_exact` or `chunks_exact_mut` with a constant chunk size.
///
/// ### Why is this bad?
/// `as_chunks` provides better ergonomics and type safety by returning arrays instead of slices.
/// It was stabilized in Rust 1.88.
///
/// ### Example
/// ```no_run
/// let slice = [1, 2, 3, 4, 5, 6];
/// let mut it = slice.chunks_exact(2);
/// for chunk in it {}
/// ```
/// Use instead:
/// ```no_run
/// let slice = [1, 2, 3, 4, 5, 6];
/// let (chunks, remainder) = slice.as_chunks::<2>();
/// for chunk in chunks {}
/// ```
#[clippy::version = "1.93.0"]
pub CHUNKS_EXACT_WITH_CONST_SIZE,
style,
"using `chunks_exact` with constant when `as_chunks` is more ergonomic"
}

declare_clippy_lint! {
/// ### What it does
/// Checks for the usage of `_.to_owned()`, `vec.to_vec()`, or similar when calling `_.clone()` would be clearer.
Expand Down Expand Up @@ -4787,6 +4814,7 @@ impl_lint_pass!(Methods => [
ITER_NTH,
ITER_NTH_ZERO,
BYTES_NTH,
CHUNKS_EXACT_WITH_CONST_SIZE,
ITER_SKIP_NEXT,
GET_UNWRAP,
GET_LAST_WITH_LEN,
Expand Down Expand Up @@ -5715,6 +5743,9 @@ impl Methods {
unwrap_expect_used::Variant::Unwrap,
);
},
(name @ (sym::chunks_exact | sym::chunks_exact_mut), [arg]) => {
chunks_exact_with_const_size::check(cx, expr, recv, arg, name, self.msrv);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, the span that the lint points at is the whole method call expression (expr.span) -- as the last result in Lintcheck (https://github.com/rust-lang/rust-clippy/actions/runs/19080004877#user-content-chunks-exact-with-const-size) shows, that might lead to a somewhat confusing diagnostics:

warning: chunks_exact_with_const_size
   --> target/lintcheck/sources/rustc-demangle-0.1.24/src/v0.rs:299:25
    |
299 |           let mut bytes = self
    |  _________________________^
300 | |             .nibbles
301 | |             .as_bytes()
302 | |             .chunks_exact(2)
    | |____________________________^
    |
    = help: consider using `as_chunks::<2>()` for better ergonomics
    = note: `--force-warn clippy::chunks-exact-with-const-size` implied by `--force-warn clippy::all`

You can instead only highlight the method call (in this case, chunks_exact(2), by using call_span, which comes from the call to method_call(expr) above (line 13) -- see filter_map_bool_then for a (somewhat) simple example

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hope I understood that correctly. Done in dc9fa9d

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty much! Though I think the .0.iter() part was actually correct, you didn't need to remove it

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, it would be great if you could add a test case for a multiline call -- to make sure that we're giving the correct diagnostic

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, it would be great if you could add a test case for a multiline call -- to make sure that we're giving the correct diagnostic

I wanted to add a multiline test, but check-fmt.rs comes in the way. And to be honest, I do not know how to handle that issue.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could either:

  1. Artificially elongate the expression, e.g. by adding more intermediate method calls, so that rustfmt splits it up into multiple lines
  2. Put the #[rustfmt::skip] attribute onto the whole statement, to avoid formatting it entirely -- but I wouldn't use that unless absolutely necessary, e.g. when you want to add tests for really bad formatting, like
    vec   .   chunks_exact(  4)

},
_ => {},
}
}
Expand Down
2 changes: 1 addition & 1 deletion clippy_utils/src/msrvs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ macro_rules! msrv_aliases {

// names may refer to stabilized feature flags or library items
msrv_aliases! {
1,88,0 { LET_CHAINS }
1,88,0 { LET_CHAINS, AS_CHUNKS }
1,87,0 { OS_STR_DISPLAY, INT_MIDPOINT, CONST_CHAR_IS_DIGIT, UNSIGNED_IS_MULTIPLE_OF, INTEGER_SIGN_CAST }
1,85,0 { UINT_FLOAT_MIDPOINT, CONST_SIZE_OF_VAL }
1,84,0 { CONST_OPTION_AS_SLICE, MANUAL_DANGLING_PTR }
Expand Down
2 changes: 2 additions & 0 deletions clippy_utils/src/sym.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ generate! {
checked_pow,
checked_rem_euclid,
checked_sub,
chunks_exact,
chunks_exact_mut,
clamp,
clippy_utils,
clone_into,
Expand Down
34 changes: 34 additions & 0 deletions tests/ui/chunks_exact_with_const_size.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#![warn(clippy::chunks_exact_with_const_size)]
#![allow(unused)]

fn main() {
let slice = [1, 2, 3, 4, 5, 6, 7, 8];

// Should trigger lint - literal constant
let mut it = slice.chunks_exact(4);
//~^ ERROR: chunks_exact_with_const_size
for chunk in it {}

// Should trigger lint - const value
const CHUNK_SIZE: usize = 4;
let mut it = slice.chunks_exact(CHUNK_SIZE);
//~^ ERROR: chunks_exact_with_const_size
for chunk in it {}

// Should NOT trigger - runtime value
let size = 4;
let mut it = slice.chunks_exact(size);
for chunk in it {}

// Should trigger lint - with remainder
let mut it = slice.chunks_exact(3);
//~^ ERROR: chunks_exact_with_const_size
for chunk in &mut it {}
for e in it.remainder() {}

// Should trigger - mutable variant
let mut arr = [1, 2, 3, 4, 5, 6, 7, 8];
let mut it = arr.chunks_exact_mut(4);
//~^ ERROR: chunks_exact_with_const_size
for chunk in it {}
}
36 changes: 36 additions & 0 deletions tests/ui/chunks_exact_with_const_size.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
error: chunks_exact_with_const_size
--> tests/ui/chunks_exact_with_const_size.rs:8:18
|
LL | let mut it = slice.chunks_exact(4);
| ^^^^^^^^^^^^^^^^^^^^^
|
= help: consider using `as_chunks::<4>()` for better ergonomics
= note: `-D clippy::chunks-exact-with-const-size` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::chunks_exact_with_const_size)]`

error: chunks_exact_with_const_size
--> tests/ui/chunks_exact_with_const_size.rs:14:18
|
LL | let mut it = slice.chunks_exact(CHUNK_SIZE);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: consider using `as_chunks::<CHUNK_SIZE>()` for better ergonomics

error: chunks_exact_with_const_size
--> tests/ui/chunks_exact_with_const_size.rs:24:18
|
LL | let mut it = slice.chunks_exact(3);
| ^^^^^^^^^^^^^^^^^^^^^
|
= help: consider using `as_chunks::<3>()` for better ergonomics

error: chunks_exact_with_const_size
--> tests/ui/chunks_exact_with_const_size.rs:31:18
|
LL | let mut it = arr.chunks_exact_mut(4);
| ^^^^^^^^^^^^^^^^^^^^^^^
|
= help: consider using `as_chunks_mut::<4>()` for better ergonomics

error: aborting due to 4 previous errors

Empty file.