]> git.lizzy.rs Git - rust.git/blob - src/len_zero.rs
Fix issue with `DOC_MARKDOWN` and punctuation
[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                 let s = i.span;
117                 span_lint(cx,
118                           LEN_WITHOUT_IS_EMPTY,
119                           Span {
120                               lo: s.lo,
121                               hi: s.lo,
122                               expn_id: s.expn_id,
123                           },
124                           &format!("item `{}` has a `.len(_: &Self)` method, but no `.is_empty(_: &Self)` method. \
125                                     Consider adding one",
126                                    ty));
127                 return;
128             }
129         }
130     }
131 }
132
133 fn is_self_sig(sig: &MethodSig) -> bool {
134     if let SelfStatic = sig.explicit_self.node {
135         false
136     } else {
137         sig.decl.inputs.len() == 1
138     }
139 }
140
141 fn check_cmp(cx: &LateContext, span: Span, left: &Expr, right: &Expr, op: &str) {
142     // check if we are in an is_empty() method
143     if let Some(name) = get_item_name(cx, left) {
144         if name.as_str() == "is_empty" {
145             return;
146         }
147     }
148     match (&left.node, &right.node) {
149         (&ExprLit(ref lit), &ExprMethodCall(ref method, _, ref args)) |
150         (&ExprMethodCall(ref method, _, ref args), &ExprLit(ref lit)) => {
151             check_len_zero(cx, span, &method.node, args, lit, op)
152         }
153         _ => (),
154     }
155 }
156
157 fn check_len_zero(cx: &LateContext, span: Span, name: &Name, args: &[P<Expr>], lit: &Lit, op: &str) {
158     if let Spanned { node: LitKind::Int(0, _), .. } = *lit {
159         if name.as_str() == "len" && args.len() == 1 && has_is_empty(cx, &args[0]) {
160             span_lint_and_then(cx, LEN_ZERO, span, "length comparison to zero", |db| {
161                 db.span_suggestion(span,
162                                    "consider using `is_empty`",
163                                    format!("{}{}.is_empty()", op, snippet(cx, args[0].span, "_")));
164             });
165         }
166     }
167 }
168
169 /// Check if this type has an `is_empty` method.
170 fn has_is_empty(cx: &LateContext, expr: &Expr) -> bool {
171     /// Get an `ImplOrTraitItem` and return true if it matches `is_empty(self)`.
172     fn is_is_empty(cx: &LateContext, id: &ImplOrTraitItemId) -> bool {
173         if let MethodTraitItemId(def_id) = *id {
174             if let ty::MethodTraitItem(ref method) = cx.tcx.impl_or_trait_item(def_id) {
175                 method.name.as_str() == "is_empty" && method.fty.sig.skip_binder().inputs.len() == 1
176             } else {
177                 false
178             }
179         } else {
180             false
181         }
182     }
183
184     /// Check the inherent impl's items for an `is_empty(self)` method.
185     fn has_is_empty_impl(cx: &LateContext, id: &DefId) -> bool {
186         let impl_items = cx.tcx.impl_items.borrow();
187         cx.tcx.inherent_impls.borrow().get(id).map_or(false, |ids| {
188             ids.iter().any(|iid| impl_items.get(iid).map_or(false, |iids| iids.iter().any(|i| is_is_empty(cx, i))))
189         })
190     }
191
192     let ty = &walk_ptrs_ty(&cx.tcx.expr_ty(expr));
193     match ty.sty {
194         ty::TyTrait(_) => {
195             cx.tcx
196               .trait_item_def_ids
197               .borrow()
198               .get(&ty.ty_to_def_id().expect("trait impl not found"))
199               .map_or(false, |ids| ids.iter().any(|i| is_is_empty(cx, i)))
200         }
201         ty::TyProjection(_) => ty.ty_to_def_id().map_or(false, |id| has_is_empty_impl(cx, &id)),
202         ty::TyEnum(ref id, _) |
203         ty::TyStruct(ref id, _) => has_is_empty_impl(cx, &id.did),
204         ty::TyArray(..) | ty::TyStr => true,
205         _ => false,
206     }
207 }