2015-08-16 01:54:43 -05:00
use rustc ::lint ::* ;
2015-09-03 09:42:17 -05:00
use rustc_front ::hir ::* ;
2015-09-23 19:30:39 -05:00
use syntax ::ast ::Name ;
2015-05-20 01:52:19 -05:00
use syntax ::ptr ::P ;
use syntax ::codemap ::{ Span , Spanned } ;
2015-08-25 16:21:38 -05:00
use rustc ::middle ::def_id ::DefId ;
2015-08-16 01:54:43 -05:00
use rustc ::middle ::ty ::{ self , MethodTraitItemId , ImplOrTraitItemId } ;
2016-02-03 08:39:22 -06:00
use syntax ::ast ::{ Lit , Lit_ } ;
2015-09-16 19:01:41 -05:00
2015-09-06 14:03:09 -05:00
use utils ::{ get_item_name , snippet , span_lint , walk_ptrs_ty } ;
2015-05-20 01:52:19 -05:00
2016-02-05 17:41:54 -06:00
/// **What it does:** This lint checks for getting the length of something via `.len()` just to compare to zero, and suggests using `.is_empty()` where applicable.
2015-12-10 18:22:27 -06:00
///
/// **Why is this bad?** Some structures can answer `.is_empty()` much faster than calculating their length. So it is good to get into the habit of using `.is_empty()`, and having it is cheap. Besides, it makes the intent clearer than a comparison.
///
/// **Known problems:** None
///
/// **Example:** `if x.len() == 0 { .. }`
2016-02-05 17:13:29 -06:00
declare_lint! {
pub LEN_ZERO , Warn ,
" checking `.len() == 0` or `.len() > 0` (or similar) when `.is_empty()` \
could be used instead "
}
2015-05-20 01:52:19 -05:00
2016-02-05 17:41:54 -06:00
/// **What it does:** This lint checks for items that implement `.len()` but not `.is_empty()`.
2015-12-10 18:22:27 -06:00
///
/// **Why is this bad?** It is good custom to have both methods, because for some data structures, asking about the length will be a costly operation, whereas `.is_empty()` can usually answer in constant time. Also it used to lead to false positives on the [`len_zero`](#len_zero) lint – currently that lint will ignore such entities.
///
/// **Known problems:** None
///
/// **Example:**
/// ```
/// impl X {
/// fn len(&self) -> usize { .. }
/// }
/// ```
2016-02-05 17:13:29 -06:00
declare_lint! {
pub LEN_WITHOUT_IS_EMPTY , Warn ,
" traits and impls that have `.len()` but not `.is_empty()` "
}
2015-05-20 01:52:19 -05:00
#[ derive(Copy,Clone) ]
pub struct LenZero ;
impl LintPass for LenZero {
2015-08-11 13:22:20 -05:00
fn get_lints ( & self ) -> LintArray {
2015-05-20 01:52:19 -05:00
lint_array! ( LEN_ZERO , LEN_WITHOUT_IS_EMPTY )
2015-08-11 13:22:20 -05:00
}
2015-09-18 21:53:04 -05:00
}
2015-08-11 13:22:20 -05:00
2015-09-18 21:53:04 -05:00
impl LateLintPass for LenZero {
fn check_item ( & mut self , cx : & LateContext , item : & Item ) {
2015-08-21 13:44:48 -05:00
match item . node {
2016-01-03 22:26:12 -06:00
ItemTrait ( _ , _ , _ , ref trait_items ) = > check_trait_items ( cx , item , trait_items ) ,
ItemImpl ( _ , _ , _ , None , _ , ref impl_items ) = > check_impl_items ( cx , item , impl_items ) ,
_ = > ( ) ,
2015-08-11 13:22:20 -05:00
}
}
2015-09-18 21:53:04 -05:00
fn check_expr ( & mut self , cx : & LateContext , expr : & Expr ) {
2016-01-03 22:26:12 -06:00
if let ExprBinary ( Spanned { node : cmp , .. } , ref left , ref right ) = expr . node {
2015-09-06 13:57:06 -05:00
match cmp {
BiEq = > check_cmp ( cx , expr . span , left , right , " " ) ,
BiGt | BiNe = > check_cmp ( cx , expr . span , left , right , " ! " ) ,
2016-01-03 22:26:12 -06:00
_ = > ( ) ,
2015-08-11 13:22:20 -05:00
}
2015-09-06 13:57:06 -05:00
}
2015-08-11 13:22:20 -05:00
}
2015-05-20 01:52:19 -05:00
}
2015-12-09 14:56:49 -06:00
fn check_trait_items ( cx : & LateContext , item : & Item , trait_items : & [ TraitItem ] ) {
2015-08-11 13:22:20 -05:00
fn is_named_self ( item : & TraitItem , name : & str ) -> bool {
2016-01-03 22:26:12 -06:00
item . name . as_str ( ) = = name & &
if let MethodTraitItem ( ref sig , _ ) = item . node {
is_self_sig ( sig )
} else {
false
}
2015-08-11 13:22:20 -05:00
}
if ! trait_items . iter ( ) . any ( | i | is_named_self ( i , " is_empty " ) ) {
2016-01-03 22:26:12 -06:00
// span_lint(cx, LEN_WITHOUT_IS_EMPTY, item.span, &format!("trait {}", item.ident));
2015-08-11 13:22:20 -05:00
for i in trait_items {
if is_named_self ( i , " len " ) {
2016-01-03 22:26:12 -06:00
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 " ,
2015-09-23 19:30:39 -05:00
item . name ) ) ;
2015-08-11 13:22:20 -05:00
}
2016-01-03 22:26:12 -06:00
}
2015-08-11 13:22:20 -05:00
}
2015-05-20 01:52:19 -05:00
}
2015-12-09 14:56:49 -06:00
fn check_impl_items ( cx : & LateContext , item : & Item , impl_items : & [ ImplItem ] ) {
2015-08-11 13:22:20 -05:00
fn is_named_self ( item : & ImplItem , name : & str ) -> bool {
2016-01-03 22:26:12 -06:00
item . name . as_str ( ) = = name & &
if let ImplItemKind ::Method ( ref sig , _ ) = item . node {
is_self_sig ( sig )
} else {
false
}
2015-08-11 13:22:20 -05:00
}
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 ;
2016-01-03 22:26:12 -06:00
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 " ,
2015-09-23 19:30:39 -05:00
item . name ) ) ;
2015-08-11 13:22:20 -05:00
return ;
}
}
}
2015-05-20 01:52:19 -05:00
}
2015-06-01 05:49:36 -05:00
fn is_self_sig ( sig : & MethodSig ) -> bool {
2015-08-11 13:22:20 -05:00
if let SelfStatic = sig . explicit_self . node {
2016-01-03 22:26:12 -06:00
false
} else {
sig . decl . inputs . len ( ) = = 1
}
2015-06-01 05:49:36 -05:00
}
2015-09-28 00:11:03 -05:00
fn check_cmp ( cx : & LateContext , span : Span , left : & Expr , right : & Expr , op : & str ) {
// check if we are in an is_empty() method
2015-09-06 14:03:09 -05:00
if let Some ( name ) = get_item_name ( cx , left ) {
2016-01-03 22:26:12 -06:00
if name . as_str ( ) = = " is_empty " {
return ;
}
2015-09-06 13:57:06 -05:00
}
2015-08-11 13:22:20 -05:00
match ( & left . node , & right . node ) {
2016-01-03 22:26:12 -06:00
( & 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 )
}
_ = > ( ) ,
2015-08-11 13:22:20 -05:00
}
2015-05-20 01:52:19 -05:00
}
2016-01-03 22:26:12 -06:00
fn check_len_zero ( cx : & LateContext , span : Span , name : & Name , args : & [ P < Expr > ] , lit : & Lit , op : & str ) {
2016-02-03 08:39:22 -06:00
if let Spanned { node : Lit_ ::LitInt ( 0 , _ ) , .. } = * lit {
2016-01-03 22:26:12 -06:00
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 , " _ " ) ) ) ;
}
2015-08-11 13:22:20 -05:00
}
2015-05-20 01:52:19 -05:00
}
2015-06-01 00:40:33 -05:00
2015-06-01 05:49:36 -05:00
/// check if this type has an is_empty method
2015-09-18 21:53:04 -05:00
fn has_is_empty ( cx : & LateContext , expr : & Expr ) -> bool {
2015-08-11 13:22:20 -05:00
/// get a ImplOrTraitItem and return true if it matches is_empty(self)
2015-09-18 21:53:04 -05:00
fn is_is_empty ( cx : & LateContext , id : & ImplOrTraitItemId ) -> bool {
2015-11-24 11:44:40 -06:00
if let MethodTraitItemId ( def_id ) = * id {
2016-01-03 22:26:12 -06:00
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
}
2015-08-11 13:22:20 -05:00
}
/// check the inherent impl's items for an is_empty(self) method
2015-09-18 21:53:04 -05:00
fn has_is_empty_impl ( cx : & LateContext , id : & DefId ) -> bool {
2015-08-11 13:22:20 -05:00
let impl_items = cx . tcx . impl_items . borrow ( ) ;
2016-01-03 22:26:12 -06:00
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 ) ) ) )
} )
2015-08-11 13:22:20 -05:00
}
2015-08-11 13:57:21 -05:00
let ty = & walk_ptrs_ty ( & cx . tcx . expr_ty ( expr ) ) ;
2015-08-11 13:22:20 -05:00
match ty . sty {
2016-01-03 22:26:12 -06:00
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 ) ,
2015-08-11 13:22:20 -05:00
ty ::TyArray ( .. ) = > true ,
_ = > false ,
}
2015-06-01 00:40:33 -05:00
}