Skip to content

Commit 4d30b08

Browse files
committed
Auto merge of #4569 - james9909:add-comparison-chain, r=oli-obk
Add a new lint for comparison chains changelog: Adds a new lint: `comparison_chain`. `comparison_chain` lints all `if` conditional chains where all the conditions are binary comparisons on the same two operands and will suggest a rewrite with `match`. Closes #4531.
2 parents bc1b04b + 2f34576 commit 4d30b08

13 files changed

+362
-107
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -962,6 +962,7 @@ Released 2018-09-13
962962
[`cmp_owned`]: https://rust-lang.github.io/rust-clippy/master/index.html#cmp_owned
963963
[`cognitive_complexity`]: https://rust-lang.github.io/rust-clippy/master/index.html#cognitive_complexity
964964
[`collapsible_if`]: https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_if
965+
[`comparison_chain`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_chain
965966
[`copy_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#copy_iterator
966967
[`crosspointer_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#crosspointer_transmute
967968
[`dbg_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#dbg_macro

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 316 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
9+
[There are 317 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/comparison_chain.rs

+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
use crate::utils::{if_sequence, parent_node_is_if_expr, span_help_and_lint, SpanlessEq};
2+
use rustc::hir::*;
3+
use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
4+
use rustc::{declare_lint_pass, declare_tool_lint};
5+
6+
declare_clippy_lint! {
7+
/// **What it does:** Checks comparison chains written with `if` that can be
8+
/// rewritten with `match` and `cmp`.
9+
///
10+
/// **Why is this bad?** `if` is not guaranteed to be exhaustive and conditionals can get
11+
/// repetitive
12+
///
13+
/// **Known problems:** None.
14+
///
15+
/// **Example:**
16+
/// ```rust,ignore
17+
/// # fn a() {}
18+
/// # fn b() {}
19+
/// # fn c() {}
20+
/// fn f(x: u8, y: u8) {
21+
/// if x > y {
22+
/// a()
23+
/// } else if x < y {
24+
/// b()
25+
/// } else {
26+
/// c()
27+
/// }
28+
/// }
29+
/// ```
30+
///
31+
/// Could be written:
32+
///
33+
/// ```rust,ignore
34+
/// use std::cmp::Ordering;
35+
/// # fn a() {}
36+
/// # fn b() {}
37+
/// # fn c() {}
38+
/// fn f(x: u8, y: u8) {
39+
/// match x.cmp(&y) {
40+
/// Ordering::Greater => a(),
41+
/// Ordering::Less => b(),
42+
/// Ordering::Equal => c()
43+
/// }
44+
/// }
45+
/// ```
46+
pub COMPARISON_CHAIN,
47+
style,
48+
"`if`s that can be rewritten with `match` and `cmp`"
49+
}
50+
51+
declare_lint_pass!(ComparisonChain => [COMPARISON_CHAIN]);
52+
53+
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ComparisonChain {
54+
fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) {
55+
if expr.span.from_expansion() {
56+
return;
57+
}
58+
59+
// We only care about the top-most `if` in the chain
60+
if parent_node_is_if_expr(expr, cx) {
61+
return;
62+
}
63+
64+
// Check that there exists at least one explicit else condition
65+
let (conds, _) = if_sequence(expr);
66+
if conds.len() < 2 {
67+
return;
68+
}
69+
70+
for cond in conds.windows(2) {
71+
if let (
72+
&ExprKind::Binary(ref kind1, ref lhs1, ref rhs1),
73+
&ExprKind::Binary(ref kind2, ref lhs2, ref rhs2),
74+
) = (&cond[0].node, &cond[1].node)
75+
{
76+
if !kind_is_cmp(kind1.node) || !kind_is_cmp(kind2.node) {
77+
return;
78+
}
79+
80+
// Check that both sets of operands are equal
81+
let mut spanless_eq = SpanlessEq::new(cx);
82+
if (!spanless_eq.eq_expr(lhs1, lhs2) || !spanless_eq.eq_expr(rhs1, rhs2))
83+
&& (!spanless_eq.eq_expr(lhs1, rhs2) || !spanless_eq.eq_expr(rhs1, lhs2))
84+
{
85+
return;
86+
}
87+
} else {
88+
// We only care about comparison chains
89+
return;
90+
}
91+
}
92+
span_help_and_lint(
93+
cx,
94+
COMPARISON_CHAIN,
95+
expr.span,
96+
"`if` chain can be rewritten with `match`",
97+
"Consider rewriting the `if` chain to use `cmp` and `match`.",
98+
)
99+
}
100+
}
101+
102+
fn kind_is_cmp(kind: BinOpKind) -> bool {
103+
match kind {
104+
BinOpKind::Lt | BinOpKind::Gt | BinOpKind::Eq => true,
105+
_ => false,
106+
}
107+
}

clippy_lints/src/copies.rs

+4-43
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
use crate::utils::{get_parent_expr, higher, same_tys, snippet, span_lint_and_then, span_note_and_lint};
1+
use crate::utils::{get_parent_expr, higher, if_sequence, same_tys, snippet, span_lint_and_then, span_note_and_lint};
22
use crate::utils::{SpanlessEq, SpanlessHash};
33
use rustc::hir::*;
44
use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
55
use rustc::ty::Ty;
66
use rustc::{declare_lint_pass, declare_tool_lint};
77
use rustc_data_structures::fx::FxHashMap;
8-
use smallvec::SmallVec;
8+
use std::cmp::Ordering;
99
use std::collections::hash_map::Entry;
1010
use std::hash::BuildHasherDefault;
1111
use syntax::symbol::Symbol;
@@ -236,39 +236,6 @@ fn lint_match_arms<'tcx>(cx: &LateContext<'_, 'tcx>, expr: &Expr) {
236236
}
237237
}
238238

239-
/// Returns the list of condition expressions and the list of blocks in a
240-
/// sequence of `if/else`.
241-
/// E.g., this returns `([a, b], [c, d, e])` for the expression
242-
/// `if a { c } else if b { d } else { e }`.
243-
fn if_sequence(mut expr: &Expr) -> (SmallVec<[&Expr; 1]>, SmallVec<[&Block; 1]>) {
244-
let mut conds = SmallVec::new();
245-
let mut blocks: SmallVec<[&Block; 1]> = SmallVec::new();
246-
247-
while let Some((ref cond, ref then_expr, ref else_expr)) = higher::if_block(&expr) {
248-
conds.push(&**cond);
249-
if let ExprKind::Block(ref block, _) = then_expr.node {
250-
blocks.push(block);
251-
} else {
252-
panic!("ExprKind::If node is not an ExprKind::Block");
253-
}
254-
255-
if let Some(ref else_expr) = *else_expr {
256-
expr = else_expr;
257-
} else {
258-
break;
259-
}
260-
}
261-
262-
// final `else {..}`
263-
if !blocks.is_empty() {
264-
if let ExprKind::Block(ref block, _) = expr.node {
265-
blocks.push(&**block);
266-
}
267-
}
268-
269-
(conds, blocks)
270-
}
271-
272239
/// Returns the list of bindings in a pattern.
273240
fn bindings<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, pat: &Pat) -> FxHashMap<Symbol, Ty<'tcx>> {
274241
fn bindings_impl<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, pat: &Pat, map: &mut FxHashMap<Symbol, Ty<'tcx>>) {
@@ -333,14 +300,8 @@ fn search_common_cases<'a, T, Eq>(exprs: &'a [T], eq: &Eq) -> Option<(&'a T, &'a
333300
where
334301
Eq: Fn(&T, &T) -> bool,
335302
{
336-
if exprs.len() < 2 {
337-
None
338-
} else if exprs.len() == 2 {
339-
if eq(&exprs[0], &exprs[1]) {
340-
Some((&exprs[0], &exprs[1]))
341-
} else {
342-
None
343-
}
303+
if exprs.len() == 2 && eq(&exprs[0], &exprs[1]) {
304+
Some((&exprs[0], &exprs[1]))
344305
} else {
345306
None
346307
}

clippy_lints/src/lib.rs

+4
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ pub mod cargo_common_metadata;
159159
pub mod checked_conversions;
160160
pub mod cognitive_complexity;
161161
pub mod collapsible_if;
162+
pub mod comparison_chain;
162163
pub mod copies;
163164
pub mod copy_iterator;
164165
pub mod dbg_macro;
@@ -600,6 +601,7 @@ pub fn register_plugins(reg: &mut rustc_driver::plugin::Registry<'_>, conf: &Con
600601
reg.register_late_lint_pass(box integer_division::IntegerDivision);
601602
reg.register_late_lint_pass(box inherent_to_string::InherentToString);
602603
reg.register_late_lint_pass(box trait_bounds::TraitBounds);
604+
reg.register_late_lint_pass(box comparison_chain::ComparisonChain);
603605

604606
reg.register_lint_group("clippy::restriction", Some("clippy_restriction"), vec![
605607
arithmetic::FLOAT_ARITHMETIC,
@@ -706,6 +708,7 @@ pub fn register_plugins(reg: &mut rustc_driver::plugin::Registry<'_>, conf: &Con
706708
bytecount::NAIVE_BYTECOUNT,
707709
cognitive_complexity::COGNITIVE_COMPLEXITY,
708710
collapsible_if::COLLAPSIBLE_IF,
711+
comparison_chain::COMPARISON_CHAIN,
709712
copies::IFS_SAME_COND,
710713
copies::IF_SAME_THEN_ELSE,
711714
derive::DERIVE_HASH_XOR_EQ,
@@ -932,6 +935,7 @@ pub fn register_plugins(reg: &mut rustc_driver::plugin::Registry<'_>, conf: &Con
932935
block_in_if_condition::BLOCK_IN_IF_CONDITION_EXPR,
933936
block_in_if_condition::BLOCK_IN_IF_CONDITION_STMT,
934937
collapsible_if::COLLAPSIBLE_IF,
938+
comparison_chain::COMPARISON_CHAIN,
935939
doc::MISSING_SAFETY_DOC,
936940
enum_variants::ENUM_VARIANT_NAMES,
937941
enum_variants::MODULE_INCEPTION,

clippy_lints/src/needless_bool.rs

+1-12
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
//! This lint is **warn** by default
44
55
use crate::utils::sugg::Sugg;
6-
use crate::utils::{higher, span_lint, span_lint_and_sugg};
6+
use crate::utils::{higher, parent_node_is_if_expr, span_lint, span_lint_and_sugg};
77
use rustc::hir::*;
88
use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
99
use rustc::{declare_lint_pass, declare_tool_lint};
@@ -118,17 +118,6 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for NeedlessBool {
118118
}
119119
}
120120

121-
fn parent_node_is_if_expr<'a, 'b>(expr: &Expr, cx: &LateContext<'a, 'b>) -> bool {
122-
let parent_id = cx.tcx.hir().get_parent_node(expr.hir_id);
123-
let parent_node = cx.tcx.hir().get(parent_id);
124-
125-
match parent_node {
126-
rustc::hir::Node::Expr(e) => higher::if_block(&e).is_some(),
127-
rustc::hir::Node::Arm(e) => higher::if_block(&e.body).is_some(),
128-
_ => false,
129-
}
130-
}
131-
132121
declare_lint_pass!(BoolComparison => [BOOL_COMPARISON]);
133122

134123
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for BoolComparison {

clippy_lints/src/non_expressive_names.rs

+54-49
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::utils::{span_lint, span_lint_and_then};
22
use rustc::lint::{EarlyContext, EarlyLintPass, LintArray, LintPass};
33
use rustc::{declare_tool_lint, impl_lint_pass};
4+
use std::cmp::Ordering;
45
use syntax::ast::*;
56
use syntax::attr;
67
use syntax::source_map::Span;
@@ -206,63 +207,67 @@ impl<'a, 'tcx, 'b> SimilarNamesNameVisitor<'a, 'tcx, 'b> {
206207
continue;
207208
}
208209
let mut split_at = None;
209-
if existing_name.len > count {
210-
if existing_name.len - count != 1 || levenstein_not_1(&interned_name, &existing_name.interned) {
211-
continue;
212-
}
213-
} else if existing_name.len < count {
214-
if count - existing_name.len != 1 || levenstein_not_1(&existing_name.interned, &interned_name) {
215-
continue;
216-
}
217-
} else {
218-
let mut interned_chars = interned_name.chars();
219-
let mut existing_chars = existing_name.interned.chars();
220-
let first_i = interned_chars.next().expect("we know we have at least one char");
221-
let first_e = existing_chars.next().expect("we know we have at least one char");
222-
let eq_or_numeric = |(a, b): (char, char)| a == b || a.is_numeric() && b.is_numeric();
210+
match existing_name.len.cmp(&count) {
211+
Ordering::Greater => {
212+
if existing_name.len - count != 1 || levenstein_not_1(&interned_name, &existing_name.interned) {
213+
continue;
214+
}
215+
},
216+
Ordering::Less => {
217+
if count - existing_name.len != 1 || levenstein_not_1(&existing_name.interned, &interned_name) {
218+
continue;
219+
}
220+
},
221+
Ordering::Equal => {
222+
let mut interned_chars = interned_name.chars();
223+
let mut existing_chars = existing_name.interned.chars();
224+
let first_i = interned_chars.next().expect("we know we have at least one char");
225+
let first_e = existing_chars.next().expect("we know we have at least one char");
226+
let eq_or_numeric = |(a, b): (char, char)| a == b || a.is_numeric() && b.is_numeric();
223227

224-
if eq_or_numeric((first_i, first_e)) {
225-
let last_i = interned_chars.next_back().expect("we know we have at least two chars");
226-
let last_e = existing_chars.next_back().expect("we know we have at least two chars");
227-
if eq_or_numeric((last_i, last_e)) {
228-
if interned_chars
229-
.zip(existing_chars)
230-
.filter(|&ie| !eq_or_numeric(ie))
231-
.count()
232-
!= 1
233-
{
234-
continue;
228+
if eq_or_numeric((first_i, first_e)) {
229+
let last_i = interned_chars.next_back().expect("we know we have at least two chars");
230+
let last_e = existing_chars.next_back().expect("we know we have at least two chars");
231+
if eq_or_numeric((last_i, last_e)) {
232+
if interned_chars
233+
.zip(existing_chars)
234+
.filter(|&ie| !eq_or_numeric(ie))
235+
.count()
236+
!= 1
237+
{
238+
continue;
239+
}
240+
} else {
241+
let second_last_i = interned_chars
242+
.next_back()
243+
.expect("we know we have at least three chars");
244+
let second_last_e = existing_chars
245+
.next_back()
246+
.expect("we know we have at least three chars");
247+
if !eq_or_numeric((second_last_i, second_last_e))
248+
|| second_last_i == '_'
249+
|| !interned_chars.zip(existing_chars).all(eq_or_numeric)
250+
{
251+
// allowed similarity foo_x, foo_y
252+
// or too many chars differ (foo_x, boo_y) or (foox, booy)
253+
continue;
254+
}
255+
split_at = interned_name.char_indices().rev().next().map(|(i, _)| i);
235256
}
236257
} else {
237-
let second_last_i = interned_chars
238-
.next_back()
239-
.expect("we know we have at least three chars");
240-
let second_last_e = existing_chars
241-
.next_back()
242-
.expect("we know we have at least three chars");
243-
if !eq_or_numeric((second_last_i, second_last_e))
244-
|| second_last_i == '_'
258+
let second_i = interned_chars.next().expect("we know we have at least two chars");
259+
let second_e = existing_chars.next().expect("we know we have at least two chars");
260+
if !eq_or_numeric((second_i, second_e))
261+
|| second_i == '_'
245262
|| !interned_chars.zip(existing_chars).all(eq_or_numeric)
246263
{
247-
// allowed similarity foo_x, foo_y
248-
// or too many chars differ (foo_x, boo_y) or (foox, booy)
264+
// allowed similarity x_foo, y_foo
265+
// or too many chars differ (x_foo, y_boo) or (xfoo, yboo)
249266
continue;
250267
}
251-
split_at = interned_name.char_indices().rev().next().map(|(i, _)| i);
268+
split_at = interned_name.chars().next().map(char::len_utf8);
252269
}
253-
} else {
254-
let second_i = interned_chars.next().expect("we know we have at least two chars");
255-
let second_e = existing_chars.next().expect("we know we have at least two chars");
256-
if !eq_or_numeric((second_i, second_e))
257-
|| second_i == '_'
258-
|| !interned_chars.zip(existing_chars).all(eq_or_numeric)
259-
{
260-
// allowed similarity x_foo, y_foo
261-
// or too many chars differ (x_foo, y_boo) or (xfoo, yboo)
262-
continue;
263-
}
264-
split_at = interned_name.chars().next().map(char::len_utf8);
265-
}
270+
},
266271
}
267272
span_lint_and_then(
268273
self.0.cx,

0 commit comments

Comments
 (0)