Skip to content

suggest a op= b over a = a op b #913

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 11, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ All notable changes to this project will be documented in this file.
[`absurd_extreme_comparisons`]: https://github.com/Manishearth/rust-clippy/wiki#absurd_extreme_comparisons
[`almost_swapped`]: https://github.com/Manishearth/rust-clippy/wiki#almost_swapped
[`approx_constant`]: https://github.com/Manishearth/rust-clippy/wiki#approx_constant
[`assign_op_pattern`]: https://github.com/Manishearth/rust-clippy/wiki#assign_op_pattern
[`assign_ops`]: https://github.com/Manishearth/rust-clippy/wiki#assign_ops
[`bad_bit_mask`]: https://github.com/Manishearth/rust-clippy/wiki#bad_bit_mask
[`blacklisted_name`]: https://github.com/Manishearth/rust-clippy/wiki#blacklisted_name
[`block_in_if_condition_expr`]: https://github.com/Manishearth/rust-clippy/wiki#block_in_if_condition_expr
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ Table of contents:

## Lints

There are 147 lints included in this crate:
There are 149 lints included in this crate:

name | default | meaning
---------------------------------------------------------------------------------------------------------------------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
[absurd_extreme_comparisons](https://github.com/Manishearth/rust-clippy/wiki#absurd_extreme_comparisons) | warn | a comparison involving a maximum or minimum value involves a case that is always true or always false
[almost_swapped](https://github.com/Manishearth/rust-clippy/wiki#almost_swapped) | warn | `foo = bar; bar = foo` sequence
[approx_constant](https://github.com/Manishearth/rust-clippy/wiki#approx_constant) | warn | the approximate of a known float constant (in `std::f64::consts` or `std::f32::consts`) is found; suggests to use the constant
[assign_op_pattern](https://github.com/Manishearth/rust-clippy/wiki#assign_op_pattern) | warn | assigning the result of an operation on a variable to that same variable
[assign_ops](https://github.com/Manishearth/rust-clippy/wiki#assign_ops) | allow | Any assignment operation
[bad_bit_mask](https://github.com/Manishearth/rust-clippy/wiki#bad_bit_mask) | warn | expressions of the form `_ & mask == select` that will only ever return `true` or `false` (because in the example `select` containing bits that `mask` doesn't have)
[blacklisted_name](https://github.com/Manishearth/rust-clippy/wiki#blacklisted_name) | warn | usage of a blacklisted/placeholder name
[block_in_if_condition_expr](https://github.com/Manishearth/rust-clippy/wiki#block_in_if_condition_expr) | warn | braces can be eliminated in conditions that are expressions, e.g `if { true } ...`
Expand Down
158 changes: 158 additions & 0 deletions src/assign_ops.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
use rustc::hir;
use rustc::lint::*;
use utils::{span_lint_and_then, span_lint, snippet_opt, SpanlessEq, get_trait_def_id, implements_trait};

/// **What it does:** This lint checks for `+=` operations and similar
///
/// **Why is this bad?** Projects with many developers from languages without those operations
/// may find them unreadable and not worth their weight
///
/// **Known problems:** Types implementing `OpAssign` don't necessarily implement `Op`
///
/// **Example:**
/// ```
/// a += 1;
/// ```
declare_restriction_lint! {
pub ASSIGN_OPS,
"Any assignment operation"
}

/// **What it does:** Check for `a = a op b` or `a = b commutative_op a` patterns
///
/// **Why is this bad?** These can be written as the shorter `a op= b`
///
/// **Known problems:** While forbidden by the spec, `OpAssign` traits may have implementations that differ from the regular `Op` impl
///
/// **Example:**
///
/// ```
/// let mut a = 5;
/// ...
/// a = a + b;
/// ```
declare_lint! {
pub ASSIGN_OP_PATTERN,
Warn,
"assigning the result of an operation on a variable to that same variable"
}

#[derive(Copy, Clone, Default)]
pub struct AssignOps;

impl LintPass for AssignOps {
fn get_lints(&self) -> LintArray {
lint_array!(ASSIGN_OPS, ASSIGN_OP_PATTERN)
}
}

impl LateLintPass for AssignOps {
fn check_expr(&mut self, cx: &LateContext, expr: &hir::Expr) {
match expr.node {
hir::ExprAssignOp(op, ref lhs, ref rhs) => {
if let (Some(l), Some(r)) = (snippet_opt(cx, lhs.span), snippet_opt(cx, rhs.span)) {
span_lint_and_then(cx,
ASSIGN_OPS,
expr.span,
"assign operation detected",
|db| {
match rhs.node {
hir::ExprBinary(op2, _, _) if op2 != op => {
db.span_suggestion(expr.span,
"replace it with",
format!("{} = {} {} ({})", l, l, op.node.as_str(), r));
},
_ => {
db.span_suggestion(expr.span,
"replace it with",
format!("{} = {} {} {}", l, l, op.node.as_str(), r));
}
}
});
} else {
span_lint(cx,
ASSIGN_OPS,
expr.span,
"assign operation detected");
}
},
hir::ExprAssign(ref assignee, ref e) => {
if let hir::ExprBinary(op, ref l, ref r) = e.node {
let lint = |assignee: &hir::Expr, rhs: &hir::Expr| {
let ty = cx.tcx.expr_ty(assignee);
if ty.walk_shallow().next().is_some() {
return; // implements_trait does not work with generics
}
let rty = cx.tcx.expr_ty(rhs);
if rty.walk_shallow().next().is_some() {
return; // implements_trait does not work with generics
}
macro_rules! ops {
($op:expr, $cx:expr, $ty:expr, $rty:expr, $($trait_name:ident:$full_trait_name:ident),+) => {
match $op {
$(hir::$full_trait_name => {
let [krate, module] = ::utils::paths::OPS_MODULE;
let path = [krate, module, concat!(stringify!($trait_name), "Assign")];
let trait_id = if let Some(trait_id) = get_trait_def_id($cx, &path) {
trait_id
} else {
return; // useless if the trait doesn't exist
};
implements_trait($cx, $ty, trait_id, vec![$rty])
},)*
_ => false,
}
}
}
if ops!(op.node, cx, ty, rty, Add:BiAdd,
Sub:BiSub,
Mul:BiMul,
Div:BiDiv,
Rem:BiRem,
And:BiAnd,
Or:BiOr,
BitAnd:BiBitAnd,
BitOr:BiBitOr,
BitXor:BiBitXor,
Shr:BiShr,
Shl:BiShl
) {
if let (Some(snip_a), Some(snip_r)) = (snippet_opt(cx, assignee.span), snippet_opt(cx, rhs.span)) {
span_lint_and_then(cx,
ASSIGN_OP_PATTERN,
expr.span,
"manual implementation of an assign operation",
|db| {
db.span_suggestion(expr.span,
"replace it with",
format!("{} {}= {}", snip_a, op.node.as_str(), snip_r));
});
} else {
span_lint(cx,
ASSIGN_OP_PATTERN,
expr.span,
"manual implementation of an assign operation");
}
}
};
// a = a op b
if SpanlessEq::new(cx).ignore_fn().eq_expr(assignee, l) {
lint(assignee, r);
}
// a = b commutative_op a
if SpanlessEq::new(cx).ignore_fn().eq_expr(assignee, r) {
match op.node {
hir::BiAdd | hir::BiMul |
hir::BiAnd | hir::BiOr |
hir::BiBitXor | hir::BiBitAnd | hir::BiBitOr => {
lint(assignee, l);
},
_ => {},
}
}
}
},
_ => {},
}
}
}
5 changes: 4 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
#![feature(question_mark)]
#![feature(stmt_expr_attributes)]
#![allow(indexing_slicing, shadow_reuse, unknown_lints)]
#![allow(float_arithmetic, integer_arithmetic)]

extern crate rustc_driver;
extern crate getopts;
Expand Down Expand Up @@ -192,6 +191,7 @@ pub mod utils;
pub mod approx_const;
pub mod arithmetic;
pub mod array_indexing;
pub mod assign_ops;
pub mod attrs;
pub mod bit_mask;
pub mod blacklisted_name;
Expand Down Expand Up @@ -383,10 +383,12 @@ pub fn plugin_registrar(reg: &mut Registry) {
reg.register_late_lint_pass(box unsafe_removed_from_name::UnsafeNameRemoval);
reg.register_late_lint_pass(box mem_forget::MemForget);
reg.register_late_lint_pass(box arithmetic::Arithmetic::default());
reg.register_late_lint_pass(box assign_ops::AssignOps);

reg.register_lint_group("clippy_restrictions", vec![
arithmetic::FLOAT_ARITHMETIC,
arithmetic::INTEGER_ARITHMETIC,
assign_ops::ASSIGN_OPS,
]);

reg.register_lint_group("clippy_pedantic", vec![
Expand Down Expand Up @@ -421,6 +423,7 @@ pub fn plugin_registrar(reg: &mut Registry) {
reg.register_lint_group("clippy", vec![
approx_const::APPROX_CONSTANT,
array_indexing::OUT_OF_BOUNDS_INDEXING,
assign_ops::ASSIGN_OP_PATTERN,
attrs::DEPRECATED_SEMVER,
attrs::INLINE_ALWAYS,
bit_mask::BAD_BIT_MASK,
Expand Down
1 change: 1 addition & 0 deletions src/utils/paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub const LINKED_LIST: [&'static str; 3] = ["collections", "linked_list", "Linke
pub const MEM_FORGET: [&'static str; 3] = ["core", "mem", "forget"];
pub const MUTEX: [&'static str; 4] = ["std", "sync", "mutex", "Mutex"];
pub const OPEN_OPTIONS: [&'static str; 3] = ["std", "fs", "OpenOptions"];
pub const OPS_MODULE: [&'static str; 2] = ["core", "ops"];
pub const OPTION: [&'static str; 3] = ["core", "option", "Option"];
pub const RANGE: [&'static str; 3] = ["core", "ops", "Range"];
pub const RANGE_FROM: [&'static str; 3] = ["core", "ops", "RangeFrom"];
Expand Down
65 changes: 65 additions & 0 deletions tests/compile-fail/assign_ops.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#![feature(plugin)]
#![plugin(clippy)]

#[deny(assign_ops)]
#[allow(unused_assignments)]
fn main() {
let mut i = 1i32;
i += 2; //~ ERROR assign operation detected
//~^ HELP replace it with
//~| SUGGESTION i = i + 2
i -= 6; //~ ERROR assign operation detected
//~^ HELP replace it with
//~| SUGGESTION i = i - 6
i *= 5; //~ ERROR assign operation detected
//~^ HELP replace it with
//~| SUGGESTION i = i * 5
i /= 32; //~ ERROR assign operation detected
//~^ HELP replace it with
//~| SUGGESTION i = i / 32
i %= 42; //~ ERROR assign operation detected
//~^ HELP replace it with
//~| SUGGESTION i = i % 42
i >>= i; //~ ERROR assign operation detected
//~^ HELP replace it with
//~| SUGGESTION i = i >> i
i <<= 9 + 6 - 7; //~ ERROR assign operation detected
//~^ HELP replace it with
//~| SUGGESTION i = i << (9 + 6 - 7)
}

#[allow(dead_code, unused_assignments)]
#[deny(assign_op_pattern)]
fn bla() {
let mut a = 5;
a = a + 1; //~ ERROR manual implementation of an assign operation
//~^ HELP replace it with
//~| SUGGESTION a += 1
a = 1 + a; //~ ERROR manual implementation of an assign operation
//~^ HELP replace it with
//~| SUGGESTION a += 1
a = a - 1; //~ ERROR manual implementation of an assign operation
//~^ HELP replace it with
//~| SUGGESTION a -= 1
a = a * 99; //~ ERROR manual implementation of an assign operation
//~^ HELP replace it with
//~| SUGGESTION a *= 99
a = 42 * a; //~ ERROR manual implementation of an assign operation
//~^ HELP replace it with
//~| SUGGESTION a *= 42
a = a / 2; //~ ERROR manual implementation of an assign operation
//~^ HELP replace it with
//~| SUGGESTION a /= 2
a = a % 5; //~ ERROR manual implementation of an assign operation
//~^ HELP replace it with
//~| SUGGESTION a %= 5
a = a & 1; //~ ERROR manual implementation of an assign operation
//~^ HELP replace it with
//~| SUGGESTION a &= 1
a = 1 - a;
a = 5 / a;
a = 42 % a;
a = 6 << a;
let mut s = String::new();
s = s + "bla";
}
2 changes: 1 addition & 1 deletion tests/compile-fail/strings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,6 @@ fn main() {

// the add is only caught for String
let mut x = 1;
x = x + 1;
x = x + 1; //~ WARN assign_op_pattern
assert_eq!(2, x);
}