1 use rustc_front::hir::*;
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};
9 use utils::{in_external_macro, span_lint};
11 /// **What it does:** This lint checks for lifetime annotations which can be removed by relying on lifetime elision. It is `Warn` by default.
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.
15 /// **Known problems:** Potential false negatives: we bail out if the function has a `where` clause where lifetimes are mentioned.
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");
22 declare_lint!(pub UNUSED_LIFETIMES, Warn,
23 "unused lifetimes in function definitions");
26 pub struct LifetimePass;
28 impl LintPass for LifetimePass {
29 fn get_lints(&self) -> LintArray {
30 lint_array!(NEEDLESS_LIFETIMES, UNUSED_LIFETIMES)
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);
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);
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);
56 /// The lifetime of a &-reference.
57 #[derive(PartialEq, Eq, Hash, Debug)]
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) {
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");
74 report_extra_lifetimes(cx, decl, &generics.lifetimes);
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.
86 let allowed_lts = allowed_lts_from(named_lts);
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);
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 {
96 SelfRegion(ref opt_lt, _, _) => input_visitor.record(opt_lt),
97 SelfExplicit(ref ty, _) => walk_ty(&mut input_visitor, ty),
101 // extract lifetimes in input argument types
102 for arg in &func.inputs {
103 input_visitor.visit_ty(&arg.ty);
105 // extract lifetimes in output type
106 if let Return(ref ty) = func.output {
107 output_visitor.visit_ty(ty);
110 let input_lts = input_visitor.into_vec();
111 let output_lts = output_visitor.into_vec();
113 // check for lifetimes from higher scopes
114 for lt in input_lts.iter().chain(output_lts.iter()) {
115 if !allowed_lts.contains(lt) {
120 // no input lifetimes? easy case!
121 if input_lts.is_empty() {
123 } else if output_lts.is_empty() {
124 // no output lifetimes, check distinctness of input lifetimes
126 // only unnamed and static, ok
127 if input_lts.iter().all(|lt| *lt == Unnamed || *lt == Static) {
130 // we have no output reference, so we only need all distinct lifetimes
131 input_lts.len() == unique_lifetimes(&input_lts)
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 {
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
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));
159 allowed_lts.insert(Unnamed);
160 allowed_lts.insert(Static);
164 /// Number of unique lifetimes in the given vector.
165 fn unique_lifetimes(lts: &[RefLt]) -> usize {
166 lts.iter().collect::<HashSet<_>>().len()
169 /// A visitor usable for rustc_front::visit::walk_ty().
170 struct RefVisitor<'v, 't: 'v> {
171 cx: &'v LateContext<'v, 't>, // context reference
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() }
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);
185 self.lts.push(Named(lt.name));
188 self.lts.push(Unnamed);
192 fn into_vec(self) -> Vec<RefLt> {
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()) {
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() {
208 DefTrait(def_id) => {
209 let trait_def = self.cx.tcx.trait_defs.borrow()[&def_id];
210 for _ in &trait_def.generics.regions {
222 impl<'v, 't> Visitor<'v> for RefVisitor<'v, 't> {
224 // for lifetimes as parameters of generics
225 fn visit_lifetime(&mut self, lifetime: &'v Lifetime) {
226 self.record(&Some(*lifetime));
229 fn visit_ty(&mut self, ty: &'v Ty) {
234 TyPath(_, ref path) => {
235 self.collect_anonymous_lifetimes(path, ty);
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 {
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);
261 // and check that all lifetimes are allowed
262 for lt in visitor.into_vec() {
263 if !allowed_lts.contains(<) {
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; }
278 struct LifetimeChecker(HashMap<Name, Span>);
280 impl<'v> Visitor<'v> for LifetimeChecker {
282 // for lifetimes as parameters of generics
283 fn visit_lifetime(&mut self, lifetime: &'v Lifetime) {
284 self.0.remove(&lifetime.name);
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");