]> git.lizzy.rs Git - rust.git/blob - src/lifetimes.rs
Merge pull request #940 from oli-obk/simplify/mut_mut
[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, 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, &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, &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, 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, &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);
106 }
107
108 fn could_use_elision<'a, T: Iterator<Item = &'a Lifetime>>(cx: &LateContext, func: &FnDecl,
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 lifetimes in input argument types
125     for arg in &func.inputs {
126         input_visitor.visit_ty(&arg.ty);
127     }
128     // extract lifetimes in output type
129     if let Return(ref ty) = func.output {
130         output_visitor.visit_ty(ty);
131     }
132
133     let input_lts = lts_from_bounds(input_visitor.into_vec(), bounds_lts);
134     let output_lts = output_visitor.into_vec();
135
136     // check for lifetimes from higher scopes
137     for lt in input_lts.iter().chain(output_lts.iter()) {
138         if !allowed_lts.contains(lt) {
139             return false;
140         }
141     }
142
143     // no input lifetimes? easy case!
144     if input_lts.is_empty() {
145         false
146     } else if output_lts.is_empty() {
147         // no output lifetimes, check distinctness of input lifetimes
148
149         // only unnamed and static, ok
150         if input_lts.iter().all(|lt| *lt == RefLt::Unnamed || *lt == RefLt::Static) {
151             return false;
152         }
153         // we have no output reference, so we only need all distinct lifetimes
154         input_lts.len() == unique_lifetimes(&input_lts)
155     } else {
156         // we have output references, so we need one input reference,
157         // and all output lifetimes must be the same
158         if unique_lifetimes(&output_lts) > 1 {
159             return false;
160         }
161         if input_lts.len() == 1 {
162             match (&input_lts[0], &output_lts[0]) {
163                 (&RefLt::Named(n1), &RefLt::Named(n2)) if n1 == n2 => true,
164                 (&RefLt::Named(_), &RefLt::Unnamed) => true,
165                 _ => false, // already elided, different named lifetimes
166                 // or something static going on
167             }
168         } else {
169             false
170         }
171     }
172 }
173
174 fn allowed_lts_from(named_lts: &[LifetimeDef]) -> HashSet<RefLt> {
175     let mut allowed_lts = HashSet::new();
176     for lt in named_lts {
177         if lt.bounds.is_empty() {
178             allowed_lts.insert(RefLt::Named(lt.lifetime.name));
179         }
180     }
181     allowed_lts.insert(RefLt::Unnamed);
182     allowed_lts.insert(RefLt::Static);
183     allowed_lts
184 }
185
186 fn lts_from_bounds<'a, T: Iterator<Item = &'a Lifetime>>(mut vec: Vec<RefLt>, bounds_lts: T) -> Vec<RefLt> {
187     for lt in bounds_lts {
188         if lt.name.as_str() != "'static" {
189             vec.push(RefLt::Named(lt.name));
190         }
191     }
192
193     vec
194 }
195
196 /// Number of unique lifetimes in the given vector.
197 fn unique_lifetimes(lts: &[RefLt]) -> usize {
198     lts.iter().collect::<HashSet<_>>().len()
199 }
200
201 /// A visitor usable for `rustc_front::visit::walk_ty()`.
202 struct RefVisitor<'v, 't: 'v> {
203     cx: &'v LateContext<'v, 't>,
204     lts: Vec<RefLt>,
205 }
206
207 impl<'v, 't> RefVisitor<'v, 't> {
208     fn new(cx: &'v LateContext<'v, 't>) -> RefVisitor<'v, 't> {
209         RefVisitor {
210             cx: cx,
211             lts: Vec::new(),
212         }
213     }
214
215     fn record(&mut self, lifetime: &Option<Lifetime>) {
216         if let Some(ref lt) = *lifetime {
217             if lt.name.as_str() == "'static" {
218                 self.lts.push(RefLt::Static);
219             } else {
220                 self.lts.push(RefLt::Named(lt.name));
221             }
222         } else {
223             self.lts.push(RefLt::Unnamed);
224         }
225     }
226
227     fn into_vec(self) -> Vec<RefLt> {
228         self.lts
229     }
230
231     fn collect_anonymous_lifetimes(&mut self, path: &Path, ty: &Ty) {
232         let last_path_segment = path.segments.last().map(|s| &s.parameters);
233         if let Some(&AngleBracketedParameters(ref params)) = last_path_segment {
234             if params.lifetimes.is_empty() {
235                 if let Some(def) = self.cx.tcx.def_map.borrow().get(&ty.id).map(|r| r.full_def()) {
236                     match def {
237                         Def::TyAlias(def_id) |
238                         Def::Struct(def_id) => {
239                             let type_scheme = self.cx.tcx.lookup_item_type(def_id);
240                             for _ in type_scheme.generics.regions.as_slice() {
241                                 self.record(&None);
242                             }
243                         }
244                         Def::Trait(def_id) => {
245                             let trait_def = self.cx.tcx.trait_defs.borrow()[&def_id];
246                             for _ in &trait_def.generics.regions {
247                                 self.record(&None);
248                             }
249                         }
250                         _ => (),
251                     }
252                 }
253             }
254         }
255     }
256 }
257
258 impl<'v, 't> Visitor<'v> for RefVisitor<'v, 't> {
259     // for lifetimes as parameters of generics
260     fn visit_lifetime(&mut self, lifetime: &'v Lifetime) {
261         self.record(&Some(*lifetime));
262     }
263
264     fn visit_ty(&mut self, ty: &'v Ty) {
265         match ty.node {
266             TyRptr(None, _) => {
267                 self.record(&None);
268             }
269             TyPath(_, ref path) => {
270                 self.collect_anonymous_lifetimes(path, ty);
271             }
272             _ => (),
273         }
274         walk_ty(self, ty);
275     }
276 }
277
278 /// Are any lifetimes mentioned in the `where` clause? If yes, we don't try to
279 /// reason about elision.
280 fn has_where_lifetimes(cx: &LateContext, where_clause: &WhereClause) -> bool {
281     for predicate in &where_clause.predicates {
282         match *predicate {
283             WherePredicate::RegionPredicate(..) => return true,
284             WherePredicate::BoundPredicate(ref pred) => {
285                 // a predicate like F: Trait or F: for<'a> Trait<'a>
286                 let mut visitor = RefVisitor::new(cx);
287                 // walk the type F, it may not contain LT refs
288                 walk_ty(&mut visitor, &pred.bounded_ty);
289                 if !visitor.lts.is_empty() {
290                     return true;
291                 }
292                 // if the bounds define new lifetimes, they are fine to occur
293                 let allowed_lts = allowed_lts_from(&pred.bound_lifetimes);
294                 // now walk the bounds
295                 for bound in pred.bounds.iter() {
296                     walk_ty_param_bound(&mut visitor, bound);
297                 }
298                 // and check that all lifetimes are allowed
299                 for lt in visitor.into_vec() {
300                     if !allowed_lts.contains(&lt) {
301                         return true;
302                     }
303                 }
304             }
305             WherePredicate::EqPredicate(ref pred) => {
306                 let mut visitor = RefVisitor::new(cx);
307                 walk_ty(&mut visitor, &pred.ty);
308                 if !visitor.lts.is_empty() {
309                     return true;
310                 }
311             }
312         }
313     }
314     false
315 }
316
317 struct LifetimeChecker(HashMap<Name, Span>);
318
319 impl<'v> Visitor<'v> for LifetimeChecker {
320     // for lifetimes as parameters of generics
321     fn visit_lifetime(&mut self, lifetime: &'v Lifetime) {
322         self.0.remove(&lifetime.name);
323     }
324
325     fn visit_lifetime_def(&mut self, _: &'v LifetimeDef) {
326         // don't actually visit `<'a>` or `<'a: 'b>`
327         // we've already visited the `'a` declarations and
328         // don't want to spuriously remove them
329         // `'b` in `'a: 'b` is useless unless used elsewhere in
330         // a non-lifetime bound
331     }
332 }
333
334 fn report_extra_lifetimes(cx: &LateContext, func: &FnDecl, generics: &Generics) {
335     let hs = generics.lifetimes
336                      .iter()
337                      .map(|lt| (lt.lifetime.name, lt.lifetime.span))
338                      .collect();
339     let mut checker = LifetimeChecker(hs);
340
341     walk_generics(&mut checker, generics);
342     walk_fn_decl(&mut checker, func);
343
344     for &v in checker.0.values() {
345         span_lint(cx, UNUSED_LIFETIMES, v, "this lifetime isn't used in the function definition");
346     }
347 }