2015-08-16 06:54:43 +00:00
use rustc ::lint ::* ;
2016-04-07 15:46:48 +00:00
use rustc ::hir ::def_id ::DefId ;
2016-03-27 18:59:02 +00:00
use rustc ::ty ::{ self , MethodTraitItemId , ImplOrTraitItemId } ;
2016-04-07 15:46:48 +00:00
use rustc ::hir ::* ;
2016-02-24 16:38:57 +00:00
use syntax ::ast ::{ Lit , LitKind , Name } ;
use syntax ::codemap ::{ Span , Spanned } ;
use syntax ::ptr ::P ;
2016-02-24 19:53:15 +00:00
use utils ::{ get_item_name , in_macro , snippet , span_lint , span_lint_and_then , walk_ptrs_ty } ;
2015-05-20 06:52:19 +00:00
2016-02-05 23:41:54 +00: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-11 00:22:27 +00: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 23:13:29 +00: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 06:52:19 +00:00
2016-02-05 23:41:54 +00:00
/// **What it does:** This lint checks for items that implement `.len()` but not `.is_empty()`.
2015-12-11 00:22:27 +00: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 23:13:29 +00:00
declare_lint! {
pub LEN_WITHOUT_IS_EMPTY , Warn ,
" traits and impls that have `.len()` but not `.is_empty()` "
}
2015-05-20 06:52:19 +00:00
#[ derive(Copy,Clone) ]
pub struct LenZero ;
impl LintPass for LenZero {
2015-08-11 18:22:20 +00:00
fn get_lints ( & self ) -> LintArray {
2015-05-20 06:52:19 +00:00
lint_array! ( LEN_ZERO , LEN_WITHOUT_IS_EMPTY )
2015-08-11 18:22:20 +00:00
}
2015-09-19 02:53:04 +00:00
}
2015-08-11 18:22:20 +00:00
2015-09-19 02:53:04 +00:00
impl LateLintPass for LenZero {
fn check_item ( & mut self , cx : & LateContext , item : & Item ) {
2016-02-24 19:53:15 +00:00
if in_macro ( cx , item . span ) {
return ;
}
2015-08-21 18:44:48 +00:00
match item . node {
2016-01-04 04:26:12 +00: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 18:22:20 +00:00
}
}
2015-09-19 02:53:04 +00:00
fn check_expr ( & mut self , cx : & LateContext , expr : & Expr ) {
2016-02-24 19:53:15 +00:00
if in_macro ( cx , expr . span ) {
return ;
}
2016-04-14 18:14:03 +00:00
if let ExprBinary ( Spanned { node : cmp , .. } , ref left , ref right ) = expr . node {
2015-09-06 18:57:06 +00:00
match cmp {
BiEq = > check_cmp ( cx , expr . span , left , right , " " ) ,
BiGt | BiNe = > check_cmp ( cx , expr . span , left , right , " ! " ) ,
2016-01-04 04:26:12 +00:00
_ = > ( ) ,
2015-08-11 18:22:20 +00:00
}
2015-09-06 18:57:06 +00:00
}
2015-08-11 18:22:20 +00:00
}
2015-05-20 06:52:19 +00:00
}
2015-12-09 20:56:49 +00:00
fn check_trait_items ( cx : & LateContext , item : & Item , trait_items : & [ TraitItem ] ) {
2015-08-11 18:22:20 +00:00
fn is_named_self ( item : & TraitItem , name : & str ) -> bool {
2016-01-04 04:26:12 +00:00
item . name . as_str ( ) = = name & &
if let MethodTraitItem ( ref sig , _ ) = item . node {
is_self_sig ( sig )
} else {
false
}
2015-08-11 18:22:20 +00:00
}
if ! trait_items . iter ( ) . any ( | i | is_named_self ( i , " is_empty " ) ) {
for i in trait_items {
if is_named_self ( i , " len " ) {
2016-01-04 04:26:12 +00: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-24 00:30:39 +00:00
item . name ) ) ;
2015-08-11 18:22:20 +00:00
}
2016-01-04 04:26:12 +00:00
}
2015-08-11 18:22:20 +00:00
}
2015-05-20 06:52:19 +00:00
}
2015-12-09 20:56:49 +00:00
fn check_impl_items ( cx : & LateContext , item : & Item , impl_items : & [ ImplItem ] ) {
2015-08-11 18:22:20 +00:00
fn is_named_self ( item : & ImplItem , name : & str ) -> bool {
2016-01-04 04:26:12 +00:00
item . name . as_str ( ) = = name & &
if let ImplItemKind ::Method ( ref sig , _ ) = item . node {
is_self_sig ( sig )
} else {
false
}
2015-08-11 18:22:20 +00:00
}
if ! impl_items . iter ( ) . any ( | i | is_named_self ( i , " is_empty " ) ) {
for i in impl_items {
if is_named_self ( i , " len " ) {
2016-04-07 15:46:48 +00:00
let ty = cx . tcx . node_id_to_type ( item . id ) ;
2015-08-11 18:22:20 +00:00
let s = i . span ;
2016-01-04 04:26:12 +00: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 " ,
2016-04-07 15:46:48 +00:00
ty ) ) ;
2015-08-11 18:22:20 +00:00
return ;
}
}
}
2015-05-20 06:52:19 +00:00
}
2015-06-01 10:49:36 +00:00
fn is_self_sig ( sig : & MethodSig ) -> bool {
2015-08-11 18:22:20 +00:00
if let SelfStatic = sig . explicit_self . node {
2016-01-04 04:26:12 +00:00
false
} else {
sig . decl . inputs . len ( ) = = 1
}
2015-06-01 10:49:36 +00:00
}
2015-09-28 05:11:03 +00: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 19:03:09 +00:00
if let Some ( name ) = get_item_name ( cx , left ) {
2016-01-04 04:26:12 +00:00
if name . as_str ( ) = = " is_empty " {
return ;
}
2015-09-06 18:57:06 +00:00
}
2015-08-11 18:22:20 +00:00
match ( & left . node , & right . node ) {
2016-02-09 23:38:53 +00:00
( & ExprLit ( ref lit ) , & ExprMethodCall ( ref method , _ , ref args ) ) |
2016-01-04 04:26:12 +00:00
( & ExprMethodCall ( ref method , _ , ref args ) , & ExprLit ( ref lit ) ) = > {
check_len_zero ( cx , span , & method . node , args , lit , op )
}
_ = > ( ) ,
2015-08-11 18:22:20 +00:00
}
2015-05-20 06:52:19 +00:00
}
2016-01-04 04:26:12 +00:00
fn check_len_zero ( cx : & LateContext , span : Span , name : & Name , args : & [ P < Expr > ] , lit : & Lit , op : & str ) {
2016-04-14 18:14:03 +00:00
if let Spanned { node : LitKind ::Int ( 0 , _ ) , .. } = * lit {
2016-01-04 04:26:12 +00:00
if name . as_str ( ) = = " len " & & args . len ( ) = = 1 & & has_is_empty ( cx , & args [ 0 ] ) {
2016-02-29 11:19:32 +00:00
span_lint_and_then ( cx , LEN_ZERO , span , " length comparison to zero " , | db | {
db . span_suggestion ( span ,
" consider using `is_empty` " ,
format! ( " {} {} .is_empty() " , op , snippet ( cx , args [ 0 ] . span , " _ " ) ) ) ;
} ) ;
2016-01-04 04:26:12 +00:00
}
2015-08-11 18:22:20 +00:00
}
2015-05-20 06:52:19 +00:00
}
2015-06-01 05:40:33 +00:00
2016-03-19 16:48:29 +00:00
/// Check if this type has an `is_empty` method.
2015-09-19 02:53:04 +00:00
fn has_is_empty ( cx : & LateContext , expr : & Expr ) -> bool {
2016-03-19 16:48:29 +00:00
/// Get an `ImplOrTraitItem` and return true if it matches `is_empty(self)`.
2015-09-19 02:53:04 +00:00
fn is_is_empty ( cx : & LateContext , id : & ImplOrTraitItemId ) -> bool {
2015-11-24 17:44:40 +00:00
if let MethodTraitItemId ( def_id ) = * id {
2016-01-04 04:26:12 +00: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 18:22:20 +00:00
}
2016-03-19 16:48:29 +00:00
/// Check the inherent impl's items for an `is_empty(self)` method.
2015-09-19 02:53:04 +00:00
fn has_is_empty_impl ( cx : & LateContext , id : & DefId ) -> bool {
2015-08-11 18:22:20 +00:00
let impl_items = cx . tcx . impl_items . borrow ( ) ;
2016-01-04 04:26:12 +00: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 18:22:20 +00:00
}
2015-08-11 18:57:21 +00:00
let ty = & walk_ptrs_ty ( & cx . tcx . expr_ty ( expr ) ) ;
2015-08-11 18:22:20 +00:00
match ty . sty {
2016-01-04 04:26:12 +00: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 ) ) ,
2016-04-14 18:14:03 +00:00
ty ::TyEnum ( ref id , _ ) |
ty ::TyStruct ( ref id , _ ) = > has_is_empty_impl ( cx , & id . did ) ,
2016-03-12 20:12:35 +00:00
ty ::TyArray ( .. ) | ty ::TyStr = > true ,
2015-08-11 18:22:20 +00:00
_ = > false ,
}
2015-06-01 05:40:33 +00:00
}