Skip to content

Commit 57a6b93

Browse files
committed
Merge pull request #62 from Manishearth/len_zero_is_empty
Here goes nothing... 😄
2 parents 4a7cd07 + 483a546 commit 57a6b93

File tree

4 files changed

+163
-0
lines changed

4 files changed

+163
-0
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ Lints included in this crate:
2323
- `redundant_closure`: Warns on usage of eta-reducible closures like `|a| foo(a)` (which can be written as just `foo`)
2424
- `identity_op`: Warns on identity operations like `x + 0` or `y / 1` (which can be reduced to `x` and `y`, respectively)
2525
- `mut_mut`: Warns on `&mut &mut` which is either a copy'n'paste error, or shows a fundamental misunderstanding of references
26+
- `len_zero`: Warns on `_.len() == 0` and suggests using `_.is_empty()` (or similar comparisons with `>` or `!=`)
27+
- `len_without_is_empty`: Warns on traits or impls that have a `.len()` but no `.is_empty()` method
2628

2729
To use, add the following lines to your Cargo.toml:
2830

src/len_zero.rs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
extern crate rustc_typeck as typeck;
2+
3+
use syntax::ptr::P;
4+
use syntax::ast::*;
5+
use rustc::lint::{Context, LintPass, LintArray, Lint};
6+
use rustc::middle::ty::{self, node_id_to_type, sty, ty_ptr, ty_rptr, mt, MethodTraitItemId};
7+
use rustc::middle::def::{DefTy, DefStruct, DefTrait};
8+
use syntax::codemap::{Span, Spanned};
9+
10+
declare_lint!(pub LEN_ZERO, Warn,
11+
"Warn on usage of double-mut refs, e.g. '&mut &mut ...'");
12+
13+
declare_lint!(pub LEN_WITHOUT_IS_EMPTY, Warn,
14+
"Warn on traits and impls that have .len() but not .is_empty()");
15+
16+
#[derive(Copy,Clone)]
17+
pub struct LenZero;
18+
19+
impl LintPass for LenZero {
20+
fn get_lints(&self) -> LintArray {
21+
lint_array!(LEN_ZERO, LEN_WITHOUT_IS_EMPTY)
22+
}
23+
24+
fn check_item(&mut self, cx: &Context, item: &Item) {
25+
match &item.node {
26+
&ItemTrait(_, _, _, ref trait_items) =>
27+
check_trait_items(cx, item, trait_items),
28+
&ItemImpl(_, _, _, None, _, ref impl_items) => // only non-trait
29+
check_impl_items(cx, item, impl_items),
30+
_ => ()
31+
}
32+
}
33+
34+
fn check_expr(&mut self, cx: &Context, expr: &Expr) {
35+
if let &ExprBinary(Spanned{node: cmp, ..}, ref left, ref right) =
36+
&expr.node {
37+
match cmp {
38+
BiEq => check_cmp(cx, expr.span, left, right, ""),
39+
BiGt | BiNe => check_cmp(cx, expr.span, left, right, "!"),
40+
_ => ()
41+
}
42+
}
43+
}
44+
}
45+
46+
fn check_trait_items(cx: &Context, item: &Item, trait_items: &[P<TraitItem>]) {
47+
fn is_named_self(item: &TraitItem, name: &str) -> bool {
48+
item.ident.as_str() == name && item.attrs.len() == 0
49+
}
50+
51+
if !trait_items.iter().any(|i| is_named_self(i, "is_empty")) {
52+
//cx.span_lint(LEN_WITHOUT_IS_EMPTY, item.span, &format!("trait {}", item.ident.as_str()));
53+
for i in trait_items {
54+
if is_named_self(i, "len") {
55+
cx.span_lint(LEN_WITHOUT_IS_EMPTY, i.span,
56+
&format!("Trait '{}' has a '.len()' method, but no \
57+
'.is_empty()' method. Consider adding one.",
58+
item.ident.as_str()));
59+
}
60+
};
61+
}
62+
}
63+
64+
fn check_impl_items(cx: &Context, item: &Item, impl_items: &[P<ImplItem>]) {
65+
fn is_named_self(item: &ImplItem, name: &str) -> bool {
66+
item.ident.as_str() == name && item.attrs.len() == 0
67+
}
68+
69+
if !impl_items.iter().any(|i| is_named_self(i, "is_empty")) {
70+
for i in impl_items {
71+
if is_named_self(i, "len") {
72+
cx.span_lint(LEN_WITHOUT_IS_EMPTY, i.span,
73+
&format!("Item '{}' has a '.len()' method, but no \
74+
'.is_empty()' method. Consider adding one.",
75+
item.ident.as_str()));
76+
return;
77+
}
78+
}
79+
}
80+
}
81+
82+
fn check_cmp(cx: &Context, span: Span, left: &Expr, right: &Expr, empty: &str) {
83+
match (&left.node, &right.node) {
84+
(&ExprLit(ref lit), &ExprMethodCall(ref method, _, ref args)) =>
85+
check_len_zero(cx, span, method, args, lit, empty),
86+
(&ExprMethodCall(ref method, _, ref args), &ExprLit(ref lit)) =>
87+
check_len_zero(cx, span, method, args, lit, empty),
88+
_ => ()
89+
}
90+
}
91+
92+
fn check_len_zero(cx: &Context, span: Span, method: &SpannedIdent,
93+
args: &[P<Expr>], lit: &Lit, empty: &str) {
94+
if let &Spanned{node: LitInt(0, _), ..} = lit {
95+
if method.node.as_str() == "len" && args.len() == 1 {
96+
cx.span_lint(LEN_ZERO, span, &format!(
97+
"Consider replacing the len comparison with '{}_.is_empty()' if available",
98+
empty))
99+
}
100+
}
101+
}

src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ pub mod approx_const;
2424
pub mod eta_reduction;
2525
pub mod identity_op;
2626
pub mod mut_mut;
27+
pub mod len_zero;
2728

2829
#[plugin_registrar]
2930
pub fn plugin_registrar(reg: &mut Registry) {
@@ -42,6 +43,7 @@ pub fn plugin_registrar(reg: &mut Registry) {
4243
reg.register_lint_pass(box eta_reduction::EtaPass as LintPassObject);
4344
reg.register_lint_pass(box identity_op::IdentityOp as LintPassObject);
4445
reg.register_lint_pass(box mut_mut::MutMut as LintPassObject);
46+
reg.register_lint_pass(box len_zero::LenZero as LintPassObject);
4547

4648
reg.register_lint_group("clippy", vec![types::BOX_VEC, types::LINKEDLIST,
4749
misc::SINGLE_MATCH, misc::STR_TO_STRING,
@@ -56,5 +58,7 @@ pub fn plugin_registrar(reg: &mut Registry) {
5658
eta_reduction::REDUNDANT_CLOSURE,
5759
identity_op::IDENTITY_OP,
5860
mut_mut::MUT_MUT,
61+
len_zero::LEN_ZERO,
62+
len_zero::LEN_WITHOUT_IS_EMPTY,
5963
]);
6064
}

tests/compile-fail/len_zero.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#![feature(plugin)]
2+
#![plugin(clippy)]
3+
4+
struct One;
5+
6+
#[deny(len_without_is_empty)]
7+
impl One {
8+
fn len(self: &Self) -> isize { //~ERROR Item 'One' has a '.len()' method
9+
1
10+
}
11+
}
12+
13+
#[deny(len_without_is_empty)]
14+
trait TraitsToo {
15+
fn len(self: &Self) -> isize; //~ERROR Trait 'TraitsToo' has a '.len()' method,
16+
}
17+
18+
impl TraitsToo for One {
19+
fn len(self: &Self) -> isize {
20+
0
21+
}
22+
}
23+
24+
#[allow(dead_code)]
25+
struct HasIsEmpty;
26+
27+
#[deny(len_without_is_empty)]
28+
#[allow(dead_code)]
29+
impl HasIsEmpty {
30+
fn len(self: &Self) -> isize {
31+
1
32+
}
33+
34+
fn is_empty() -> bool {
35+
false
36+
}
37+
}
38+
39+
#[deny(len_zero)]
40+
fn main() {
41+
let x = [1, 2];
42+
if x.len() == 0 { //~ERROR Consider replacing the len comparison
43+
println!("This should not happen!");
44+
}
45+
46+
let y = One;
47+
// false positives here
48+
if y.len() == 0 { //~ERROR Consider replacing the len comparison
49+
println!("This should not happen either!");
50+
}
51+
52+
let z : &TraitsToo = &y;
53+
if z.len() > 0 { //~ERROR Consider replacing the len comparison
54+
println!("Nor should this!");
55+
}
56+
}

0 commit comments

Comments
 (0)