]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/len_zero.rs
Merge pull request #2821 from mati865/rust-2018-migration
[rust.git] / clippy_lints / src / len_zero.rs
1 use rustc::hir::def_id::DefId;
2 use rustc::hir::*;
3 use rustc::lint::*;
4 use rustc::ty;
5 use std::collections::HashSet;
6 use syntax::ast::{Lit, LitKind, Name};
7 use syntax::codemap::{Span, Spanned};
8 use crate::utils::{get_item_name, in_macro, snippet, span_lint, span_lint_and_sugg, walk_ptrs_ty};
9
10 /// **What it does:** Checks for getting the length of something via `.len()`
11 /// just to compare to zero, and suggests using `.is_empty()` where applicable.
12 ///
13 /// **Why is this bad?** Some structures can answer `.is_empty()` much faster
14 /// than calculating their length. Notably, for slices, getting the length
15 /// requires a subtraction whereas `.is_empty()` is just a comparison. So it is
16 /// good to get into the habit of using `.is_empty()`, and having it is cheap.
17 /// Besides, it makes the intent clearer than a manual comparison.
18 ///
19 /// **Known problems:** None.
20 ///
21 /// **Example:**
22 /// ```rust
23 /// if x.len() == 0 { .. }
24 /// ```
25 declare_clippy_lint! {
26     pub LEN_ZERO,
27     style,
28     "checking `.len() == 0` or `.len() > 0` (or similar) when `.is_empty()` \
29      could be used instead"
30 }
31
32 /// **What it does:** Checks for items that implement `.len()` but not
33 /// `.is_empty()`.
34 ///
35 /// **Why is this bad?** It is good custom to have both methods, because for
36 /// some data structures, asking about the length will be a costly operation,
37 /// whereas `.is_empty()` can usually answer in constant time. Also it used to
38 /// lead to false positives on the [`len_zero`](#len_zero) lint – currently that
39 /// lint will ignore such entities.
40 ///
41 /// **Known problems:** None.
42 ///
43 /// **Example:**
44 /// ```rust
45 /// impl X {
46 ///     pub fn len(&self) -> usize { .. }
47 /// }
48 /// ```
49 declare_clippy_lint! {
50     pub LEN_WITHOUT_IS_EMPTY,
51     style,
52     "traits or impls with a public `len` method but no corresponding `is_empty` method"
53 }
54
55 #[derive(Copy, Clone)]
56 pub struct LenZero;
57
58 impl LintPass for LenZero {
59     fn get_lints(&self) -> LintArray {
60         lint_array!(LEN_ZERO, LEN_WITHOUT_IS_EMPTY)
61     }
62 }
63
64 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for LenZero {
65     fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx Item) {
66         if in_macro(item.span) {
67             return;
68         }
69
70         match item.node {
71             ItemTrait(_, _, _, _, ref trait_items) => check_trait_items(cx, item, trait_items),
72             ItemImpl(_, _, _, _, None, _, ref impl_items) => check_impl_items(cx, item, impl_items),
73             _ => (),
74         }
75     }
76
77     fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) {
78         if in_macro(expr.span) {
79             return;
80         }
81
82         if let ExprBinary(Spanned { node: cmp, .. }, ref left, ref right) = expr.node {
83             match cmp {
84                 BiEq => {
85                     check_cmp(cx, expr.span, left, right, "", 0); // len == 0
86                     check_cmp(cx, expr.span, right, left, "", 0); // 0 == len
87                 },
88                 BiNe => {
89                     check_cmp(cx, expr.span, left, right, "!", 0); // len != 0
90                     check_cmp(cx, expr.span, right, left, "!", 0); // 0 != len
91                 },
92                 BiGt => {
93                     check_cmp(cx, expr.span, left, right, "!", 0); // len > 0
94                     check_cmp(cx, expr.span, right, left, "", 1); // 1 > len
95                 },
96                 BiLt => {
97                     check_cmp(cx, expr.span, left, right, "", 1); // len < 1
98                     check_cmp(cx, expr.span, right, left, "!", 0); // 0 < len
99                 },
100                 BiGe => check_cmp(cx, expr.span, left, right, "!", 1), // len <= 1
101                 BiLe => check_cmp(cx, expr.span, right, left, "!", 1), // 1 >= len
102                 _ => (),
103             }
104         }
105     }
106 }
107
108 fn check_trait_items(cx: &LateContext, visited_trait: &Item, trait_items: &[TraitItemRef]) {
109     fn is_named_self(cx: &LateContext, item: &TraitItemRef, name: &str) -> bool {
110         item.name == name && if let AssociatedItemKind::Method { has_self } = item.kind {
111             has_self && {
112                 let did = cx.tcx.hir.local_def_id(item.id.node_id);
113                 cx.tcx.fn_sig(did).inputs().skip_binder().len() == 1
114             }
115         } else {
116             false
117         }
118     }
119
120     // fill the set with current and super traits
121     fn fill_trait_set(traitt: DefId, set: &mut HashSet<DefId>, cx: &LateContext) {
122         if set.insert(traitt) {
123             for supertrait in ::rustc::traits::supertrait_def_ids(cx.tcx, traitt) {
124                 fill_trait_set(supertrait, set, cx);
125             }
126         }
127     }
128
129     if cx.access_levels.is_exported(visited_trait.id) && trait_items.iter().any(|i| is_named_self(cx, i, "len")) {
130         let mut current_and_super_traits = HashSet::new();
131         let visited_trait_def_id = cx.tcx.hir.local_def_id(visited_trait.id);
132         fill_trait_set(visited_trait_def_id, &mut current_and_super_traits, cx);
133
134         let is_empty_method_found = current_and_super_traits
135             .iter()
136             .flat_map(|&i| cx.tcx.associated_items(i))
137             .any(|i| {
138                 i.kind == ty::AssociatedKind::Method && i.method_has_self_argument && i.name == "is_empty"
139                     && cx.tcx.fn_sig(i.def_id).inputs().skip_binder().len() == 1
140             });
141
142         if !is_empty_method_found {
143             span_lint(
144                 cx,
145                 LEN_WITHOUT_IS_EMPTY,
146                 visited_trait.span,
147                 &format!(
148                     "trait `{}` has a `len` method but no (possibly inherited) `is_empty` method",
149                     visited_trait.name
150                 ),
151             );
152         }
153     }
154 }
155
156 fn check_impl_items(cx: &LateContext, item: &Item, impl_items: &[ImplItemRef]) {
157     fn is_named_self(cx: &LateContext, item: &ImplItemRef, name: &str) -> bool {
158         item.name == name && if let AssociatedItemKind::Method { has_self } = item.kind {
159             has_self && {
160                 let did = cx.tcx.hir.local_def_id(item.id.node_id);
161                 cx.tcx.fn_sig(did).inputs().skip_binder().len() == 1
162             }
163         } else {
164             false
165         }
166     }
167
168     let is_empty = if let Some(is_empty) = impl_items.iter().find(|i| is_named_self(cx, i, "is_empty")) {
169         if cx.access_levels.is_exported(is_empty.id.node_id) {
170             return;
171         } else {
172             "a private"
173         }
174     } else {
175         "no corresponding"
176     };
177
178     if let Some(i) = impl_items.iter().find(|i| is_named_self(cx, i, "len")) {
179         if cx.access_levels.is_exported(i.id.node_id) {
180             let def_id = cx.tcx.hir.local_def_id(item.id);
181             let ty = cx.tcx.type_of(def_id);
182
183             span_lint(
184                 cx,
185                 LEN_WITHOUT_IS_EMPTY,
186                 item.span,
187                 &format!(
188                     "item `{}` has a public `len` method but {} `is_empty` method",
189                     ty, is_empty
190                 ),
191             );
192         }
193     }
194 }
195
196 fn check_cmp(cx: &LateContext, span: Span, method: &Expr, lit: &Expr, op: &str, compare_to: u32) {
197     if let (&ExprMethodCall(ref method_path, _, ref args), &ExprLit(ref lit)) = (&method.node, &lit.node) {
198         // check if we are in an is_empty() method
199         if let Some(name) = get_item_name(cx, method) {
200             if name == "is_empty" {
201                 return;
202             }
203         }
204
205         check_len(cx, span, method_path.name, args, lit, op, compare_to)
206     }
207 }
208
209 fn check_len(cx: &LateContext, span: Span, method_name: Name, args: &[Expr], lit: &Lit, op: &str, compare_to: u32) {
210     if let Spanned {
211         node: LitKind::Int(lit, _),
212         ..
213     } = *lit
214     {
215         // check if length is compared to the specified number
216         if lit != u128::from(compare_to) {
217             return;
218         }
219
220         if method_name == "len" && args.len() == 1 && has_is_empty(cx, &args[0]) {
221             span_lint_and_sugg(
222                 cx,
223                 LEN_ZERO,
224                 span,
225                 &format!("length comparison to {}", if compare_to == 0 { "zero" } else { "one" }),
226                 "using `is_empty` is more concise",
227                 format!("{}{}.is_empty()", op, snippet(cx, args[0].span, "_")),
228             );
229         }
230     }
231 }
232
233 /// Check if this type has an `is_empty` method.
234 fn has_is_empty(cx: &LateContext, expr: &Expr) -> bool {
235     /// Get an `AssociatedItem` and return true if it matches `is_empty(self)`.
236     fn is_is_empty(cx: &LateContext, item: &ty::AssociatedItem) -> bool {
237         if let ty::AssociatedKind::Method = item.kind {
238             if item.name == "is_empty" {
239                 let sig = cx.tcx.fn_sig(item.def_id);
240                 let ty = sig.skip_binder();
241                 ty.inputs().len() == 1
242             } else {
243                 false
244             }
245         } else {
246             false
247         }
248     }
249
250     /// Check the inherent impl's items for an `is_empty(self)` method.
251     fn has_is_empty_impl(cx: &LateContext, id: DefId) -> bool {
252         cx.tcx.inherent_impls(id).iter().any(|imp| {
253             cx.tcx
254                 .associated_items(*imp)
255                 .any(|item| is_is_empty(cx, &item))
256         })
257     }
258
259     let ty = &walk_ptrs_ty(cx.tables.expr_ty(expr));
260     match ty.sty {
261         ty::TyDynamic(..) => cx.tcx
262             .associated_items(ty.ty_to_def_id().expect("trait impl not found"))
263             .any(|item| is_is_empty(cx, &item)),
264         ty::TyProjection(_) => ty.ty_to_def_id()
265             .map_or(false, |id| has_is_empty_impl(cx, id)),
266         ty::TyAdt(id, _) => has_is_empty_impl(cx, id.did),
267         ty::TyArray(..) | ty::TySlice(..) | ty::TyStr => true,
268         _ => false,
269     }
270 }