]> git.lizzy.rs Git - rust.git/blob - src/lifetimes.rs
Fix rustup fallout: lifetimes false positives
[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::visit::{Visitor, walk_ty, walk_ty_param_bound};
6 use std::collections::HashSet;
7
8 use utils::{in_external_macro, span_lint};
9
10 declare_lint!(pub NEEDLESS_LIFETIMES, Warn,
11               "using explicit lifetimes for references in function arguments when elision rules \
12                would allow omitting them");
13
14 #[derive(Copy,Clone)]
15 pub struct LifetimePass;
16
17 impl LintPass for LifetimePass {
18     fn get_lints(&self) -> LintArray {
19         lint_array!(NEEDLESS_LIFETIMES)
20     }
21 }
22
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);
27         }
28     }
29
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);
34         }
35     }
36
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);
41         }
42     }
43 }
44
45 /// The lifetime of a &-reference.
46 #[derive(PartialEq, Eq, Hash, Debug)]
47 enum RefLt {
48     Unnamed,
49     Static,
50     Named(Name),
51 }
52 use self::RefLt::*;
53
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) {
57         return;
58     }
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");
62     }
63 }
64
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.
72
73     // check named LTs
74     let allowed_lts = allowed_lts_from(named_lts);
75
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());
79
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 {
83         match slf.node {
84             SelfRegion(ref opt_lt, _, _) => input_visitor.record(opt_lt),
85             SelfExplicit(ref ty, _) => walk_ty(&mut input_visitor, ty),
86             _ => { }
87         }
88     }
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);
94         }
95     }
96     // extract lifetimes in output type
97     if let Return(ref ty) = func.output {
98         walk_ty(&mut output_visitor, ty);
99     }
100
101     let input_lts = input_visitor.into_vec();
102     let output_lts = output_visitor.into_vec();
103
104     // check for lifetimes from higher scopes
105     for lt in input_lts.iter().chain(output_lts.iter()) {
106         if !allowed_lts.contains(lt) {
107             return false;
108         }
109     }
110
111     // no input lifetimes? easy case!
112     if input_lts.is_empty() {
113         return false;
114     } else if output_lts.is_empty() {
115         // no output lifetimes, check distinctness of input lifetimes
116
117         // only unnamed and static, ok
118         if input_lts.iter().all(|lt| *lt == Unnamed || *lt == Static) {
119             return false;
120         }
121         // we have no output reference, so we only need all distinct lifetimes
122         if input_lts.len() == unique_lifetimes(&input_lts) {
123             return true;
124         }
125     } else {
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 {
129             return false;
130         }
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
138             }
139         }
140     }
141     false
142 }
143
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));
149         }
150     }
151     allowed_lts.insert(Unnamed);
152     allowed_lts.insert(Static);
153     allowed_lts
154 }
155
156 /// Number of unique lifetimes in the given vector.
157 fn unique_lifetimes(lts: &[RefLt]) -> usize {
158     lts.iter().collect::<HashSet<_>>().len()
159 }
160
161 /// A visitor usable for rustc_front::visit::walk_ty().
162 struct RefVisitor(Vec<RefLt>);
163
164 impl RefVisitor {
165     fn record(&mut self, lifetime: &Option<Lifetime>) {
166         if let &Some(ref lt) = lifetime {
167             if lt.name.as_str() == "'static" {
168                 self.0.push(Static);
169             } else {
170                 self.0.push(Named(lt.name));
171             }
172         } else {
173             self.0.push(Unnamed);
174         }
175     }
176
177     fn into_vec(self) -> Vec<RefLt> {
178         self.0
179     }
180 }
181
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));
186     }
187
188     fn visit_ty(&mut self, ty: &'v Ty) {
189         if let TyRptr(None, _) = ty.node {
190             self.record(&None);
191         }
192         walk_ty(self, ty);
193     }
194 }
195
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 {
200         match *predicate {
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);
213                 }
214                 // and check that all lifetimes are allowed
215                 for lt in visitor.into_vec() {
216                     if !allowed_lts.contains(&lt) {
217                         return true;
218                     }
219                 }
220             }
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; }
225             }
226         }
227     }
228     false
229 }