Skip to content

Commit e82b1f4

Browse files
committed
add manual_slice_fill lint
1 parent 3d6e090 commit e82b1f4

File tree

9 files changed

+392
-0
lines changed

9 files changed

+392
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5772,6 +5772,7 @@ Released 2018-09-13
57725772
[`manual_retain`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_retain
57735773
[`manual_rotate`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_rotate
57745774
[`manual_saturating_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_saturating_arithmetic
5775+
[`manual_slice_fill`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_slice_fill
57755776
[`manual_slice_size_calculation`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_slice_size_calculation
57765777
[`manual_split_once`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_split_once
57775778
[`manual_str_repeat`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_str_repeat

book/src/lint_configuration.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -751,6 +751,7 @@ The minimum rust version that the project supports. Defaults to the `rust-versio
751751
* [`manual_range_contains`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_contains)
752752
* [`manual_rem_euclid`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_rem_euclid)
753753
* [`manual_retain`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_retain)
754+
* [`manual_slice_fill`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_slice_fill)
754755
* [`manual_split_once`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_split_once)
755756
* [`manual_str_repeat`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_str_repeat)
756757
* [`manual_strip`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_strip)

clippy_config/src/conf.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,7 @@ define_Conf! {
620620
manual_range_contains,
621621
manual_rem_euclid,
622622
manual_retain,
623+
manual_slice_fill,
623624
manual_split_once,
624625
manual_str_repeat,
625626
manual_strip,

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
291291
crate::loops::MANUAL_FIND_INFO,
292292
crate::loops::MANUAL_FLATTEN_INFO,
293293
crate::loops::MANUAL_MEMCPY_INFO,
294+
crate::loops::MANUAL_SLICE_FILL_INFO,
294295
crate::loops::MANUAL_WHILE_LET_SOME_INFO,
295296
crate::loops::MISSING_SPIN_LOOP_INFO,
296297
crate::loops::MUT_RANGE_BOUND_INFO,
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::eager_or_lazy::switch_to_eager_eval;
3+
use clippy_utils::macros::span_is_local;
4+
use clippy_utils::msrvs::{self, Msrv};
5+
use clippy_utils::source::{HasSession, snippet_with_applicability};
6+
use clippy_utils::ty::implements_trait;
7+
use clippy_utils::{higher, peel_blocks_with_stmt, span_contains_comment};
8+
use rustc_ast::ast::LitKind;
9+
use rustc_ast::{RangeLimits, UnOp};
10+
use rustc_data_structures::packed::Pu128;
11+
use rustc_errors::Applicability;
12+
use rustc_hir::QPath::Resolved;
13+
use rustc_hir::def::Res;
14+
use rustc_hir::{Expr, ExprKind, Pat};
15+
use rustc_lint::LateContext;
16+
use rustc_span::source_map::Spanned;
17+
use rustc_span::sym;
18+
19+
use super::MANUAL_SLICE_FILL;
20+
21+
pub(super) fn check<'tcx>(
22+
cx: &LateContext<'tcx>,
23+
pat: &'tcx Pat<'_>,
24+
arg: &'tcx Expr<'_>,
25+
body: &'tcx Expr<'_>,
26+
expr: &'tcx Expr<'_>,
27+
msrv: &Msrv,
28+
) {
29+
if !msrv.meets(msrvs::SLICE_FILL) {
30+
return;
31+
}
32+
33+
// `for _ in 0..slice.len() { slice[_] = value; }`
34+
if let Some(higher::Range {
35+
start: Some(start),
36+
end: Some(end),
37+
limits: RangeLimits::HalfOpen,
38+
}) = higher::Range::hir(arg)
39+
&& let ExprKind::Lit(Spanned {
40+
node: LitKind::Int(Pu128(0), _),
41+
..
42+
}) = start.kind
43+
&& let ExprKind::Block(..) = body.kind
44+
// Check if the body is an assignment to a slice element.
45+
&& let ExprKind::Assign(assignee, assignval, _) = peel_blocks_with_stmt(body).kind
46+
&& let ExprKind::Index(slice, _, _) = assignee.kind
47+
// Check if `len()` is used for the range end.
48+
&& let ExprKind::MethodCall(path, recv,..) = end.kind
49+
&& path.ident.name == sym::len
50+
// Check if the slice which is being assigned to is the same as the one being iterated over.
51+
&& let ExprKind::Path(Resolved(_, recv_path)) = recv.kind
52+
&& let ExprKind::Path(Resolved(_, slice_path)) = slice.kind
53+
&& recv_path.res == slice_path.res
54+
&& !assignval.span.from_expansion()
55+
// It is generally not equivalent to use the `fill` method if `assignval` can have side effects
56+
&& switch_to_eager_eval(cx, assignval)
57+
&& span_is_local(assignval.span)
58+
// The `fill` method requires that the slice's element type implements the `Clone` trait.
59+
&& let Some(clone_trait) = cx.tcx.lang_items().clone_trait()
60+
&& implements_trait(cx, cx.typeck_results().expr_ty(slice), clone_trait, &[])
61+
{
62+
sugg(cx, body, expr, slice.span, assignval.span);
63+
}
64+
// `for _ in &mut slice { *_ = value; }`
65+
else if let ExprKind::AddrOf(_, _, recv) = arg.kind
66+
// Check if the body is an assignment to a slice element.
67+
&& let ExprKind::Assign(assignee, assignval, _) = peel_blocks_with_stmt(body).kind
68+
&& let ExprKind::Unary(UnOp::Deref, slice_iter) = assignee.kind
69+
&& let ExprKind::Path(Resolved(_, recv_path)) = recv.kind
70+
// Check if the slice which is being assigned to is the same as the one being iterated over.
71+
&& let ExprKind::Path(Resolved(_, slice_path)) = slice_iter.kind
72+
&& let Res::Local(local) = slice_path.res
73+
&& local == pat.hir_id
74+
&& !assignval.span.from_expansion()
75+
&& switch_to_eager_eval(cx, assignval)
76+
&& span_is_local(assignval.span)
77+
// The `fill` method cannot be used if the slice's element type does not implement the `Clone` trait.
78+
&& let Some(clone_trait) = cx.tcx.lang_items().clone_trait()
79+
&& implements_trait(cx, cx.typeck_results().expr_ty(recv), clone_trait, &[])
80+
{
81+
sugg(cx, body, expr, recv_path.span, assignval.span);
82+
}
83+
}
84+
85+
fn sugg<'tcx>(
86+
cx: &LateContext<'tcx>,
87+
body: &'tcx Expr<'_>,
88+
expr: &'tcx Expr<'_>,
89+
slice_span: rustc_span::Span,
90+
assignval_span: rustc_span::Span,
91+
) {
92+
let mut app = if span_contains_comment(cx.sess().source_map(), body.span) {
93+
Applicability::MaybeIncorrect // Comments may be informational.
94+
} else {
95+
Applicability::MachineApplicable
96+
};
97+
98+
span_lint_and_sugg(
99+
cx,
100+
MANUAL_SLICE_FILL,
101+
expr.span,
102+
"manually filling a slice",
103+
"try",
104+
format!(
105+
"{}.fill({});",
106+
snippet_with_applicability(cx, slice_span, "..", &mut app),
107+
snippet_with_applicability(cx, assignval_span, "..", &mut app),
108+
),
109+
app,
110+
);
111+
}

clippy_lints/src/loops/mod.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ mod iter_next_loop;
88
mod manual_find;
99
mod manual_flatten;
1010
mod manual_memcpy;
11+
mod manual_slice_fill;
1112
mod manual_while_let_some;
1213
mod missing_spin_loop;
1314
mod mut_range_bound;
@@ -714,6 +715,31 @@ declare_clippy_lint! {
714715
"possibly unintended infinite loop"
715716
}
716717

718+
declare_clippy_lint! {
719+
/// ### What it does
720+
/// Checks for manually filling a slice with a value.
721+
///
722+
/// ### Why is this bad?
723+
/// Using the `fill` method is more idiomatic and concise.
724+
///
725+
/// ### Example
726+
/// ```no_run
727+
/// let mut some_slice = [1, 2, 3, 4, 5];
728+
/// for i in 0..some_slice.len() {
729+
/// some_slice[i] = 0;
730+
/// }
731+
/// ```
732+
/// Use instead:
733+
/// ```no_run
734+
/// let mut some_slice = [1, 2, 3, 4, 5];
735+
/// some_slice.fill(0);
736+
/// ```
737+
#[clippy::version = "1.86.0"]
738+
pub MANUAL_SLICE_FILL,
739+
style,
740+
"manually filling a slice with a value"
741+
}
742+
717743
pub struct Loops {
718744
msrv: Msrv,
719745
enforce_iter_loop_reborrow: bool,
@@ -750,6 +776,7 @@ impl_lint_pass!(Loops => [
750776
MANUAL_WHILE_LET_SOME,
751777
UNUSED_ENUMERATE_INDEX,
752778
INFINITE_LOOP,
779+
MANUAL_SLICE_FILL,
753780
]);
754781

755782
impl<'tcx> LateLintPass<'tcx> for Loops {
@@ -823,6 +850,7 @@ impl Loops {
823850
) {
824851
let is_manual_memcpy_triggered = manual_memcpy::check(cx, pat, arg, body, expr);
825852
if !is_manual_memcpy_triggered {
853+
manual_slice_fill::check(cx, pat, arg, body, expr, &self.msrv);
826854
needless_range_loop::check(cx, pat, arg, body, expr);
827855
explicit_counter_loop::check(cx, pat, arg, body, expr, label);
828856
}

tests/ui/manual_slice_fill.fixed

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#![warn(clippy::manual_slice_fill)]
2+
#![allow(clippy::needless_range_loop)]
3+
4+
macro_rules! assign_element {
5+
($slice:ident, $index:expr) => {
6+
$slice[$index] = 0;
7+
};
8+
}
9+
10+
macro_rules! assign_element_2 {
11+
($i:expr) => {
12+
$i = 0;
13+
};
14+
}
15+
16+
struct NoClone;
17+
18+
fn num() -> usize {
19+
5
20+
}
21+
22+
fn should_lint() {
23+
let mut some_slice = [1, 2, 3, 4, 5];
24+
25+
some_slice.fill(0);
26+
27+
let x = 5;
28+
some_slice.fill(x);
29+
30+
some_slice.fill(0);
31+
32+
// This should trigger `manual_slice_fill`, but the applicability is `MaybeIncorrect` since comments
33+
// within the loop might be purely informational.
34+
some_slice.fill(0);
35+
}
36+
37+
fn should_not_lint() {
38+
let mut some_slice = [1, 2, 3, 4, 5];
39+
40+
// Should not lint because we can't determine if the scope of the loop is intended to access all the
41+
// elements of the slice.
42+
for i in 0..5 {
43+
some_slice[i] = 0;
44+
}
45+
46+
// Should not lint, as using a function to assign values to elements might be
47+
// intentional. For example, the contents of `num()` could be temporary and subject to change
48+
// later.
49+
for i in 0..some_slice.len() {
50+
some_slice[i] = num();
51+
}
52+
53+
// Should not lint because this loop isn't equivalent to `fill`.
54+
for i in 0..some_slice.len() {
55+
some_slice[i] = 0;
56+
println!("foo");
57+
}
58+
59+
// Should not lint because it may be intentional to use a macro to perform an operation equivalent
60+
// to `fill`.
61+
for i in 0..some_slice.len() {
62+
assign_element!(some_slice, i);
63+
}
64+
65+
let another_slice = [1, 2, 3];
66+
// Should not lint because the range is not for `some_slice`.
67+
for i in 0..another_slice.len() {
68+
some_slice[i] = 0;
69+
}
70+
71+
let mut vec: Vec<Option<NoClone>> = Vec::with_capacity(5);
72+
// Should not lint because `NoClone` does not have `Clone` trait.
73+
for i in 0..vec.len() {
74+
vec[i] = None;
75+
}
76+
77+
// Should not lint, as using a function to assign values to elements might be
78+
// intentional. For example, the contents of `num()` could be temporary and subject to change
79+
// later.
80+
for i in &mut some_slice {
81+
*i = num();
82+
}
83+
84+
// Should not lint because this loop isn't equivalent to `fill`.
85+
for i in &mut some_slice {
86+
*i = 0;
87+
println!("foo");
88+
}
89+
90+
// Should not lint because it may be intentional to use a macro to perform an operation equivalent
91+
// to `fill`.
92+
for i in &mut some_slice {
93+
assign_element_2!(*i);
94+
}
95+
96+
let mut vec: Vec<Option<NoClone>> = Vec::with_capacity(5);
97+
// Should not lint because `NoClone` does not have `Clone` trait.
98+
for i in &mut vec {
99+
*i = None;
100+
}
101+
}

0 commit comments

Comments
 (0)