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 } ;
2015-09-16 19:01:41 -05:00
use syntax ::ast ::Lit_ ::* ;
use syntax ::ast ::Lit ;
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
2015-12-10 18:22:27 -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. It is `Warn` by default.
///
/// **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 { .. }`
2015-05-20 01:52:19 -05:00
declare_lint! ( pub LEN_ZERO , Warn ,
2015-08-13 03:32:35 -05:00
" checking `.len() == 0` or `.len() > 0` (or similar) when `.is_empty()` \
could be used instead " );
2015-05-20 01:52:19 -05:00
2015-12-10 18:22:27 -06:00
/// **What it does:** This lint checks for items that implement `.len()` but not `.is_empty()`. It is `Warn` by default.
///
/// **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 { .. }
/// }
/// ```
2015-05-20 01:52:19 -05:00
declare_lint! ( pub LEN_WITHOUT_IS_EMPTY , Warn ,
2015-08-13 03:32:35 -05:00
" 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 {
ItemTrait ( _ , _ , _ , ref trait_items ) = >
2015-08-11 13:22:20 -05:00
check_trait_items ( cx , item , trait_items ) ,
2015-08-21 13:44:48 -05:00
ItemImpl ( _ , _ , _ , None , _ , ref impl_items ) = > // only non-trait
2015-08-11 13:22:20 -05:00
check_impl_items ( cx , item , impl_items ) ,
_ = > ( )
}
}
2015-09-18 21:53:04 -05:00
fn check_expr ( & mut self , cx : & LateContext , expr : & Expr ) {
2015-09-06 13:57:06 -05:00
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 , " ! " ) ,
_ = > ( )
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 {
2015-09-28 00:04:06 -05:00
item . name . as_str ( ) = = name & & if let MethodTraitItem ( ref sig , _ ) =
2015-08-11 13:22:20 -05:00
item . node { is_self_sig ( sig ) } else { false }
}
if ! trait_items . iter ( ) . any ( | i | is_named_self ( i , " is_empty " ) ) {
2015-08-16 01:33:10 -05: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 " ) {
span_lint ( cx , LEN_WITHOUT_IS_EMPTY , i . span ,
2015-08-12 03:46:49 -05:00
& format! ( " trait ` {} ` has a `.len(_: &Self)` method, but no \
2015-08-13 01:15:42 -05:00
` . 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
}
} ;
}
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 {
2015-11-19 08:51:30 -06:00
item . name . as_str ( ) = = name & & if let ImplItemKind ::Method ( ref sig , _ ) =
2015-08-11 13:22:20 -05:00
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 } ,
2015-08-12 03:46:49 -05:00
& format! ( " item ` {} ` has a `.len(_: &Self)` method, but no \
2015-08-13 01:15:42 -05:00
` . 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 {
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 ) {
2015-09-28 00:04:06 -05: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 ) {
( & ExprLit ( ref lit ) , & ExprMethodCall ( ref method , _ , ref args ) ) = >
2015-09-23 19:30:39 -05:00
check_len_zero ( cx , span , & method . node , args , lit , op ) ,
2015-08-11 13:22:20 -05:00
( & ExprMethodCall ( ref method , _ , ref args ) , & ExprLit ( ref lit ) ) = >
2015-09-23 19:30:39 -05:00
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
}
2015-09-23 19:30:39 -05:00
fn check_len_zero ( cx : & LateContext , span : Span , name : & Name ,
2015-08-12 03:53:14 -05:00
args : & [ P < Expr > ] , lit : & Lit , op : & str ) {
2015-08-21 13:44:48 -05:00
if let Spanned { node : LitInt ( 0 , _ ) , .. } = * lit {
2015-09-28 00:04:06 -05:00
if name . as_str ( ) = = " len " & & args . len ( ) = = 1 & &
2015-08-25 07:41:35 -05:00
has_is_empty ( cx , & args [ 0 ] ) {
2015-08-11 13:22:20 -05:00
span_lint ( cx , LEN_ZERO , span , & format! (
2015-08-12 03:53:14 -05:00
" consider replacing the len comparison with `{}{}.is_empty()` " ,
2015-12-31 14:39:03 -06:00
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 {
2015-08-11 13:22:20 -05:00
if let ty ::MethodTraitItem ( ref method ) =
cx . tcx . impl_or_trait_item ( def_id ) {
2015-09-28 00:04:06 -05:00
method . name . as_str ( ) = = " is_empty "
2015-08-11 13:22:20 -05:00
& & method . fty . sig . skip_binder ( ) . inputs . len ( ) = = 1
} else { false }
} else { false }
}
/// 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 ( ) ;
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: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 {
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 ,
}
2015-06-01 00:40:33 -05:00
}