1 use rustc_front::hir::*;
4 use syntax::codemap::Span;
5 use rustc_front::visit::{Visitor, walk_ty, walk_ty_param_bound};
6 use std::collections::HashSet;
8 use utils::{in_external_macro, span_lint};
10 declare_lint!(pub NEEDLESS_LIFETIMES, Warn,
11 "using explicit lifetimes for references in function arguments when elision rules \
12 would allow omitting them");
15 pub struct LifetimePass;
17 impl LintPass for LifetimePass {
18 fn get_lints(&self) -> LintArray {
19 lint_array!(NEEDLESS_LIFETIMES)
23 impl LateLintPass for LifetimePass {
24 fn check_item(&mut self, cx: &LateContext, item: &Item) {
25 if let ItemFn(ref decl, _, _, _, ref generics, _) = item.node {
26 check_fn_inner(cx, decl, None, &generics, item.span);
30 fn check_impl_item(&mut self, cx: &LateContext, item: &ImplItem) {
31 if let MethodImplItem(ref sig, _) = item.node {
32 check_fn_inner(cx, &sig.decl, Some(&sig.explicit_self),
33 &sig.generics, item.span);
37 fn check_trait_item(&mut self, cx: &LateContext, item: &TraitItem) {
38 if let MethodTraitItem(ref sig, _) = item.node {
39 check_fn_inner(cx, &sig.decl, Some(&sig.explicit_self),
40 &sig.generics, item.span);
45 /// The lifetime of a &-reference.
46 #[derive(PartialEq, Eq, Hash, Debug)]
54 fn check_fn_inner(cx: &LateContext, decl: &FnDecl, slf: Option<&ExplicitSelf>,
55 generics: &Generics, span: Span) {
56 if in_external_macro(cx, span) || has_where_lifetimes(&generics.where_clause) {
59 if could_use_elision(decl, slf, &generics.lifetimes) {
60 span_lint(cx, NEEDLESS_LIFETIMES, span,
61 "explicit lifetimes given in parameter types where they could be elided");
65 fn could_use_elision(func: &FnDecl, slf: Option<&ExplicitSelf>,
66 named_lts: &[LifetimeDef]) -> bool {
67 // There are two scenarios where elision works:
68 // * no output references, all input references have different LT
69 // * output references, exactly one input reference with same LT
70 // All lifetimes must be unnamed, 'static or defined without bounds on the
71 // level of the current item.
74 let allowed_lts = allowed_lts_from(named_lts);
76 // these will collect all the lifetimes for references in arg/return types
77 let mut input_visitor = RefVisitor(Vec::new());
78 let mut output_visitor = RefVisitor(Vec::new());
80 // extract lifetime in "self" argument for methods (there is a "self" argument
81 // in func.inputs, but its type is TyInfer)
82 if let Some(slf) = slf {
84 SelfRegion(ref opt_lt, _, _) => input_visitor.record(opt_lt),
85 SelfExplicit(ref ty, _) => walk_ty(&mut input_visitor, ty),
89 // extract lifetimes in input argument types
90 for arg in &func.inputs {
91 walk_ty(&mut input_visitor, &arg.ty);
92 if let TyRptr(None, _) = arg.ty.node {
93 input_visitor.record(&None);
96 // extract lifetimes in output type
97 if let Return(ref ty) = func.output {
98 walk_ty(&mut output_visitor, ty);
101 let input_lts = input_visitor.into_vec();
102 let output_lts = output_visitor.into_vec();
104 // check for lifetimes from higher scopes
105 for lt in input_lts.iter().chain(output_lts.iter()) {
106 if !allowed_lts.contains(lt) {
111 // no input lifetimes? easy case!
112 if input_lts.is_empty() {
114 } else if output_lts.is_empty() {
115 // no output lifetimes, check distinctness of input lifetimes
117 // only unnamed and static, ok
118 if input_lts.iter().all(|lt| *lt == Unnamed || *lt == Static) {
121 // we have no output reference, so we only need all distinct lifetimes
122 if input_lts.len() == unique_lifetimes(&input_lts) {
126 // we have output references, so we need one input reference,
127 // and all output lifetimes must be the same
128 if unique_lifetimes(&output_lts) > 1 {
131 if input_lts.len() == 1 {
132 match (&input_lts[0], &output_lts[0]) {
133 (&Named(n1), &Named(n2)) if n1 == n2 => { return true; }
134 (&Named(_), &Unnamed) => { return true; }
135 (&Unnamed, &Named(_)) => { return true; }
136 _ => { } // already elided, different named lifetimes
137 // or something static going on
144 fn allowed_lts_from(named_lts: &[LifetimeDef]) -> HashSet<RefLt> {
145 let mut allowed_lts = HashSet::new();
146 for lt in named_lts {
147 if lt.bounds.is_empty() {
148 allowed_lts.insert(Named(lt.lifetime.name));
151 allowed_lts.insert(Unnamed);
152 allowed_lts.insert(Static);
156 /// Number of unique lifetimes in the given vector.
157 fn unique_lifetimes(lts: &[RefLt]) -> usize {
158 lts.iter().collect::<HashSet<_>>().len()
161 /// A visitor usable for rustc_front::visit::walk_ty().
162 struct RefVisitor(Vec<RefLt>);
165 fn record(&mut self, lifetime: &Option<Lifetime>) {
166 if let &Some(ref lt) = lifetime {
167 if lt.name.as_str() == "'static" {
170 self.0.push(Named(lt.name));
173 self.0.push(Unnamed);
177 fn into_vec(self) -> Vec<RefLt> {
182 impl<'v> Visitor<'v> for RefVisitor {
183 // for lifetimes as parameters of generics
184 fn visit_lifetime(&mut self, lifetime: &'v Lifetime) {
185 self.record(&Some(*lifetime));
188 fn visit_ty(&mut self, ty: &'v Ty) {
189 if let TyRptr(None, _) = ty.node {
196 /// Are any lifetimes mentioned in the `where` clause? If yes, we don't try to
197 /// reason about elision.
198 fn has_where_lifetimes(where_clause: &WhereClause) -> bool {
199 for predicate in &where_clause.predicates {
201 WherePredicate::RegionPredicate(..) => return true,
202 WherePredicate::BoundPredicate(ref pred) => {
203 // a predicate like F: Trait or F: for<'a> Trait<'a>
204 let mut visitor = RefVisitor(Vec::new());
205 // walk the type F, it may not contain LT refs
206 walk_ty(&mut visitor, &pred.bounded_ty);
207 if !visitor.0.is_empty() { return true; }
208 // if the bounds define new lifetimes, they are fine to occur
209 let allowed_lts = allowed_lts_from(&pred.bound_lifetimes);
210 // now walk the bounds
211 for bound in pred.bounds.iter() {
212 walk_ty_param_bound(&mut visitor, bound);
214 // and check that all lifetimes are allowed
215 for lt in visitor.into_vec() {
216 if !allowed_lts.contains(<) {
221 WherePredicate::EqPredicate(ref pred) => {
222 let mut visitor = RefVisitor(Vec::new());
223 walk_ty(&mut visitor, &pred.ty);
224 if !visitor.0.is_empty() { return true; }