Skip to content

Commit 72f0bae

Browse files
committed
Implement if_same_cond_fn lint
1 parent 0574d66 commit 72f0bae

File tree

6 files changed

+204
-2
lines changed

6 files changed

+204
-2
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ document.
88

99
[3aea860...master](https://github.com/rust-lang/rust-clippy/compare/3aea860...master)
1010

11+
* New lints:
12+
* [`ifs_same_cond_fn`] [#4814](https://github.com/rust-lang/rust-clippy/pull/4814)
13+
1114
## Rust 1.39
1215

1316
Current Beta

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

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

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

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

clippy_lints/src/copies.rs

+52-1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,28 @@ declare_clippy_lint! {
4040
"consecutive `ifs` with the same condition"
4141
}
4242

43+
declare_clippy_lint! {
44+
/// **What it does:** Checks for consecutive `if`s with the same function call.
45+
///
46+
/// **Why is this bad?** This is probably a copy & paste error.
47+
/// Despite the fact that function can have side effects and `if` works as
48+
/// intended, such an approach is implicit and can be considered a "code smell".
49+
///
50+
/// **Known problems:** Hopefully none.
51+
///
52+
/// **Example:**
53+
/// ```ignore
54+
/// if foo() {
55+
/// …
56+
/// } else if foo() {
57+
/// …
58+
/// }
59+
/// ```
60+
pub IFS_SAME_COND_FN,
61+
pedantic,
62+
"consecutive `ifs` with the same function call"
63+
}
64+
4365
declare_clippy_lint! {
4466
/// **What it does:** Checks for `if/else` with the same body as the *then* part
4567
/// and the *else* part.
@@ -102,7 +124,7 @@ declare_clippy_lint! {
102124
"`match` with identical arm bodies"
103125
}
104126

105-
declare_lint_pass!(CopyAndPaste => [IFS_SAME_COND, IF_SAME_THEN_ELSE, MATCH_SAME_ARMS]);
127+
declare_lint_pass!(CopyAndPaste => [IFS_SAME_COND, IFS_SAME_COND_FN, IF_SAME_THEN_ELSE, MATCH_SAME_ARMS]);
106128

107129
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for CopyAndPaste {
108130
fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) {
@@ -119,6 +141,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for CopyAndPaste {
119141
let (conds, blocks) = if_sequence(expr);
120142
lint_same_then_else(cx, &blocks);
121143
lint_same_cond(cx, &conds);
144+
lint_same_cond_fn(cx, &conds);
122145
lint_match_arms(cx, expr);
123146
}
124147
}
@@ -163,6 +186,34 @@ fn lint_same_cond(cx: &LateContext<'_, '_>, conds: &[&Expr]) {
163186
}
164187
}
165188

189+
/// Implementation of `IFS_SAME_COND_FN`.
190+
fn lint_same_cond_fn(cx: &LateContext<'_, '_>, conds: &[&Expr]) {
191+
let hash: &dyn Fn(&&Expr) -> u64 = &|expr| -> u64 {
192+
let mut h = SpanlessHash::new(cx, cx.tables);
193+
h.hash_expr(expr);
194+
h.finish()
195+
};
196+
197+
let eq: &dyn Fn(&&Expr, &&Expr) -> bool = &|&lhs, &rhs| -> bool {
198+
// Do not spawn warning if `IFS_SAME_COND` already produced it.
199+
if SpanlessEq::new(cx).ignore_fn().eq_expr(lhs, rhs) {
200+
return false;
201+
}
202+
SpanlessEq::new(cx).eq_expr(lhs, rhs)
203+
};
204+
205+
for (i, j) in search_same(conds, hash, eq) {
206+
span_note_and_lint(
207+
cx,
208+
IFS_SAME_COND_FN,
209+
j.span,
210+
"this `if` has the same function call as a previous if",
211+
i.span,
212+
"same as this",
213+
);
214+
}
215+
}
216+
166217
/// Implementation of `MATCH_SAME_ARMS`.
167218
fn lint_match_arms<'tcx>(cx: &LateContext<'_, 'tcx>, expr: &Expr) {
168219
fn same_bindings<'tcx>(

clippy_lints/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,7 @@ pub fn register_plugins(store: &mut lint::LintStore, sess: &Session, conf: &Conf
471471
&collapsible_if::COLLAPSIBLE_IF,
472472
&comparison_chain::COMPARISON_CHAIN,
473473
&copies::IFS_SAME_COND,
474+
&copies::IFS_SAME_COND_FN,
474475
&copies::IF_SAME_THEN_ELSE,
475476
&copies::MATCH_SAME_ARMS,
476477
&copy_iterator::COPY_ITERATOR,
@@ -989,6 +990,7 @@ pub fn register_plugins(store: &mut lint::LintStore, sess: &Session, conf: &Conf
989990
store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![
990991
LintId::of(&attrs::INLINE_ALWAYS),
991992
LintId::of(&checked_conversions::CHECKED_CONVERSIONS),
993+
LintId::of(&copies::IFS_SAME_COND_FN),
992994
LintId::of(&copies::MATCH_SAME_ARMS),
993995
LintId::of(&copy_iterator::COPY_ITERATOR),
994996
LintId::of(&default_trait_access::DEFAULT_TRAIT_ACCESS),
@@ -1067,6 +1069,7 @@ pub fn register_plugins(store: &mut lint::LintStore, sess: &Session, conf: &Conf
10671069
LintId::of(&collapsible_if::COLLAPSIBLE_IF),
10681070
LintId::of(&comparison_chain::COMPARISON_CHAIN),
10691071
LintId::of(&copies::IFS_SAME_COND),
1072+
LintId::of(&copies::IFS_SAME_COND_FN),
10701073
LintId::of(&copies::IF_SAME_THEN_ELSE),
10711074
LintId::of(&derive::DERIVE_HASH_XOR_EQ),
10721075
LintId::of(&doc::MISSING_SAFETY_DOC),

tests/ui/ifs_same_cond_fn.rs

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#![warn(clippy::ifs_same_cond_fn)]
2+
#![allow(clippy::ifs_same_cond)] // This warning is different from `ifs_same_cond`.
3+
#![allow(clippy::if_same_then_else, clippy::comparison_chain)] // all empty blocks
4+
5+
fn function() -> bool {
6+
true
7+
}
8+
9+
fn fn_arg(_arg: u8) -> bool {
10+
true
11+
}
12+
13+
struct Struct;
14+
15+
impl Struct {
16+
fn method(&self) -> bool {
17+
true
18+
}
19+
fn method_arg(&self, _arg: u8) -> bool {
20+
true
21+
}
22+
}
23+
24+
fn ifs_same_cond_fn() {
25+
let a = 0;
26+
let obj = Struct;
27+
28+
if function() {
29+
} else if function() {
30+
//~ ERROR ifs same condition
31+
}
32+
33+
if fn_arg(a) {
34+
} else if fn_arg(a) {
35+
//~ ERROR ifs same condition
36+
}
37+
38+
if obj.method() {
39+
} else if obj.method() {
40+
//~ ERROR ifs same condition
41+
}
42+
43+
if obj.method_arg(a) {
44+
} else if obj.method_arg(a) {
45+
//~ ERROR ifs same condition
46+
}
47+
48+
let mut v = vec![1];
49+
if v.pop() == None {
50+
//~ ERROR ifs same condition
51+
} else if v.pop() == None {
52+
}
53+
54+
if v.len() == 42 {
55+
//~ ERROR ifs same condition
56+
} else if v.len() == 42 {
57+
}
58+
59+
if v.len() == 1 {
60+
// ok, different conditions
61+
} else if v.len() == 2 {
62+
}
63+
64+
if a == 1 {
65+
// ok, warning is on `ifs_same_cond` behalf.
66+
} else if a == 1 {
67+
}
68+
}
69+
70+
fn main() {}

tests/ui/ifs_same_cond_fn.stderr

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
error: this `if` has the same function call as a previous if
2+
--> $DIR/ifs_same_cond_fn.rs:29:15
3+
|
4+
LL | } else if function() {
5+
| ^^^^^^^^^^
6+
|
7+
= note: `-D clippy::ifs-same-cond-fn` implied by `-D warnings`
8+
note: same as this
9+
--> $DIR/ifs_same_cond_fn.rs:28:8
10+
|
11+
LL | if function() {
12+
| ^^^^^^^^^^
13+
14+
error: this `if` has the same function call as a previous if
15+
--> $DIR/ifs_same_cond_fn.rs:34:15
16+
|
17+
LL | } else if fn_arg(a) {
18+
| ^^^^^^^^^
19+
|
20+
note: same as this
21+
--> $DIR/ifs_same_cond_fn.rs:33:8
22+
|
23+
LL | if fn_arg(a) {
24+
| ^^^^^^^^^
25+
26+
error: this `if` has the same function call as a previous if
27+
--> $DIR/ifs_same_cond_fn.rs:39:15
28+
|
29+
LL | } else if obj.method() {
30+
| ^^^^^^^^^^^^
31+
|
32+
note: same as this
33+
--> $DIR/ifs_same_cond_fn.rs:38:8
34+
|
35+
LL | if obj.method() {
36+
| ^^^^^^^^^^^^
37+
38+
error: this `if` has the same function call as a previous if
39+
--> $DIR/ifs_same_cond_fn.rs:44:15
40+
|
41+
LL | } else if obj.method_arg(a) {
42+
| ^^^^^^^^^^^^^^^^^
43+
|
44+
note: same as this
45+
--> $DIR/ifs_same_cond_fn.rs:43:8
46+
|
47+
LL | if obj.method_arg(a) {
48+
| ^^^^^^^^^^^^^^^^^
49+
50+
error: this `if` has the same function call as a previous if
51+
--> $DIR/ifs_same_cond_fn.rs:51:15
52+
|
53+
LL | } else if v.pop() == None {
54+
| ^^^^^^^^^^^^^^^
55+
|
56+
note: same as this
57+
--> $DIR/ifs_same_cond_fn.rs:49:8
58+
|
59+
LL | if v.pop() == None {
60+
| ^^^^^^^^^^^^^^^
61+
62+
error: this `if` has the same function call as a previous if
63+
--> $DIR/ifs_same_cond_fn.rs:56:15
64+
|
65+
LL | } else if v.len() == 42 {
66+
| ^^^^^^^^^^^^^
67+
|
68+
note: same as this
69+
--> $DIR/ifs_same_cond_fn.rs:54:8
70+
|
71+
LL | if v.len() == 42 {
72+
| ^^^^^^^^^^^^^
73+
74+
error: aborting due to 6 previous errors
75+

0 commit comments

Comments
 (0)