2 use rustc
::hir
::def_id
::DefId
;
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}
;
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.
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.
19 /// **Known problems:** None.
23 /// if x.len() == 0 { .. }
28 "checking `.len() == 0` or `.len() > 0` (or similar) when `.is_empty()` \
29 could be used instead"
32 /// **What it does:** Checks for items that implement `.len()` but not
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.
41 /// **Known problems:** None.
46 /// pub fn len(&self) -> usize { .. }
50 pub LEN_WITHOUT_IS_EMPTY
,
52 "traits or impls with a public `len` method but no corresponding `is_empty` method"
55 #[derive(Copy, Clone)]
58 impl LintPass
for LenZero
{
59 fn get_lints(&self) -> LintArray
{
60 lint_array
!(LEN_ZERO
, LEN_WITHOUT_IS_EMPTY
)
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
) {
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
),
77 fn check_expr(&mut self, cx
: &LateContext
<'a
, 'tcx
>, expr
: &'tcx Expr
) {
78 if in_macro(expr
.span
) {
82 if let ExprBinary(Spanned { node: cmp, .. }
, ref left
, ref right
) = expr
.node
{
84 BiEq
=> check_cmp(cx
, expr
.span
, left
, right
, ""),
85 BiGt
| BiNe
=> check_cmp(cx
, expr
.span
, left
, right
, "!"),
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
{
95 if let AssociatedItemKind
::Method { has_self }
= item
.kind
{
98 let did
= cx
.tcx
.hir
.local_def_id(item
.id
.node_id
);
99 cx
.tcx
.fn_sig(did
).inputs().skip_binder().len() == 1
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
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
);
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
);
128 let is_empty_method_found
= current_and_super_traits
130 .flat_map(|i
| match i
.node
{
131 ItemTrait(.., ref trait_items
) => trait_items
.iter(),
132 _
=> bug
!("should only handle traits"),
134 .any(|i
| is_named_self(cx
, i
, "is_empty"));
136 if !is_empty_method_found
{
139 LEN_WITHOUT_IS_EMPTY
,
142 "trait `{}` has a `len` method but no (possibly inherited) `is_empty` method",
150 fn check_impl_items(cx
: &LateContext
, item
: &Item
, impl_items
: &[ImplItemRef
]) {
151 fn is_named_self(cx
: &LateContext
, item
: &ImplItemRef
, name
: &str) -> bool
{
153 if let AssociatedItemKind
::Method { has_self }
= item
.kind
{
156 let did
= cx
.tcx
.hir
.local_def_id(item
.id
.node_id
);
157 cx
.tcx
.fn_sig(did
).inputs().skip_binder().len() == 1
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
) {
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
);
181 LEN_WITHOUT_IS_EMPTY
,
183 &format
!("item `{}` has a public `len` method but {} `is_empty` method", ty
, is_empty
),
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" {
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
)
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]) {
212 "length comparison to zero",
213 "using `is_empty` is more concise",
214 format
!("{}{}.is_empty()", op
, snippet(cx
, args
[0].span
, "_")),
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
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
),
246 let ty
= &walk_ptrs_ty(cx
.tables
.expr_ty(expr
));
248 ty
::TyDynamic(..) => {
250 .associated_items(ty
.ty_to_def_id().expect("trait impl not found"))
251 .any(|item
| is_is_empty(cx
, &item
))
253 ty
::TyProjection(_
) => {
254 ty
.ty_to_def_id().map_or(
256 |id
| has_is_empty_impl(cx
, id
),
259 ty
::TyAdt(id
, _
) => has_is_empty_impl(cx
, id
.did
),
260 ty
::TyArray(..) | ty
::TySlice(..) | ty
::TyStr
=> true,