]> git.proxmox.com Git - rustc.git/blob - src/tools/clippy/clippy_lints/src/len_zero.rs
New upstream version 1.22.1+dfsg1
[rustc.git] / src / tools / clippy / clippy_lints / src / len_zero.rs
1 use rustc::lint::*;
2 use rustc::hir::def_id::DefId;
3 use rustc::ty;
4 use rustc::hir::*;
5 use std::collections::HashSet;
6 use syntax::ast::{Lit, LitKind, Name};
7 use syntax::codemap::{Span, Spanned};
8 use 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_lint! {
26 pub LEN_ZERO,
27 Warn,
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_lint! {
50 pub LEN_WITHOUT_IS_EMPTY,
51 Warn,
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 => check_cmp(cx, expr.span, left, right, ""),
85 BiGt | BiNe => check_cmp(cx, expr.span, left, right, "!"),
86 _ => (),
87 }
88 }
89 }
90 }
91
92 fn check_trait_items(cx: &LateContext, visited_trait: &Item, trait_items: &[TraitItemRef]) {
93 fn is_named_self(cx: &LateContext, item: &TraitItemRef, name: &str) -> bool {
94 item.name == name &&
95 if let AssociatedItemKind::Method { has_self } = item.kind {
96 has_self &&
97 {
98 let did = cx.tcx.hir.local_def_id(item.id.node_id);
99 cx.tcx.fn_sig(did).inputs().skip_binder().len() == 1
100 }
101 } else {
102 false
103 }
104 }
105
106 // fill the set with current and super traits
107 fn fill_trait_set<'a, 'b: 'a>(traitt: &'b Item, set: &'a mut HashSet<&'b Item>, cx: &'b LateContext) {
108 if set.insert(traitt) {
109 if let ItemTrait(.., ref ty_param_bounds, _) = traitt.node {
110 for ty_param_bound in ty_param_bounds {
111 if let TraitTyParamBound(ref poly_trait_ref, _) = *ty_param_bound {
112 let super_trait_node_id = cx.tcx
113 .hir
114 .as_local_node_id(poly_trait_ref.trait_ref.path.def.def_id())
115 .expect("the DefId is local, the NodeId should be available");
116 let super_trait = cx.tcx.hir.expect_item(super_trait_node_id);
117 fill_trait_set(super_trait, set, cx);
118 }
119 }
120 }
121 }
122 }
123
124 if cx.access_levels.is_exported(visited_trait.id) && trait_items.iter().any(|i| is_named_self(cx, i, "len")) {
125 let mut current_and_super_traits = HashSet::new();
126 fill_trait_set(visited_trait, &mut current_and_super_traits, cx);
127
128 let is_empty_method_found = current_and_super_traits
129 .iter()
130 .flat_map(|i| match i.node {
131 ItemTrait(.., ref trait_items) => trait_items.iter(),
132 _ => bug!("should only handle traits"),
133 })
134 .any(|i| is_named_self(cx, i, "is_empty"));
135
136 if !is_empty_method_found {
137 span_lint(
138 cx,
139 LEN_WITHOUT_IS_EMPTY,
140 visited_trait.span,
141 &format!(
142 "trait `{}` has a `len` method but no (possibly inherited) `is_empty` method",
143 visited_trait.name
144 ),
145 );
146 }
147 }
148 }
149
150 fn check_impl_items(cx: &LateContext, item: &Item, impl_items: &[ImplItemRef]) {
151 fn is_named_self(cx: &LateContext, item: &ImplItemRef, name: &str) -> bool {
152 item.name == name &&
153 if let AssociatedItemKind::Method { has_self } = item.kind {
154 has_self &&
155 {
156 let did = cx.tcx.hir.local_def_id(item.id.node_id);
157 cx.tcx.fn_sig(did).inputs().skip_binder().len() == 1
158 }
159 } else {
160 false
161 }
162 }
163
164 let is_empty = if let Some(is_empty) = impl_items.iter().find(|i| is_named_self(cx, i, "is_empty")) {
165 if cx.access_levels.is_exported(is_empty.id.node_id) {
166 return;
167 } else {
168 "a private"
169 }
170 } else {
171 "no corresponding"
172 };
173
174 if let Some(i) = impl_items.iter().find(|i| is_named_self(cx, i, "len")) {
175 if cx.access_levels.is_exported(i.id.node_id) {
176 let def_id = cx.tcx.hir.local_def_id(item.id);
177 let ty = cx.tcx.type_of(def_id);
178
179 span_lint(
180 cx,
181 LEN_WITHOUT_IS_EMPTY,
182 item.span,
183 &format!("item `{}` has a public `len` method but {} `is_empty` method", ty, is_empty),
184 );
185 }
186 }
187 }
188
189 fn check_cmp(cx: &LateContext, span: Span, left: &Expr, right: &Expr, op: &str) {
190 // check if we are in an is_empty() method
191 if let Some(name) = get_item_name(cx, left) {
192 if name == "is_empty" {
193 return;
194 }
195 }
196 match (&left.node, &right.node) {
197 (&ExprLit(ref lit), &ExprMethodCall(ref method_path, _, ref args)) |
198 (&ExprMethodCall(ref method_path, _, ref args), &ExprLit(ref lit)) => {
199 check_len_zero(cx, span, method_path.name, args, lit, op)
200 },
201 _ => (),
202 }
203 }
204
205 fn check_len_zero(cx: &LateContext, span: Span, name: Name, args: &[Expr], lit: &Lit, op: &str) {
206 if let Spanned { node: LitKind::Int(0, _), .. } = *lit {
207 if name == "len" && args.len() == 1 && has_is_empty(cx, &args[0]) {
208 span_lint_and_sugg(
209 cx,
210 LEN_ZERO,
211 span,
212 "length comparison to zero",
213 "using `is_empty` is more concise",
214 format!("{}{}.is_empty()", op, snippet(cx, args[0].span, "_")),
215 );
216 }
217 }
218 }
219
220 /// Check if this type has an `is_empty` method.
221 fn has_is_empty(cx: &LateContext, expr: &Expr) -> bool {
222 /// Get an `AssociatedItem` and return true if it matches `is_empty(self)`.
223 fn is_is_empty(cx: &LateContext, item: &ty::AssociatedItem) -> bool {
224 if let ty::AssociatedKind::Method = item.kind {
225 if item.name == "is_empty" {
226 let sig = cx.tcx.fn_sig(item.def_id);
227 let ty = sig.skip_binder();
228 ty.inputs().len() == 1
229 } else {
230 false
231 }
232 } else {
233 false
234 }
235 }
236
237 /// Check the inherent impl's items for an `is_empty(self)` method.
238 fn has_is_empty_impl(cx: &LateContext, id: DefId) -> bool {
239 cx.tcx.inherent_impls(id).iter().any(|imp| {
240 cx.tcx.associated_items(*imp).any(
241 |item| is_is_empty(cx, &item),
242 )
243 })
244 }
245
246 let ty = &walk_ptrs_ty(cx.tables.expr_ty(expr));
247 match ty.sty {
248 ty::TyDynamic(..) => {
249 cx.tcx
250 .associated_items(ty.ty_to_def_id().expect("trait impl not found"))
251 .any(|item| is_is_empty(cx, &item))
252 },
253 ty::TyProjection(_) => {
254 ty.ty_to_def_id().map_or(
255 false,
256 |id| has_is_empty_impl(cx, id),
257 )
258 },
259 ty::TyAdt(id, _) => has_is_empty_impl(cx, id.did),
260 ty::TyArray(..) | ty::TySlice(..) | ty::TyStr => true,
261 _ => false,
262 }
263 }