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