Skip to content

Commit e1a2845

Browse files
committed
Auto merge of rust-lang#6226 - Urcra:master, r=flip1995
Add lint for comparing to empty slices instead of using .is_empty() Hey first time making a clippy lint I added the implementation of the lint the `len_zero` since it shared a lot of the code, I would otherwise have to rewrite. Just tell me if the lint should use it's own file instead changelog: Add lint for comparing to empty slices Fixes rust-lang#6217
2 parents ee9da9a + e3de544 commit e1a2845

File tree

7 files changed

+161
-1
lines changed

7 files changed

+161
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1665,6 +1665,7 @@ Released 2018-09-13
16651665
[`cognitive_complexity`]: https://rust-lang.github.io/rust-clippy/master/index.html#cognitive_complexity
16661666
[`collapsible_if`]: https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_if
16671667
[`comparison_chain`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_chain
1668+
[`comparison_to_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_to_empty
16681669
[`copy_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#copy_iterator
16691670
[`create_dir`]: https://rust-lang.github.io/rust-clippy/master/index.html#create_dir
16701671
[`crosspointer_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#crosspointer_transmute

clippy_lints/src/len_zero.rs

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,44 @@ declare_clippy_lint! {
6868
"traits or impls with a public `len` method but no corresponding `is_empty` method"
6969
}
7070

71-
declare_lint_pass!(LenZero => [LEN_ZERO, LEN_WITHOUT_IS_EMPTY]);
71+
declare_clippy_lint! {
72+
/// **What it does:** Checks for comparing to an empty slice such as "" or [],`
73+
/// and suggests using `.is_empty()` where applicable.
74+
///
75+
/// **Why is this bad?** Some structures can answer `.is_empty()` much faster
76+
/// than checking for equality. So it is good to get into the habit of using
77+
/// `.is_empty()`, and having it is cheap.
78+
/// Besides, it makes the intent clearer than a manual comparison in some contexts.
79+
///
80+
/// **Known problems:** None.
81+
///
82+
/// **Example:**
83+
///
84+
/// ```ignore
85+
/// if s == "" {
86+
/// ..
87+
/// }
88+
///
89+
/// if arr == [] {
90+
/// ..
91+
/// }
92+
/// ```
93+
/// Use instead:
94+
/// ```ignore
95+
/// if s.is_empty() {
96+
/// ..
97+
/// }
98+
///
99+
/// if arr.is_empty() {
100+
/// ..
101+
/// }
102+
/// ```
103+
pub COMPARISON_TO_EMPTY,
104+
style,
105+
"checking `x == \"\"` or `x == []` (or similar) when `.is_empty()` could be used instead"
106+
}
107+
108+
declare_lint_pass!(LenZero => [LEN_ZERO, LEN_WITHOUT_IS_EMPTY, COMPARISON_TO_EMPTY]);
72109

73110
impl<'tcx> LateLintPass<'tcx> for LenZero {
74111
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
@@ -221,6 +258,8 @@ fn check_cmp(cx: &LateContext<'_>, span: Span, method: &Expr<'_>, lit: &Expr<'_>
221258
}
222259

223260
check_len(cx, span, method_path.ident.name, args, &lit.node, op, compare_to)
261+
} else {
262+
check_empty_expr(cx, span, method, lit, op)
224263
}
225264
}
226265

@@ -258,6 +297,42 @@ fn check_len(
258297
}
259298
}
260299

300+
fn check_empty_expr(cx: &LateContext<'_>, span: Span, lit1: &Expr<'_>, lit2: &Expr<'_>, op: &str) {
301+
if (is_empty_array(lit2) || is_empty_string(lit2)) && has_is_empty(cx, lit1) {
302+
let mut applicability = Applicability::MachineApplicable;
303+
span_lint_and_sugg(
304+
cx,
305+
COMPARISON_TO_EMPTY,
306+
span,
307+
"comparison to empty slice",
308+
&format!("using `{}is_empty` is clearer and more explicit", op),
309+
format!(
310+
"{}{}.is_empty()",
311+
op,
312+
snippet_with_applicability(cx, lit1.span, "_", &mut applicability)
313+
),
314+
applicability,
315+
);
316+
}
317+
}
318+
319+
fn is_empty_string(expr: &Expr<'_>) -> bool {
320+
if let ExprKind::Lit(ref lit) = expr.kind {
321+
if let LitKind::Str(lit, _) = lit.node {
322+
let lit = lit.as_str();
323+
return lit == "";
324+
}
325+
}
326+
false
327+
}
328+
329+
fn is_empty_array(expr: &Expr<'_>) -> bool {
330+
if let ExprKind::Array(ref arr) = expr.kind {
331+
return arr.is_empty();
332+
}
333+
false
334+
}
335+
261336
/// Checks if this type has an `is_empty` method.
262337
fn has_is_empty(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
263338
/// Gets an `AssocItem` and return true if it matches `is_empty(self)`.

clippy_lints/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
614614
&large_const_arrays::LARGE_CONST_ARRAYS,
615615
&large_enum_variant::LARGE_ENUM_VARIANT,
616616
&large_stack_arrays::LARGE_STACK_ARRAYS,
617+
&len_zero::COMPARISON_TO_EMPTY,
617618
&len_zero::LEN_WITHOUT_IS_EMPTY,
618619
&len_zero::LEN_ZERO,
619620
&let_if_seq::USELESS_LET_IF_SEQ,
@@ -1365,6 +1366,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
13651366
LintId::of(&int_plus_one::INT_PLUS_ONE),
13661367
LintId::of(&large_const_arrays::LARGE_CONST_ARRAYS),
13671368
LintId::of(&large_enum_variant::LARGE_ENUM_VARIANT),
1369+
LintId::of(&len_zero::COMPARISON_TO_EMPTY),
13681370
LintId::of(&len_zero::LEN_WITHOUT_IS_EMPTY),
13691371
LintId::of(&len_zero::LEN_ZERO),
13701372
LintId::of(&let_underscore::LET_UNDERSCORE_LOCK),
@@ -1591,6 +1593,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
15911593
LintId::of(&functions::RESULT_UNIT_ERR),
15921594
LintId::of(&if_let_some_result::IF_LET_SOME_RESULT),
15931595
LintId::of(&inherent_to_string::INHERENT_TO_STRING),
1596+
LintId::of(&len_zero::COMPARISON_TO_EMPTY),
15941597
LintId::of(&len_zero::LEN_WITHOUT_IS_EMPTY),
15951598
LintId::of(&len_zero::LEN_ZERO),
15961599
LintId::of(&literal_representation::INCONSISTENT_DIGIT_GROUPING),

src/lintlist/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,13 @@ vec![
298298
deprecation: None,
299299
module: "comparison_chain",
300300
},
301+
Lint {
302+
name: "comparison_to_empty",
303+
group: "style",
304+
desc: "checking `x == \"\"` or `x == []` (or similar) when `.is_empty()` could be used instead",
305+
deprecation: None,
306+
module: "len_zero",
307+
},
301308
Lint {
302309
name: "copy_iterator",
303310
group: "pedantic",

tests/ui/comparison_to_empty.fixed

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// run-rustfix
2+
3+
#![warn(clippy::comparison_to_empty)]
4+
5+
fn main() {
6+
// Disallow comparisons to empty
7+
let s = String::new();
8+
let _ = s.is_empty();
9+
let _ = !s.is_empty();
10+
11+
let v = vec![0];
12+
let _ = v.is_empty();
13+
let _ = !v.is_empty();
14+
15+
// Allow comparisons to non-empty
16+
let s = String::new();
17+
let _ = s == " ";
18+
let _ = s != " ";
19+
20+
let v = vec![0];
21+
let _ = v == [0];
22+
let _ = v != [0];
23+
}

tests/ui/comparison_to_empty.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// run-rustfix
2+
3+
#![warn(clippy::comparison_to_empty)]
4+
5+
fn main() {
6+
// Disallow comparisons to empty
7+
let s = String::new();
8+
let _ = s == "";
9+
let _ = s != "";
10+
11+
let v = vec![0];
12+
let _ = v == [];
13+
let _ = v != [];
14+
15+
// Allow comparisons to non-empty
16+
let s = String::new();
17+
let _ = s == " ";
18+
let _ = s != " ";
19+
20+
let v = vec![0];
21+
let _ = v == [0];
22+
let _ = v != [0];
23+
}

tests/ui/comparison_to_empty.stderr

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
error: comparison to empty slice
2+
--> $DIR/comparison_to_empty.rs:8:13
3+
|
4+
LL | let _ = s == "";
5+
| ^^^^^^^ help: using `is_empty` is clearer and more explicit: `s.is_empty()`
6+
|
7+
= note: `-D clippy::comparison-to-empty` implied by `-D warnings`
8+
9+
error: comparison to empty slice
10+
--> $DIR/comparison_to_empty.rs:9:13
11+
|
12+
LL | let _ = s != "";
13+
| ^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!s.is_empty()`
14+
15+
error: comparison to empty slice
16+
--> $DIR/comparison_to_empty.rs:12:13
17+
|
18+
LL | let _ = v == [];
19+
| ^^^^^^^ help: using `is_empty` is clearer and more explicit: `v.is_empty()`
20+
21+
error: comparison to empty slice
22+
--> $DIR/comparison_to_empty.rs:13:13
23+
|
24+
LL | let _ = v != [];
25+
| ^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!v.is_empty()`
26+
27+
error: aborting due to 4 previous errors
28+

0 commit comments

Comments
 (0)