Skip to content
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

New lint: Implement ifs_same_cond_fn #4814

Merged
merged 1 commit into from
Nov 23, 2019
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -1030,6 +1030,7 @@ Released 2018-09-13
[`if_not_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_not_else
[`if_same_then_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_same_then_else
[`ifs_same_cond`]: https://rust-lang.github.io/rust-clippy/master/index.html#ifs_same_cond
[`ifs_same_cond_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#ifs_same_cond_fn
[`implicit_hasher`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_hasher
[`implicit_return`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_return
[`inconsistent_digit_grouping`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_digit_grouping
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code.

[There are 333 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
[There are 334 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)

We have a bunch of lint categories to allow you to choose how much Clippy is supposed to ~~annoy~~ help you:

Expand Down
78 changes: 77 additions & 1 deletion clippy_lints/src/copies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,53 @@ declare_clippy_lint! {
"consecutive `ifs` with the same condition"
}

declare_clippy_lint! {
/// **What it does:** Checks for consecutive `if`s with the same function call.
///
/// **Why is this bad?** This is probably a copy & paste error.
/// Despite the fact that function can have side effects and `if` works as
/// intended, such an approach is implicit and can be considered a "code smell".
///
/// **Known problems:** Hopefully none.
///
/// **Example:**
/// ```ignore
/// if foo() == bar {
/// …
/// } else if foo() == bar {
/// …
/// }
/// ```
///
/// This probably should be:
/// ```ignore
/// if foo() == bar {
/// …
/// } else if foo() == baz {
/// …
/// }
/// ```
///
/// or if the original code was not a typo and called function mutates a state,
/// consider move the mutation out of the `if` condition to avoid similarity to
/// a copy & paste error:
///
/// ```ignore
/// let first = foo();
/// if first == bar {
/// …
/// } else {
/// let second = foo();
/// if second == bar {
/// …
/// }
/// }
/// ```
pub SAME_FUNCTIONS_IN_IF_CONDITION,
pedantic,
"consecutive `ifs` with the same function call"
}

declare_clippy_lint! {
/// **What it does:** Checks for `if/else` with the same body as the *then* part
/// and the *else* part.
Expand Down Expand Up @@ -102,7 +149,7 @@ declare_clippy_lint! {
"`match` with identical arm bodies"
}

declare_lint_pass!(CopyAndPaste => [IFS_SAME_COND, IF_SAME_THEN_ELSE, MATCH_SAME_ARMS]);
declare_lint_pass!(CopyAndPaste => [IFS_SAME_COND, SAME_FUNCTIONS_IN_IF_CONDITION, IF_SAME_THEN_ELSE, MATCH_SAME_ARMS]);

impl<'a, 'tcx> LateLintPass<'a, 'tcx> for CopyAndPaste {
fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) {
Expand All @@ -119,6 +166,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for CopyAndPaste {
let (conds, blocks) = if_sequence(expr);
lint_same_then_else(cx, &blocks);
lint_same_cond(cx, &conds);
lint_same_fns_in_if_cond(cx, &conds);
lint_match_arms(cx, expr);
}
}
Expand Down Expand Up @@ -163,6 +211,34 @@ fn lint_same_cond(cx: &LateContext<'_, '_>, conds: &[&Expr]) {
}
}

/// Implementation of `SAME_FUNCTIONS_IN_IF_CONDITION`.
fn lint_same_fns_in_if_cond(cx: &LateContext<'_, '_>, conds: &[&Expr]) {
let hash: &dyn Fn(&&Expr) -> u64 = &|expr| -> u64 {
let mut h = SpanlessHash::new(cx, cx.tables);
h.hash_expr(expr);
h.finish()
};

let eq: &dyn Fn(&&Expr, &&Expr) -> bool = &|&lhs, &rhs| -> bool {
// Do not spawn warning if `IFS_SAME_COND` already produced it.
if SpanlessEq::new(cx).ignore_fn().eq_expr(lhs, rhs) {
return false;
}
SpanlessEq::new(cx).eq_expr(lhs, rhs)
};

for (i, j) in search_same(conds, hash, eq) {
span_note_and_lint(
cx,
SAME_FUNCTIONS_IN_IF_CONDITION,
j.span,
"this `if` has the same function call as a previous if",
i.span,
"same as this",
);
}
}

/// Implementation of `MATCH_SAME_ARMS`.
fn lint_match_arms<'tcx>(cx: &LateContext<'_, 'tcx>, expr: &Expr) {
fn same_bindings<'tcx>(
Expand Down
2 changes: 2 additions & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,7 @@ pub fn register_plugins(store: &mut lint::LintStore, sess: &Session, conf: &Conf
&collapsible_if::COLLAPSIBLE_IF,
&comparison_chain::COMPARISON_CHAIN,
&copies::IFS_SAME_COND,
&copies::SAME_FUNCTIONS_IN_IF_CONDITION,
&copies::IF_SAME_THEN_ELSE,
&copies::MATCH_SAME_ARMS,
&copy_iterator::COPY_ITERATOR,
Expand Down Expand Up @@ -989,6 +990,7 @@ pub fn register_plugins(store: &mut lint::LintStore, sess: &Session, conf: &Conf
store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![
LintId::of(&attrs::INLINE_ALWAYS),
LintId::of(&checked_conversions::CHECKED_CONVERSIONS),
LintId::of(&copies::SAME_FUNCTIONS_IN_IF_CONDITION),
LintId::of(&copies::MATCH_SAME_ARMS),
LintId::of(&copy_iterator::COPY_ITERATOR),
LintId::of(&default_trait_access::DEFAULT_TRAIT_ACCESS),
Expand Down
9 changes: 8 additions & 1 deletion src/lintlist/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pub use lint::Lint;
pub use lint::LINT_LEVELS;

// begin lint list, do not remove this comment, it’s used in `update_lints`
pub const ALL_LINTS: [Lint; 333] = [
pub const ALL_LINTS: [Lint; 334] = [
Lint {
name: "absurd_extreme_comparisons",
group: "correctness",
Expand Down Expand Up @@ -714,6 +714,13 @@ pub const ALL_LINTS: [Lint; 333] = [
deprecation: None,
module: "copies",
},
Lint {
name: "ifs_same_cond_fn",
group: "pedantic",
desc: "consecutive `ifs` with the same function call",
deprecation: None,
module: "copies",
},
Lint {
name: "implicit_hasher",
group: "style",
Expand Down
80 changes: 80 additions & 0 deletions tests/ui/same_functions_in_if_condition.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#![warn(clippy::same_functions_in_if_condition)]
#![allow(clippy::ifs_same_cond)] // This warning is different from `ifs_same_cond`.
#![allow(clippy::if_same_then_else, clippy::comparison_chain)] // all empty blocks

fn function() -> bool {
true
}

fn fn_arg(_arg: u8) -> bool {
true
}

struct Struct;

impl Struct {
fn method(&self) -> bool {
true
}
fn method_arg(&self, _arg: u8) -> bool {
true
}
}

fn ifs_same_cond_fn() {
let a = 0;
let obj = Struct;

if function() {
} else if function() {
//~ ERROR ifs same condition
}

if fn_arg(a) {
} else if fn_arg(a) {
//~ ERROR ifs same condition
}

if obj.method() {
} else if obj.method() {
//~ ERROR ifs same condition
}

if obj.method_arg(a) {
} else if obj.method_arg(a) {
//~ ERROR ifs same condition
}

let mut v = vec![1];
if v.pop() == None {
//~ ERROR ifs same condition
} else if v.pop() == None {
}

if v.len() == 42 {
//~ ERROR ifs same condition
} else if v.len() == 42 {
}

if v.len() == 1 {
// ok, different conditions
} else if v.len() == 2 {
}

if fn_arg(0) {
// ok, different arguments.
} else if fn_arg(1) {
}

if obj.method_arg(0) {
// ok, different arguments.
} else if obj.method_arg(1) {
}

if a == 1 {
// ok, warning is on `ifs_same_cond` behalf.
} else if a == 1 {
}
}

fn main() {}
75 changes: 75 additions & 0 deletions tests/ui/same_functions_in_if_condition.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
error: this `if` has the same function call as a previous if
--> $DIR/same_functions_in_if_condition.rs:29:15
|
LL | } else if function() {
| ^^^^^^^^^^
|
= note: `-D clippy::same-functions-in-if-condition` implied by `-D warnings`
note: same as this
--> $DIR/same_functions_in_if_condition.rs:28:8
|
LL | if function() {
| ^^^^^^^^^^

error: this `if` has the same function call as a previous if
--> $DIR/same_functions_in_if_condition.rs:34:15
|
LL | } else if fn_arg(a) {
| ^^^^^^^^^
|
note: same as this
--> $DIR/same_functions_in_if_condition.rs:33:8
|
LL | if fn_arg(a) {
| ^^^^^^^^^

error: this `if` has the same function call as a previous if
--> $DIR/same_functions_in_if_condition.rs:39:15
|
LL | } else if obj.method() {
| ^^^^^^^^^^^^
|
note: same as this
--> $DIR/same_functions_in_if_condition.rs:38:8
|
LL | if obj.method() {
| ^^^^^^^^^^^^

error: this `if` has the same function call as a previous if
--> $DIR/same_functions_in_if_condition.rs:44:15
|
LL | } else if obj.method_arg(a) {
| ^^^^^^^^^^^^^^^^^
|
note: same as this
--> $DIR/same_functions_in_if_condition.rs:43:8
|
LL | if obj.method_arg(a) {
| ^^^^^^^^^^^^^^^^^

error: this `if` has the same function call as a previous if
--> $DIR/same_functions_in_if_condition.rs:51:15
|
LL | } else if v.pop() == None {
| ^^^^^^^^^^^^^^^
|
note: same as this
--> $DIR/same_functions_in_if_condition.rs:49:8
|
LL | if v.pop() == None {
| ^^^^^^^^^^^^^^^

error: this `if` has the same function call as a previous if
--> $DIR/same_functions_in_if_condition.rs:56:15
|
LL | } else if v.len() == 42 {
| ^^^^^^^^^^^^^
|
note: same as this
--> $DIR/same_functions_in_if_condition.rs:54:8
|
LL | if v.len() == 42 {
| ^^^^^^^^^^^^^

error: aborting due to 6 previous errors