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