1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
use rustc::lint::*;
use rustc_front::hir::*;
use syntax::ast::Name;
use syntax::ptr::P;
use syntax::codemap::{Span, Spanned};
use rustc::middle::def_id::DefId;
use rustc::middle::ty::{self, MethodTraitItemId, ImplOrTraitItemId};
use syntax::ast::Lit_::*;
use syntax::ast::Lit;
use utils::{get_item_name, snippet, span_lint, walk_ptrs_ty};
declare_lint!(pub LEN_ZERO, Warn,
"checking `.len() == 0` or `.len() > 0` (or similar) when `.is_empty()` \
could be used instead");
declare_lint!(pub LEN_WITHOUT_IS_EMPTY, Warn,
"traits and impls that have `.len()` but not `.is_empty()`");
#[derive(Copy,Clone)]
pub struct LenZero;
impl LintPass for LenZero {
fn get_lints(&self) -> LintArray {
lint_array!(LEN_ZERO, LEN_WITHOUT_IS_EMPTY)
}
}
impl LateLintPass for LenZero {
fn check_item(&mut self, cx: &LateContext, item: &Item) {
match item.node {
ItemTrait(_, _, _, ref trait_items) =>
check_trait_items(cx, item, trait_items),
ItemImpl(_, _, _, None, _, ref impl_items) =>
check_impl_items(cx, item, impl_items),
_ => ()
}
}
fn check_expr(&mut self, cx: &LateContext, expr: &Expr) {
if let ExprBinary(Spanned{node: cmp, ..}, ref left, ref right) =
expr.node {
match cmp {
BiEq => check_cmp(cx, expr.span, left, right, ""),
BiGt | BiNe => check_cmp(cx, expr.span, left, right, "!"),
_ => ()
}
}
}
}
fn check_trait_items(cx: &LateContext, item: &Item, trait_items: &[P<TraitItem>]) {
fn is_named_self(item: &TraitItem, name: &str) -> bool {
item.name.as_str() == name && if let MethodTraitItem(ref sig, _) =
item.node { is_self_sig(sig) } else { false }
}
if !trait_items.iter().any(|i| is_named_self(i, "is_empty")) {
for i in trait_items {
if is_named_self(i, "len") {
span_lint(cx, LEN_WITHOUT_IS_EMPTY, i.span,
&format!("trait `{}` has a `.len(_: &Self)` method, but no \
`.is_empty(_: &Self)` method. Consider adding one",
item.name));
}
};
}
}
fn check_impl_items(cx: &LateContext, item: &Item, impl_items: &[P<ImplItem>]) {
fn is_named_self(item: &ImplItem, name: &str) -> bool {
item.name.as_str() == name && if let MethodImplItem(ref sig, _) =
item.node { is_self_sig(sig) } else { false }
}
if !impl_items.iter().any(|i| is_named_self(i, "is_empty")) {
for i in impl_items {
if is_named_self(i, "len") {
let s = i.span;
span_lint(cx, LEN_WITHOUT_IS_EMPTY,
Span{ lo: s.lo, hi: s.lo, expn_id: s.expn_id },
&format!("item `{}` has a `.len(_: &Self)` method, but no \
`.is_empty(_: &Self)` method. Consider adding one",
item.name));
return;
}
}
}
}
fn is_self_sig(sig: &MethodSig) -> bool {
if let SelfStatic = sig.explicit_self.node {
false } else { sig.decl.inputs.len() == 1 }
}
fn check_cmp(cx: &LateContext, span: Span, left: &Expr, right: &Expr, op: &str) {
if let Some(name) = get_item_name(cx, left) {
if name.as_str() == "is_empty" { return; }
}
match (&left.node, &right.node) {
(&ExprLit(ref lit), &ExprMethodCall(ref method, _, ref args)) =>
check_len_zero(cx, span, &method.node, args, lit, op),
(&ExprMethodCall(ref method, _, ref args), &ExprLit(ref lit)) =>
check_len_zero(cx, span, &method.node, args, lit, op),
_ => ()
}
}
fn check_len_zero(cx: &LateContext, span: Span, name: &Name,
args: &[P<Expr>], lit: &Lit, op: &str) {
if let Spanned{node: LitInt(0, _), ..} = *lit {
if name.as_str() == "len" && args.len() == 1 &&
has_is_empty(cx, &args[0]) {
span_lint(cx, LEN_ZERO, span, &format!(
"consider replacing the len comparison with `{}{}.is_empty()`",
op, snippet(cx, args[0].span, "_")))
}
}
}
fn has_is_empty(cx: &LateContext, expr: &Expr) -> bool {
fn is_is_empty(cx: &LateContext, id: &ImplOrTraitItemId) -> bool {
if let &MethodTraitItemId(def_id) = id {
if let ty::MethodTraitItem(ref method) =
cx.tcx.impl_or_trait_item(def_id) {
method.name.as_str() == "is_empty"
&& method.fty.sig.skip_binder().inputs.len() == 1
} else { false }
} else { false }
}
fn has_is_empty_impl(cx: &LateContext, id: &DefId) -> bool {
let impl_items = cx.tcx.impl_items.borrow();
cx.tcx.inherent_impls.borrow().get(id).map_or(false,
|ids| ids.iter().any(|iid| impl_items.get(iid).map_or(false,
|iids| iids.iter().any(|i| is_is_empty(cx, i)))))
}
let ty = &walk_ptrs_ty(&cx.tcx.expr_ty(expr));
match ty.sty {
ty::TyTrait(_) => cx.tcx.trait_item_def_ids.borrow().get(
&ty.ty_to_def_id().expect("trait impl not found")).map_or(false,
|ids| ids.iter().any(|i| is_is_empty(cx, i))),
ty::TyProjection(_) => ty.ty_to_def_id().map_or(false,
|id| has_is_empty_impl(cx, &id)),
ty::TyEnum(ref id, _) | ty::TyStruct(ref id, _) =>
has_is_empty_impl(cx, &id.did),
ty::TyArray(..) => true,
_ => false,
}
}