2015-09-03 14:42:17 +00:00
use rustc_front ::hir ::* ;
use reexport ::* ;
2015-08-16 06:54:43 +00:00
use rustc ::lint ::* ;
2015-08-12 11:16:09 +00:00
use syntax ::codemap ::Span ;
2015-12-10 16:44:12 +00:00
use rustc_front ::intravisit ::{ Visitor , walk_ty , walk_ty_param_bound , walk_fn_decl , walk_generics } ;
2015-12-04 14:56:25 +00:00
use rustc ::middle ::def ::Def ::{ DefTy , DefTrait , DefStruct } ;
2015-12-06 21:36:22 +00:00
use std ::collections ::{ HashSet , HashMap } ;
2015-08-16 06:54:43 +00:00
use utils ::{ in_external_macro , span_lint } ;
2015-08-12 11:16:09 +00:00
2015-12-11 00:22:27 +00:00
/// **What it does:** This lint checks for lifetime annotations which can be removed by relying on lifetime elision. It is `Warn` by default.
///
/// **Why is this bad?** The additional lifetimes make the code look more complicated, while there is nothing out of the ordinary going on. Removing them leads to more readable code.
///
/// **Known problems:** Potential false negatives: we bail out if the function has a `where` clause where lifetimes are mentioned.
///
/// **Example:** `fn in_and_out<'a>(x: &'a u8, y: u8) -> &'a u8 { x }`
2015-08-12 11:16:09 +00:00
declare_lint! ( pub NEEDLESS_LIFETIMES , Warn ,
2015-08-13 08:32:35 +00:00
" using explicit lifetimes for references in function arguments when elision rules \
would allow omitting them " );
2015-08-12 11:16:09 +00:00
2015-12-06 21:36:22 +00:00
declare_lint! ( pub UNUSED_LIFETIMES , Warn ,
" unused lifetimes in function definitions " ) ;
2015-08-12 11:16:09 +00:00
#[ derive(Copy,Clone) ]
pub struct LifetimePass ;
impl LintPass for LifetimePass {
fn get_lints ( & self ) -> LintArray {
2015-12-06 21:36:22 +00:00
lint_array! ( NEEDLESS_LIFETIMES , UNUSED_LIFETIMES )
2015-08-12 11:16:09 +00:00
}
2015-09-19 02:53:04 +00:00
}
2015-08-12 11:16:09 +00:00
2015-09-19 02:53:04 +00:00
impl LateLintPass for LifetimePass {
fn check_item ( & mut self , cx : & LateContext , item : & Item ) {
2015-08-13 15:24:47 +00:00
if let ItemFn ( ref decl , _ , _ , _ , ref generics , _ ) = item . node {
2015-08-30 07:57:52 +00:00
check_fn_inner ( cx , decl , None , & generics , item . span ) ;
2015-08-12 11:16:09 +00:00
}
2015-08-13 15:24:47 +00:00
}
2015-09-19 02:53:04 +00:00
fn check_impl_item ( & mut self , cx : & LateContext , item : & ImplItem ) {
2015-11-19 14:51:30 +00:00
if let ImplItemKind ::Method ( ref sig , _ ) = item . node {
2015-08-25 12:41:35 +00:00
check_fn_inner ( cx , & sig . decl , Some ( & sig . explicit_self ) ,
2015-08-30 07:57:52 +00:00
& sig . generics , item . span ) ;
2015-08-13 15:24:47 +00:00
}
}
2015-09-19 02:53:04 +00:00
fn check_trait_item ( & mut self , cx : & LateContext , item : & TraitItem ) {
2015-08-13 15:24:47 +00:00
if let MethodTraitItem ( ref sig , _ ) = item . node {
2015-08-25 12:41:35 +00:00
check_fn_inner ( cx , & sig . decl , Some ( & sig . explicit_self ) ,
2015-08-30 07:57:52 +00:00
& sig . generics , item . span ) ;
2015-08-12 11:16:09 +00:00
}
}
}
2015-08-12 18:13:14 +00:00
/// The lifetime of a &-reference.
2015-08-12 11:16:09 +00:00
#[ derive(PartialEq, Eq, Hash, Debug) ]
enum RefLt {
Unnamed ,
Static ,
Named ( Name ) ,
}
use self ::RefLt ::* ;
2015-09-19 02:53:04 +00:00
fn check_fn_inner ( cx : & LateContext , decl : & FnDecl , slf : Option < & ExplicitSelf > ,
2015-08-30 07:57:52 +00:00
generics : & Generics , span : Span ) {
2015-11-10 23:12:45 +00:00
if in_external_macro ( cx , span ) | | has_where_lifetimes ( cx , & generics . where_clause ) {
2015-08-13 15:24:47 +00:00
return ;
}
2015-11-10 23:12:45 +00:00
if could_use_elision ( cx , decl , slf , & generics . lifetimes ) {
2015-08-13 15:24:47 +00:00
span_lint ( cx , NEEDLESS_LIFETIMES , span ,
" explicit lifetimes given in parameter types where they could be elided " ) ;
}
2015-12-10 16:44:12 +00:00
report_extra_lifetimes ( cx , decl , & generics ) ;
2015-08-13 15:24:47 +00:00
}
2015-11-10 23:12:45 +00:00
fn could_use_elision ( cx : & LateContext , func : & FnDecl , slf : Option < & ExplicitSelf > ,
2015-08-13 15:24:47 +00:00
named_lts : & [ LifetimeDef ] ) -> bool {
2015-08-12 11:16:09 +00:00
// There are two scenarios where elision works:
// * no output references, all input references have different LT
// * output references, exactly one input reference with same LT
2015-08-13 15:24:47 +00:00
// All lifetimes must be unnamed, 'static or defined without bounds on the
// level of the current item.
// check named LTs
2015-08-31 09:11:51 +00:00
let allowed_lts = allowed_lts_from ( named_lts ) ;
2015-08-12 11:16:09 +00:00
2015-08-12 18:13:14 +00:00
// these will collect all the lifetimes for references in arg/return types
2015-11-10 23:12:45 +00:00
let mut input_visitor = RefVisitor ::new ( cx ) ;
let mut output_visitor = RefVisitor ::new ( cx ) ;
2015-08-12 11:16:09 +00:00
2015-08-12 18:13:14 +00:00
// extract lifetime in "self" argument for methods (there is a "self" argument
// in func.inputs, but its type is TyInfer)
2015-08-13 15:24:47 +00:00
if let Some ( slf ) = slf {
match slf . node {
2015-08-12 18:13:14 +00:00
SelfRegion ( ref opt_lt , _ , _ ) = > input_visitor . record ( opt_lt ) ,
SelfExplicit ( ref ty , _ ) = > walk_ty ( & mut input_visitor , ty ) ,
2015-08-12 11:16:09 +00:00
_ = > { }
}
}
2015-08-12 18:13:14 +00:00
// extract lifetimes in input argument types
for arg in & func . inputs {
2015-11-10 23:12:45 +00:00
input_visitor . visit_ty ( & arg . ty ) ;
2015-08-12 18:13:14 +00:00
}
// extract lifetimes in output type
2015-08-12 11:16:09 +00:00
if let Return ( ref ty ) = func . output {
2015-11-10 23:12:45 +00:00
output_visitor . visit_ty ( ty ) ;
2015-08-12 11:16:09 +00:00
}
let input_lts = input_visitor . into_vec ( ) ;
let output_lts = output_visitor . into_vec ( ) ;
2015-08-13 15:24:47 +00:00
// check for lifetimes from higher scopes
for lt in input_lts . iter ( ) . chain ( output_lts . iter ( ) ) {
if ! allowed_lts . contains ( lt ) {
return false ;
}
}
2015-08-12 11:16:09 +00:00
// no input lifetimes? easy case!
if input_lts . is_empty ( ) {
2015-11-18 11:35:18 +00:00
false
2015-08-12 11:16:09 +00:00
} else if output_lts . is_empty ( ) {
// no output lifetimes, check distinctness of input lifetimes
2015-08-15 07:36:02 +00:00
// only unnamed and static, ok
if input_lts . iter ( ) . all ( | lt | * lt = = Unnamed | | * lt = = Static ) {
2015-08-12 11:16:09 +00:00
return false ;
}
// we have no output reference, so we only need all distinct lifetimes
2015-11-18 11:35:18 +00:00
input_lts . len ( ) = = unique_lifetimes ( & input_lts )
2015-08-12 11:16:09 +00:00
} else {
// we have output references, so we need one input reference,
// and all output lifetimes must be the same
if unique_lifetimes ( & output_lts ) > 1 {
return false ;
}
if input_lts . len ( ) = = 1 {
match ( & input_lts [ 0 ] , & output_lts [ 0 ] ) {
2015-11-18 11:35:18 +00:00
( & Named ( n1 ) , & Named ( n2 ) ) if n1 = = n2 = > true ,
( & Named ( _ ) , & Unnamed ) = > true ,
( & Unnamed , & Named ( _ ) ) = > true ,
_ = > false // already elided, different named lifetimes
// or something static going on
2015-08-12 11:16:09 +00:00
}
2015-11-18 11:35:18 +00:00
} else {
false
2015-08-12 11:16:09 +00:00
}
}
}
2015-08-31 09:11:51 +00:00
fn allowed_lts_from ( named_lts : & [ LifetimeDef ] ) -> HashSet < RefLt > {
let mut allowed_lts = HashSet ::new ( ) ;
for lt in named_lts {
if lt . bounds . is_empty ( ) {
allowed_lts . insert ( Named ( lt . lifetime . name ) ) ;
}
}
allowed_lts . insert ( Unnamed ) ;
allowed_lts . insert ( Static ) ;
allowed_lts
}
2015-08-12 18:13:14 +00:00
/// Number of unique lifetimes in the given vector.
2015-08-13 14:28:11 +00:00
fn unique_lifetimes ( lts : & [ RefLt ] ) -> usize {
2015-08-12 18:13:14 +00:00
lts . iter ( ) . collect ::< HashSet < _ > > ( ) . len ( )
2015-08-12 11:16:09 +00:00
}
2015-09-03 14:42:17 +00:00
/// A visitor usable for rustc_front::visit::walk_ty().
2015-11-10 23:12:45 +00:00
struct RefVisitor < ' v , ' t : ' v > {
cx : & ' v LateContext < ' v , ' t > , // context reference
lts : Vec < RefLt >
}
impl < ' v , ' t > RefVisitor < ' v , ' t > {
fn new ( cx : & ' v LateContext < ' v , ' t > ) -> RefVisitor < ' v , ' t > {
RefVisitor { cx : cx , lts : Vec ::new ( ) }
}
2015-08-12 11:16:09 +00:00
2015-08-12 18:13:14 +00:00
fn record ( & mut self , lifetime : & Option < Lifetime > ) {
2015-11-24 17:44:40 +00:00
if let Some ( ref lt ) = * lifetime {
2015-09-28 05:04:06 +00:00
if lt . name . as_str ( ) = = " 'static " {
2015-11-10 23:12:45 +00:00
self . lts . push ( Static ) ;
2015-08-12 11:16:09 +00:00
} else {
2015-11-10 23:12:45 +00:00
self . lts . push ( Named ( lt . name ) ) ;
2015-08-12 11:16:09 +00:00
}
} else {
2015-11-10 23:12:45 +00:00
self . lts . push ( Unnamed ) ;
2015-08-12 11:16:09 +00:00
}
}
2015-08-12 18:13:14 +00:00
fn into_vec ( self ) -> Vec < RefLt > {
2015-11-10 23:12:45 +00:00
self . lts
}
fn collect_anonymous_lifetimes ( & mut self , path : & Path , ty : & Ty ) {
let last_path_segment = path . segments . last ( ) . map ( | s | & s . parameters ) ;
if let Some ( & AngleBracketedParameters ( ref params ) ) = last_path_segment {
if params . lifetimes . is_empty ( ) {
2015-12-04 14:56:25 +00:00
if let Some ( def ) = self . cx . tcx . def_map . borrow ( ) . get ( & ty . id ) . map ( | r | r . full_def ( ) ) {
match def {
DefTy ( def_id , _ ) | DefStruct ( def_id ) = > {
let type_scheme = self . cx . tcx . lookup_item_type ( def_id ) ;
for _ in type_scheme . generics . regions . as_slice ( ) {
2015-11-10 23:12:45 +00:00
self . record ( & None ) ;
}
2015-12-04 14:56:25 +00:00
} ,
DefTrait ( def_id ) = > {
let trait_def = self . cx . tcx . trait_defs . borrow ( ) [ & def_id ] ;
for _ in & trait_def . generics . regions {
self . record ( & None ) ;
}
} ,
_ = > { }
2015-11-17 04:39:42 +00:00
}
2015-11-10 23:12:45 +00:00
}
}
}
2015-08-12 18:13:14 +00:00
}
}
2015-11-10 23:12:45 +00:00
impl < ' v , ' t > Visitor < ' v > for RefVisitor < ' v , ' t > {
2015-08-13 04:43:25 +00:00
// for lifetimes as parameters of generics
2015-09-30 14:24:41 +00:00
fn visit_lifetime ( & mut self , lifetime : & ' v Lifetime ) {
2015-08-13 04:43:25 +00:00
self . record ( & Some ( * lifetime ) ) ;
}
2015-09-30 14:40:54 +00:00
fn visit_ty ( & mut self , ty : & ' v Ty ) {
2015-11-10 23:12:45 +00:00
match ty . node {
TyRptr ( None , _ ) = > {
self . record ( & None ) ;
2015-11-17 04:39:42 +00:00
}
2015-11-10 23:12:45 +00:00
TyPath ( _ , ref path ) = > {
self . collect_anonymous_lifetimes ( path , ty ) ;
2015-11-17 04:39:42 +00:00
}
2015-11-10 23:12:45 +00:00
_ = > { }
2015-09-30 14:40:54 +00:00
}
walk_ty ( self , ty ) ;
}
2015-08-12 11:16:09 +00:00
}
2015-08-30 07:57:52 +00:00
/// Are any lifetimes mentioned in the `where` clause? If yes, we don't try to
/// reason about elision.
2015-11-10 23:12:45 +00:00
fn has_where_lifetimes ( cx : & LateContext , where_clause : & WhereClause ) -> bool {
2015-08-30 07:57:52 +00:00
for predicate in & where_clause . predicates {
match * predicate {
WherePredicate ::RegionPredicate ( .. ) = > return true ,
WherePredicate ::BoundPredicate ( ref pred ) = > {
2015-08-31 09:11:51 +00:00
// a predicate like F: Trait or F: for<'a> Trait<'a>
2015-11-10 23:12:45 +00:00
let mut visitor = RefVisitor ::new ( cx ) ;
2015-08-31 09:11:51 +00:00
// walk the type F, it may not contain LT refs
walk_ty ( & mut visitor , & pred . bounded_ty ) ;
2015-11-10 23:12:45 +00:00
if ! visitor . lts . is_empty ( ) { return true ; }
2015-08-31 09:11:51 +00:00
// if the bounds define new lifetimes, they are fine to occur
let allowed_lts = allowed_lts_from ( & pred . bound_lifetimes ) ;
// now walk the bounds
for bound in pred . bounds . iter ( ) {
walk_ty_param_bound ( & mut visitor , bound ) ;
}
// and check that all lifetimes are allowed
for lt in visitor . into_vec ( ) {
if ! allowed_lts . contains ( & lt ) {
return true ;
}
}
2015-08-30 07:57:52 +00:00
}
WherePredicate ::EqPredicate ( ref pred ) = > {
2015-11-10 23:12:45 +00:00
let mut visitor = RefVisitor ::new ( cx ) ;
2015-08-31 09:11:51 +00:00
walk_ty ( & mut visitor , & pred . ty ) ;
2015-11-10 23:12:45 +00:00
if ! visitor . lts . is_empty ( ) { return true ; }
2015-08-30 07:57:52 +00:00
}
}
}
2015-08-31 09:11:51 +00:00
false
2015-08-30 07:57:52 +00:00
}
2015-12-06 21:36:22 +00:00
struct LifetimeChecker ( HashMap < Name , Span > ) ;
impl < ' v > Visitor < ' v > for LifetimeChecker {
// for lifetimes as parameters of generics
fn visit_lifetime ( & mut self , lifetime : & ' v Lifetime ) {
self . 0. remove ( & lifetime . name ) ;
}
2015-12-10 16:44:12 +00:00
fn visit_lifetime_def ( & mut self , _ : & ' v LifetimeDef ) {
// don't actually visit `<'a>` or `<'a: 'b>`
// we've already visited the `'a` declarations and
// don't want to spuriously remove them
// `'b` in `'a: 'b` is useless unless used elsewhere in
// a non-lifetime bound
}
2015-12-06 21:36:22 +00:00
}
fn report_extra_lifetimes ( cx : & LateContext , func : & FnDecl ,
2015-12-10 16:44:12 +00:00
generics : & Generics ) {
let hs = generics . lifetimes . iter ( )
. map ( | lt | ( lt . lifetime . name , lt . lifetime . span ) )
. collect ( ) ;
2015-12-06 21:36:22 +00:00
let mut checker = LifetimeChecker ( hs ) ;
2015-12-10 16:44:12 +00:00
walk_generics ( & mut checker , generics ) ;
2015-12-06 21:36:22 +00:00
walk_fn_decl ( & mut checker , func ) ;
for ( _ , v ) in checker . 0 {
span_lint ( cx , UNUSED_LIFETIMES , v ,
" this lifetime isn't used in the function definition " ) ;
}
}