]> git.lizzy.rs Git - rust.git/blob - src/lifetimes.rs
67dfabe2569cacf3053c5eaedc2bdf9161849318
[rust.git] / src / lifetimes.rs
1 use reexport::*;
2 use rustc::lint::*;
3 use rustc::hir::def::Def;
4 use rustc::hir::*;
5 use rustc::hir::intravisit::{Visitor, walk_ty, walk_ty_param_bound, walk_fn_decl, walk_generics};
6 use std::collections::{HashSet, HashMap};
7 use syntax::codemap::Span;
8 use utils::{in_external_macro, span_lint};
9
10 /// **What it does:** This lint checks for lifetime annotations which can be removed by relying on lifetime elision.
11 ///
12 /// **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.
13 ///
14 /// **Known problems:** Potential false negatives: we bail out if the function has a `where` clause where lifetimes are mentioned.
15 ///
16 /// **Example:** `fn in_and_out<'a>(x: &'a u8, y: u8) -> &'a u8 { x }`
17 declare_lint! {
18     pub NEEDLESS_LIFETIMES,
19     Warn,
20     "using explicit lifetimes for references in function arguments when elision rules \
21      would allow omitting them"
22 }
23
24 /// **What it does:** This lint checks for lifetimes in generics that are never used anywhere else.
25 ///
26 /// **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.
27 ///
28 /// **Known problems:** None
29 ///
30 /// **Example:** `fn unused_lifetime<'a>(x: u8) { .. }`
31 declare_lint! {
32     pub UNUSED_LIFETIMES,
33     Warn,
34     "unused lifetimes in function definitions"
35 }
36
37 #[derive(Copy,Clone)]
38 pub struct LifetimePass;
39
40 impl LintPass for LifetimePass {
41     fn get_lints(&self) -> LintArray {
42         lint_array!(NEEDLESS_LIFETIMES, UNUSED_LIFETIMES)
43     }
44 }
45
46 impl LateLintPass for LifetimePass {
47     fn check_item(&mut self, cx: &LateContext, item: &Item) {
48         if let ItemFn(ref decl, _, _, _, ref generics, _) = item.node {
49             check_fn_inner(cx, decl, None, &generics, item.span);
50         }
51     }
52
53     fn check_impl_item(&mut self, cx: &LateContext, item: &ImplItem) {
54         if let ImplItemKind::Method(ref sig, _) = item.node {
55             check_fn_inner(cx, &sig.decl, Some(&sig.explicit_self), &sig.generics, item.span);
56         }
57     }
58
59     fn check_trait_item(&mut self, cx: &LateContext, item: &TraitItem) {
60         if let MethodTraitItem(ref sig, _) = item.node {
61             check_fn_inner(cx, &sig.decl, Some(&sig.explicit_self), &sig.generics, item.span);
62         }
63     }
64 }
65
66 /// The lifetime of a &-reference.
67 #[derive(PartialEq, Eq, Hash, Debug)]
68 enum RefLt {
69     Unnamed,
70     Static,
71     Named(Name),
72 }
73
74 fn bound_lifetimes(bound: &TyParamBound) -> Option<HirVec<&Lifetime>> {
75     if let TraitTyParamBound(ref trait_ref, _) = *bound {
76         let lt = trait_ref.trait_ref
77                           .path
78                           .segments
79                           .last()
80                           .expect("a path must have at least one segment")
81                           .parameters
82                           .lifetimes();
83
84         Some(lt)
85     } else {
86         None
87     }
88 }
89
90 fn check_fn_inner(cx: &LateContext, decl: &FnDecl, slf: Option<&ExplicitSelf>, generics: &Generics, span: Span) {
91     if in_external_macro(cx, span) || has_where_lifetimes(cx, &generics.where_clause) {
92         return;
93     }
94
95     let bounds_lts = generics.ty_params
96                              .iter()
97                              .flat_map(|ref typ| typ.bounds.iter().filter_map(bound_lifetimes).flat_map(|lts| lts));
98
99     if could_use_elision(cx, decl, slf, &generics.lifetimes, bounds_lts) {
100         span_lint(cx,
101                   NEEDLESS_LIFETIMES,
102                   span,
103                   "explicit lifetimes given in parameter types where they could be elided");
104     }
105     report_extra_lifetimes(cx, decl, &generics, slf);
106 }
107
108 fn could_use_elision<'a, T: Iterator<Item = &'a Lifetime>>(cx: &LateContext, func: &FnDecl, slf: Option<&ExplicitSelf>,
109                                                            named_lts: &[LifetimeDef], bounds_lts: T)
110                                                            -> bool {
111     // There are two scenarios where elision works:
112     // * no output references, all input references have different LT
113     // * output references, exactly one input reference with same LT
114     // All lifetimes must be unnamed, 'static or defined without bounds on the
115     // level of the current item.
116
117     // check named LTs
118     let allowed_lts = allowed_lts_from(named_lts);
119
120     // these will collect all the lifetimes for references in arg/return types
121     let mut input_visitor = RefVisitor::new(cx);
122     let mut output_visitor = RefVisitor::new(cx);
123
124     // extract lifetime in "self" argument for methods (there is a "self" argument
125     // in func.inputs, but its type is TyInfer)
126     if let Some(slf) = slf {
127         match slf.node {
128             SelfRegion(ref opt_lt, _, _) => input_visitor.record(opt_lt),
129             SelfExplicit(ref ty, _) => walk_ty(&mut input_visitor, ty),
130             _ => (),
131         }
132     }
133     // extract lifetimes in input argument types
134     for arg in &func.inputs {
135         input_visitor.visit_ty(&arg.ty);
136     }
137     // extract lifetimes in output type
138     if let Return(ref ty) = func.output {
139         output_visitor.visit_ty(ty);
140     }
141
142     let input_lts = lts_from_bounds(input_visitor.into_vec(), bounds_lts);
143     let output_lts = output_visitor.into_vec();
144
145     // check for lifetimes from higher scopes
146     for lt in input_lts.iter().chain(output_lts.iter()) {
147         if !allowed_lts.contains(lt) {
148             return false;
149         }
150     }
151
152     // no input lifetimes? easy case!
153     if input_lts.is_empty() {
154         false
155     } else if output_lts.is_empty() {
156         // no output lifetimes, check distinctness of input lifetimes
157
158         // only unnamed and static, ok
159         if input_lts.iter().all(|lt| *lt == RefLt::Unnamed || *lt == RefLt::Static) {
160             return false;
161         }
162         // we have no output reference, so we only need all distinct lifetimes
163         input_lts.len() == unique_lifetimes(&input_lts)
164     } else {
165         // we have output references, so we need one input reference,
166         // and all output lifetimes must be the same
167         if unique_lifetimes(&output_lts) > 1 {
168             return false;
169         }
170         if input_lts.len() == 1 {
171             match (&input_lts[0], &output_lts[0]) {
172                 (&RefLt::Named(n1), &RefLt::Named(n2)) if n1 == n2 => true,
173                 (&RefLt::Named(_), &RefLt::Unnamed) => true,
174                 _ => false, // already elided, different named lifetimes
175                 // or something static going on
176             }
177         } else {
178             false
179         }
180     }
181 }
182
183 fn allowed_lts_from(named_lts: &[LifetimeDef]) -> HashSet<RefLt> {
184     let mut allowed_lts = HashSet::new();
185     for lt in named_lts {
186         if lt.bounds.is_empty() {
187             allowed_lts.insert(RefLt::Named(lt.lifetime.name));
188         }
189     }
190     allowed_lts.insert(RefLt::Unnamed);
191     allowed_lts.insert(RefLt::Static);
192     allowed_lts
193 }
194
195 fn lts_from_bounds<'a, T: Iterator<Item = &'a Lifetime>>(mut vec: Vec<RefLt>, bounds_lts: T) -> Vec<RefLt> {
196     for lt in bounds_lts {
197         if lt.name.as_str() != "'static" {
198             vec.push(RefLt::Named(lt.name));
199         }
200     }
201
202     vec
203 }
204
205 /// Number of unique lifetimes in the given vector.
206 fn unique_lifetimes(lts: &[RefLt]) -> usize {
207     lts.iter().collect::<HashSet<_>>().len()
208 }
209
210 /// A visitor usable for `rustc_front::visit::walk_ty()`.
211 struct RefVisitor<'v, 't: 'v> {
212     cx: &'v LateContext<'v, 't>,
213     lts: Vec<RefLt>,
214 }
215
216 impl<'v, 't> RefVisitor<'v, 't> {
217     fn new(cx: &'v LateContext<'v, 't>) -> RefVisitor<'v, 't> {
218         RefVisitor {
219             cx: cx,
220             lts: Vec::new(),
221         }
222     }
223
224     fn record(&mut self, lifetime: &Option<Lifetime>) {
225         if let Some(ref lt) = *lifetime {
226             if lt.name.as_str() == "'static" {
227                 self.lts.push(RefLt::Static);
228             } else {
229                 self.lts.push(RefLt::Named(lt.name));
230             }
231         } else {
232             self.lts.push(RefLt::Unnamed);
233         }
234     }
235
236     fn into_vec(self) -> Vec<RefLt> {
237         self.lts
238     }
239
240     fn collect_anonymous_lifetimes(&mut self, path: &Path, ty: &Ty) {
241         let last_path_segment = path.segments.last().map(|s| &s.parameters);
242         if let Some(&AngleBracketedParameters(ref params)) = last_path_segment {
243             if params.lifetimes.is_empty() {
244                 if let Some(def) = self.cx.tcx.def_map.borrow().get(&ty.id).map(|r| r.full_def()) {
245                     match def {
246                         Def::TyAlias(def_id) |
247                         Def::Struct(def_id) => {
248                             let type_scheme = self.cx.tcx.lookup_item_type(def_id);
249                             for _ in type_scheme.generics.regions.as_slice() {
250                                 self.record(&None);
251                             }
252                         }
253                         Def::Trait(def_id) => {
254                             let trait_def = self.cx.tcx.trait_defs.borrow()[&def_id];
255                             for _ in &trait_def.generics.regions {
256                                 self.record(&None);
257                             }
258                         }
259                         _ => (),
260                     }
261                 }
262             }
263         }
264     }
265 }
266
267 impl<'v, 't> Visitor<'v> for RefVisitor<'v, 't> {
268     // for lifetimes as parameters of generics
269     fn visit_lifetime(&mut self, lifetime: &'v Lifetime) {
270         self.record(&Some(*lifetime));
271     }
272
273     fn visit_ty(&mut self, ty: &'v Ty) {
274         match ty.node {
275             TyRptr(None, _) => {
276                 self.record(&None);
277             }
278             TyPath(_, ref path) => {
279                 self.collect_anonymous_lifetimes(path, ty);
280             }
281             _ => (),
282         }
283         walk_ty(self, ty);
284     }
285 }
286
287 /// Are any lifetimes mentioned in the `where` clause? If yes, we don't try to
288 /// reason about elision.
289 fn has_where_lifetimes(cx: &LateContext, where_clause: &WhereClause) -> bool {
290     for predicate in &where_clause.predicates {
291         match *predicate {
292             WherePredicate::RegionPredicate(..) => return true,
293             WherePredicate::BoundPredicate(ref pred) => {
294                 // a predicate like F: Trait or F: for<'a> Trait<'a>
295                 let mut visitor = RefVisitor::new(cx);
296                 // walk the type F, it may not contain LT refs
297                 walk_ty(&mut visitor, &pred.bounded_ty);
298                 if !visitor.lts.is_empty() {
299                     return true;
300                 }
301                 // if the bounds define new lifetimes, they are fine to occur
302                 let allowed_lts = allowed_lts_from(&pred.bound_lifetimes);
303                 // now walk the bounds
304                 for bound in pred.bounds.iter() {
305                     walk_ty_param_bound(&mut visitor, bound);
306                 }
307                 // and check that all lifetimes are allowed
308                 for lt in visitor.into_vec() {
309                     if !allowed_lts.contains(&lt) {
310                         return true;
311                     }
312                 }
313             }
314             WherePredicate::EqPredicate(ref pred) => {
315                 let mut visitor = RefVisitor::new(cx);
316                 walk_ty(&mut visitor, &pred.ty);
317                 if !visitor.lts.is_empty() {
318                     return true;
319                 }
320             }
321         }
322     }
323     false
324 }
325
326 struct LifetimeChecker(HashMap<Name, Span>);
327
328 impl<'v> Visitor<'v> for LifetimeChecker {
329     // for lifetimes as parameters of generics
330     fn visit_lifetime(&mut self, lifetime: &'v Lifetime) {
331         self.0.remove(&lifetime.name);
332     }
333
334     fn visit_lifetime_def(&mut self, _: &'v LifetimeDef) {
335         // don't actually visit `<'a>` or `<'a: 'b>`
336         // we've already visited the `'a` declarations and
337         // don't want to spuriously remove them
338         // `'b` in `'a: 'b` is useless unless used elsewhere in
339         // a non-lifetime bound
340     }
341 }
342
343 fn report_extra_lifetimes(cx: &LateContext, func: &FnDecl, generics: &Generics, slf: Option<&ExplicitSelf>) {
344     let hs = generics.lifetimes
345                      .iter()
346                      .map(|lt| (lt.lifetime.name, lt.lifetime.span))
347                      .collect();
348     let mut checker = LifetimeChecker(hs);
349
350     walk_generics(&mut checker, generics);
351     walk_fn_decl(&mut checker, func);
352
353     if let Some(slf) = slf {
354         match slf.node {
355             SelfRegion(Some(ref lt), _, _) => checker.visit_lifetime(lt),
356             SelfExplicit(ref t, _) => walk_ty(&mut checker, t),
357             _ => (),
358         }
359     }
360
361     for &v in checker.0.values() {
362         span_lint(cx, UNUSED_LIFETIMES, v, "this lifetime isn't used in the function definition");
363     }
364 }