]> git.lizzy.rs Git - rust.git/blob - src/len_zero.rs
Merge pull request #938 from Manishearth/fix-876
[rust.git] / src / len_zero.rs
1 use rustc::lint::*;
2 use rustc::hir::def_id::DefId;
3 use rustc::ty::{self, MethodTraitItemId, ImplOrTraitItemId};
4 use rustc::hir::*;
5 use syntax::ast::{Lit, LitKind, Name};
6 use syntax::codemap::{Span, Spanned};
7 use syntax::ptr::P;
8 use utils::{get_item_name, in_macro, snippet, span_lint, span_lint_and_then, walk_ptrs_ty};
9
10 /// **What it does:** This lint checks for getting the length of something via `.len()` just to compare to zero, and suggests using `.is_empty()` where applicable.
11 ///
12 /// **Why is this bad?** Some structures can answer `.is_empty()` much faster than calculating their length. So it is good to get into the habit of using `.is_empty()`, and having it is cheap. Besides, it makes the intent clearer than a comparison.
13 ///
14 /// **Known problems:** None
15 ///
16 /// **Example:** `if x.len() == 0 { .. }`
17 declare_lint! {
18     pub LEN_ZERO, Warn,
19     "checking `.len() == 0` or `.len() > 0` (or similar) when `.is_empty()` \
20      could be used instead"
21 }
22
23 /// **What it does:** This lint checks for items that implement `.len()` but not `.is_empty()`.
24 ///
25 /// **Why is this bad?** It is good custom to have both methods, because for some data structures, asking about the length will be a costly operation, whereas `.is_empty()` can usually answer in constant time. Also it used to lead to false positives on the [`len_zero`](#len_zero) lint – currently that lint will ignore such entities.
26 ///
27 /// **Known problems:** None
28 ///
29 /// **Example:**
30 /// ```
31 /// impl X {
32 ///     fn len(&self) -> usize { .. }
33 /// }
34 /// ```
35 declare_lint! {
36     pub LEN_WITHOUT_IS_EMPTY, Warn,
37     "traits and impls that have `.len()` but not `.is_empty()`"
38 }
39
40 #[derive(Copy,Clone)]
41 pub struct LenZero;
42
43 impl LintPass for LenZero {
44     fn get_lints(&self) -> LintArray {
45         lint_array!(LEN_ZERO, LEN_WITHOUT_IS_EMPTY)
46     }
47 }
48
49 impl LateLintPass for LenZero {
50     fn check_item(&mut self, cx: &LateContext, item: &Item) {
51         if in_macro(cx, item.span) {
52             return;
53         }
54
55         match item.node {
56             ItemTrait(_, _, _, ref trait_items) => check_trait_items(cx, item, trait_items),
57             ItemImpl(_, _, _, None, _, ref impl_items) => check_impl_items(cx, item, impl_items),
58             _ => (),
59         }
60     }
61
62     fn check_expr(&mut self, cx: &LateContext, expr: &Expr) {
63         if in_macro(cx, expr.span) {
64             return;
65         }
66
67         if let ExprBinary(Spanned { node: cmp, .. }, ref left, ref right) = expr.node {
68             match cmp {
69                 BiEq => check_cmp(cx, expr.span, left, right, ""),
70                 BiGt | BiNe => check_cmp(cx, expr.span, left, right, "!"),
71                 _ => (),
72             }
73         }
74     }
75 }
76
77 fn check_trait_items(cx: &LateContext, item: &Item, trait_items: &[TraitItem]) {
78     fn is_named_self(item: &TraitItem, name: &str) -> bool {
79         item.name.as_str() == name &&
80         if let MethodTraitItem(ref sig, _) = item.node {
81             is_self_sig(sig)
82         } else {
83             false
84         }
85     }
86
87     if !trait_items.iter().any(|i| is_named_self(i, "is_empty")) {
88         for i in trait_items {
89             if is_named_self(i, "len") {
90                 span_lint(cx,
91                           LEN_WITHOUT_IS_EMPTY,
92                           i.span,
93                           &format!("trait `{}` has a `.len(_: &Self)` method, but no `.is_empty(_: &Self)` method. \
94                                     Consider adding one",
95                                    item.name));
96             }
97         }
98     }
99 }
100
101 fn check_impl_items(cx: &LateContext, item: &Item, impl_items: &[ImplItem]) {
102     fn is_named_self(item: &ImplItem, name: &str) -> bool {
103         item.name.as_str() == name &&
104         if let ImplItemKind::Method(ref sig, _) = item.node {
105             is_self_sig(sig)
106         } else {
107             false
108         }
109     }
110
111     if !impl_items.iter().any(|i| is_named_self(i, "is_empty")) {
112         for i in impl_items {
113             if is_named_self(i, "len") {
114                 let ty = cx.tcx.node_id_to_type(item.id);
115
116                 span_lint(cx,
117                           LEN_WITHOUT_IS_EMPTY,
118                           i.span,
119                           &format!("item `{}` has a `.len(_: &Self)` method, but no `.is_empty(_: &Self)` method. \
120                                     Consider adding one",
121                                    ty));
122                 return;
123             }
124         }
125     }
126 }
127
128 fn is_self_sig(sig: &MethodSig) -> bool {
129     if sig.decl.has_self() {
130         sig.decl.inputs.len() == 1
131     } else {
132         false
133     }
134 }
135
136 fn check_cmp(cx: &LateContext, span: Span, left: &Expr, right: &Expr, op: &str) {
137     // check if we are in an is_empty() method
138     if let Some(name) = get_item_name(cx, left) {
139         if name.as_str() == "is_empty" {
140             return;
141         }
142     }
143     match (&left.node, &right.node) {
144         (&ExprLit(ref lit), &ExprMethodCall(ref method, _, ref args)) |
145         (&ExprMethodCall(ref method, _, ref args), &ExprLit(ref lit)) => {
146             check_len_zero(cx, span, &method.node, args, lit, op)
147         }
148         _ => (),
149     }
150 }
151
152 fn check_len_zero(cx: &LateContext, span: Span, name: &Name, args: &[P<Expr>], lit: &Lit, op: &str) {
153     if let Spanned { node: LitKind::Int(0, _), .. } = *lit {
154         if name.as_str() == "len" && args.len() == 1 && has_is_empty(cx, &args[0]) {
155             span_lint_and_then(cx, LEN_ZERO, span, "length comparison to zero", |db| {
156                 db.span_suggestion(span,
157                                    "consider using `is_empty`",
158                                    format!("{}{}.is_empty()", op, snippet(cx, args[0].span, "_")));
159             });
160         }
161     }
162 }
163
164 /// Check if this type has an `is_empty` method.
165 fn has_is_empty(cx: &LateContext, expr: &Expr) -> bool {
166     /// Get an `ImplOrTraitItem` and return true if it matches `is_empty(self)`.
167     fn is_is_empty(cx: &LateContext, id: &ImplOrTraitItemId) -> bool {
168         if let MethodTraitItemId(def_id) = *id {
169             if let ty::MethodTraitItem(ref method) = cx.tcx.impl_or_trait_item(def_id) {
170                 method.name.as_str() == "is_empty" && method.fty.sig.skip_binder().inputs.len() == 1
171             } else {
172                 false
173             }
174         } else {
175             false
176         }
177     }
178
179     /// Check the inherent impl's items for an `is_empty(self)` method.
180     fn has_is_empty_impl(cx: &LateContext, id: &DefId) -> bool {
181         let impl_items = cx.tcx.impl_items.borrow();
182         cx.tcx.inherent_impls.borrow().get(id).map_or(false, |ids| {
183             ids.iter().any(|iid| impl_items.get(iid).map_or(false, |iids| iids.iter().any(|i| is_is_empty(cx, i))))
184         })
185     }
186
187     let ty = &walk_ptrs_ty(cx.tcx.expr_ty(expr));
188     match ty.sty {
189         ty::TyTrait(_) => {
190             cx.tcx
191               .trait_item_def_ids
192               .borrow()
193               .get(&ty.ty_to_def_id().expect("trait impl not found"))
194               .map_or(false, |ids| ids.iter().any(|i| is_is_empty(cx, i)))
195         }
196         ty::TyProjection(_) => ty.ty_to_def_id().map_or(false, |id| has_is_empty_impl(cx, &id)),
197         ty::TyEnum(ref id, _) |
198         ty::TyStruct(ref id, _) => has_is_empty_impl(cx, &id.did),
199         ty::TyArray(..) | ty::TyStr => true,
200         _ => false,
201     }
202 }