Skip to content

Commit

Permalink
Auto merge of rust-lang#5727 - rail-rain:manual_memcpy_with_counter, …
Browse files Browse the repository at this point in the history
…r=flip1995

Expands `manual_memcpy` to lint ones with loop counters

Closes rust-lang#1670

This PR expands `manual_memcpy` to lint ones with loop counters as described in rust-lang/rust-clippy#1670 (comment)

Although the current code is working, I have a couple of questions and concerns.

~~Firstly, I manually implemented `Clone` for `Sugg` because `AssocOp` lacks `Clone`. As `AssocOp` only holds an enum, which is `Copy`, as a value, it seems `AssocOp` can be `Clone`; but, I was not sure where to ask it. Should I make a PR to `rustc`?~~ The [PR]( rust-lang#73629) was made.

Secondly, manual copying with loop counters are likely to trigger `needless_range_loop` and `explicit_counter_loop` along with `manual_memcpy`; in fact, I explicitly allowed them in the tests. Is there any way to disable these two lints when a code triggers `manual_memcpy`?

And, another thing I'd like to note is that `Sugg` adds unnecessary parentheses when expressions with parentheses passed to its `hir` function, as seen here:

```
error: it looks like you're manually copying between slices
  --> $DIR/manual_memcpy.rs:145:14
   |
LL |     for i in 3..(3 + src.len()) {
   |              ^^^^^^^^^^^^^^^^^^ help: try replacing the loop by: `dst[3..((3 + src.len()))].clone_from_slice(&src[..((3 + src.len()) - 3)])
```

However, using the `hir` function is needed to prevent the suggestion causing  errors when users use bitwise operations; and also this have already existed, for example: `verbose_bit_mask`. Thus, I think this is fine.

changelog: Expands `manual_memcpy` to lint ones with loop counters
  • Loading branch information
bors committed Oct 10, 2020
2 parents 2bdadd8 + b541884 commit dbc0285
Show file tree
Hide file tree
Showing 8 changed files with 804 additions and 297 deletions.
638 changes: 437 additions & 201 deletions clippy_lints/src/loops.rs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion clippy_lints/src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -708,7 +708,7 @@ fn reindent_multiline_inner(s: &str, ignore_first: bool, indent: Option<usize>,
}

/// Gets the parent expression, if any –- this is useful to constrain a lint.
pub fn get_parent_expr<'c>(cx: &'c LateContext<'_>, e: &Expr<'_>) -> Option<&'c Expr<'c>> {
pub fn get_parent_expr<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
let map = &cx.tcx.hir();
let hir_id = e.hir_id;
let parent_id = map.get_parent_node(hir_id);
Expand Down
59 changes: 52 additions & 7 deletions clippy_lints/src/utils/sugg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ use rustc_span::{BytePos, Pos};
use std::borrow::Cow;
use std::convert::TryInto;
use std::fmt::Display;
use std::ops::{Add, Neg, Not, Sub};

/// A helper type to build suggestion correctly handling parenthesis.
#[derive(Clone, PartialEq)]
pub enum Sugg<'a> {
/// An expression that never needs parenthesis such as `1337` or `[0; 42]`.
NonParen(Cow<'a, str>),
Expand All @@ -25,8 +27,12 @@ pub enum Sugg<'a> {
BinOp(AssocOp, Cow<'a, str>),
}

/// Literal constant `0`, for convenience.
pub const ZERO: Sugg<'static> = Sugg::NonParen(Cow::Borrowed("0"));
/// Literal constant `1`, for convenience.
pub const ONE: Sugg<'static> = Sugg::NonParen(Cow::Borrowed("1"));
/// a constant represents an empty string, for convenience.
pub const EMPTY: Sugg<'static> = Sugg::NonParen(Cow::Borrowed(""));

impl Display for Sugg<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
Expand Down Expand Up @@ -267,21 +273,60 @@ impl<'a> Sugg<'a> {
}
}

impl<'a, 'b> std::ops::Add<Sugg<'b>> for Sugg<'a> {
// Copied from the rust standart library, and then edited
macro_rules! forward_binop_impls_to_ref {
(impl $imp:ident, $method:ident for $t:ty, type Output = $o:ty) => {
impl $imp<$t> for &$t {
type Output = $o;

fn $method(self, other: $t) -> $o {
$imp::$method(self, &other)
}
}

impl $imp<&$t> for $t {
type Output = $o;

fn $method(self, other: &$t) -> $o {
$imp::$method(&self, other)
}
}

impl $imp for $t {
type Output = $o;

fn $method(self, other: $t) -> $o {
$imp::$method(&self, &other)
}
}
};
}

impl Add for &Sugg<'_> {
type Output = Sugg<'static>;
fn add(self, rhs: Sugg<'b>) -> Sugg<'static> {
make_binop(ast::BinOpKind::Add, &self, &rhs)
fn add(self, rhs: &Sugg<'_>) -> Sugg<'static> {
make_binop(ast::BinOpKind::Add, self, rhs)
}
}

impl<'a, 'b> std::ops::Sub<Sugg<'b>> for Sugg<'a> {
impl Sub for &Sugg<'_> {
type Output = Sugg<'static>;
fn sub(self, rhs: &Sugg<'_>) -> Sugg<'static> {
make_binop(ast::BinOpKind::Sub, self, rhs)
}
}

forward_binop_impls_to_ref!(impl Add, add for Sugg<'_>, type Output = Sugg<'static>);
forward_binop_impls_to_ref!(impl Sub, sub for Sugg<'_>, type Output = Sugg<'static>);

impl Neg for Sugg<'_> {
type Output = Sugg<'static>;
fn sub(self, rhs: Sugg<'b>) -> Sugg<'static> {
make_binop(ast::BinOpKind::Sub, &self, &rhs)
fn neg(self) -> Sugg<'static> {
make_unop("-", self)
}
}

impl<'a> std::ops::Not for Sugg<'a> {
impl Not for Sugg<'_> {
type Output = Sugg<'static>;
fn not(self) -> Sugg<'static> {
make_unop("!", self)
Expand Down
88 changes: 0 additions & 88 deletions tests/ui/manual_memcpy.stderr

This file was deleted.

88 changes: 88 additions & 0 deletions tests/ui/manual_memcpy/with_loop_counters.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#![warn(clippy::needless_range_loop, clippy::manual_memcpy)]

pub fn manual_copy_with_counters(src: &[i32], dst: &mut [i32], dst2: &mut [i32]) {
let mut count = 0;
for i in 3..src.len() {
dst[i] = src[count];
count += 1;
}

let mut count = 0;
for i in 3..src.len() {
dst[count] = src[i];
count += 1;
}

let mut count = 3;
for i in 0..src.len() {
dst[count] = src[i];
count += 1;
}

let mut count = 3;
for i in 0..src.len() {
dst[i] = src[count];
count += 1;
}

let mut count = 0;
for i in 3..(3 + src.len()) {
dst[i] = src[count];
count += 1;
}

let mut count = 3;
for i in 5..src.len() {
dst[i] = src[count - 2];
count += 1;
}

let mut count = 2;
for i in 0..dst.len() {
dst[i] = src[count];
count += 1;
}

let mut count = 5;
for i in 3..10 {
dst[i] = src[count];
count += 1;
}

let mut count = 3;
let mut count2 = 30;
for i in 0..src.len() {
dst[count] = src[i];
dst2[count2] = src[i];
count += 1;
count2 += 1;
}

// make sure parentheses are added properly to bitwise operators, which have lower precedence than
// arithmetric ones
let mut count = 0 << 1;
for i in 0..1 << 1 {
dst[count] = src[i + 2];
count += 1;
}

// make sure incrementing expressions without semicolons at the end of loops are handled correctly.
let mut count = 0;
for i in 3..src.len() {
dst[i] = src[count];
count += 1
}

// make sure ones where the increment is not at the end of the loop.
// As a possible enhancement, one could adjust the offset in the suggestion according to
// the position. For example, if the increment is at the top of the loop;
// treating the loop counter as if it were initialized 1 greater than the original value.
let mut count = 0;
#[allow(clippy::needless_range_loop)]
for i in 0..src.len() {
count += 1;
dst[i] = src[count];
}
}

fn main() {}
111 changes: 111 additions & 0 deletions tests/ui/manual_memcpy/with_loop_counters.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
error: it looks like you're manually copying between slices
--> $DIR/with_loop_counters.rs:5:5
|
LL | / for i in 3..src.len() {
LL | | dst[i] = src[count];
LL | | count += 1;
LL | | }
| |_____^ help: try replacing the loop by: `dst[3..src.len()].clone_from_slice(&src[..(src.len() - 3)]);`
|
= note: `-D clippy::manual-memcpy` implied by `-D warnings`

error: it looks like you're manually copying between slices
--> $DIR/with_loop_counters.rs:11:5
|
LL | / for i in 3..src.len() {
LL | | dst[count] = src[i];
LL | | count += 1;
LL | | }
| |_____^ help: try replacing the loop by: `dst[..(src.len() - 3)].clone_from_slice(&src[3..]);`

error: it looks like you're manually copying between slices
--> $DIR/with_loop_counters.rs:17:5
|
LL | / for i in 0..src.len() {
LL | | dst[count] = src[i];
LL | | count += 1;
LL | | }
| |_____^ help: try replacing the loop by: `dst[3..(src.len() + 3)].clone_from_slice(&src[..]);`

error: it looks like you're manually copying between slices
--> $DIR/with_loop_counters.rs:23:5
|
LL | / for i in 0..src.len() {
LL | | dst[i] = src[count];
LL | | count += 1;
LL | | }
| |_____^ help: try replacing the loop by: `dst[..src.len()].clone_from_slice(&src[3..(src.len() + 3)]);`

error: it looks like you're manually copying between slices
--> $DIR/with_loop_counters.rs:29:5
|
LL | / for i in 3..(3 + src.len()) {
LL | | dst[i] = src[count];
LL | | count += 1;
LL | | }
| |_____^ help: try replacing the loop by: `dst[3..((3 + src.len()))].clone_from_slice(&src[..((3 + src.len()) - 3)]);`

error: it looks like you're manually copying between slices
--> $DIR/with_loop_counters.rs:35:5
|
LL | / for i in 5..src.len() {
LL | | dst[i] = src[count - 2];
LL | | count += 1;
LL | | }
| |_____^ help: try replacing the loop by: `dst[5..src.len()].clone_from_slice(&src[(3 - 2)..((src.len() - 2) + 3 - 5)]);`

error: it looks like you're manually copying between slices
--> $DIR/with_loop_counters.rs:41:5
|
LL | / for i in 0..dst.len() {
LL | | dst[i] = src[count];
LL | | count += 1;
LL | | }
| |_____^ help: try replacing the loop by: `dst.clone_from_slice(&src[2..(dst.len() + 2)]);`

error: it looks like you're manually copying between slices
--> $DIR/with_loop_counters.rs:47:5
|
LL | / for i in 3..10 {
LL | | dst[i] = src[count];
LL | | count += 1;
LL | | }
| |_____^ help: try replacing the loop by: `dst[3..10].clone_from_slice(&src[5..(10 + 5 - 3)]);`

error: it looks like you're manually copying between slices
--> $DIR/with_loop_counters.rs:54:5
|
LL | / for i in 0..src.len() {
LL | | dst[count] = src[i];
LL | | dst2[count2] = src[i];
LL | | count += 1;
LL | | count2 += 1;
LL | | }
| |_____^
|
help: try replacing the loop by
|
LL | dst[3..(src.len() + 3)].clone_from_slice(&src[..]);
LL | dst2[30..(src.len() + 30)].clone_from_slice(&src[..]);
|

error: it looks like you're manually copying between slices
--> $DIR/with_loop_counters.rs:64:5
|
LL | / for i in 0..1 << 1 {
LL | | dst[count] = src[i + 2];
LL | | count += 1;
LL | | }
| |_____^ help: try replacing the loop by: `dst[(0 << 1)..((1 << 1) + (0 << 1))].clone_from_slice(&src[2..((1 << 1) + 2)]);`

error: it looks like you're manually copying between slices
--> $DIR/with_loop_counters.rs:71:5
|
LL | / for i in 3..src.len() {
LL | | dst[i] = src[count];
LL | | count += 1
LL | | }
| |_____^ help: try replacing the loop by: `dst[3..src.len()].clone_from_slice(&src[..(src.len() - 3)]);`

error: aborting due to 11 previous errors

File renamed without changes.
Loading

0 comments on commit dbc0285

Please sign in to comment.