]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/lifetimes.rs
Merge branch 'master' into add-lints-aseert-checks
[rust.git] / clippy_lints / src / lifetimes.rs
1 use crate::reexport::*;
2 use crate::utils::{last_path_segment, span_lint};
3 use matches::matches;
4 use rustc::hir::def::Def;
5 use rustc::hir::intravisit::*;
6 use rustc::hir::*;
7 use rustc::lint::{in_external_macro, LateContext, LateLintPass, LintArray, LintContext, LintPass};
8 use rustc::{declare_tool_lint, lint_array};
9 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
10 use syntax::source_map::Span;
11 use syntax::symbol::keywords;
12
13 /// **What it does:** Checks for lifetime annotations which can be removed by
14 /// relying on lifetime elision.
15 ///
16 /// **Why is this bad?** The additional lifetimes make the code look more
17 /// complicated, while there is nothing out of the ordinary going on. Removing
18 /// them leads to more readable code.
19 ///
20 /// **Known problems:** Potential false negatives: we bail out if the function
21 /// has a `where` clause where lifetimes are mentioned.
22 ///
23 /// **Example:**
24 /// ```rust
25 /// fn in_and_out<'a>(x: &'a u8, y: u8) -> &'a u8 {
26 ///     x
27 /// }
28 /// ```
29 declare_clippy_lint! {
30 pub NEEDLESS_LIFETIMES,
31 complexity,
32 "using explicit lifetimes for references in function arguments when elision rules \
33  would allow omitting them"
34 }
35
36 /// **What it does:** Checks for lifetimes in generics that are never used
37 /// anywhere else.
38 ///
39 /// **Why is this bad?** The additional lifetimes make the code look more
40 /// complicated, while there is nothing out of the ordinary going on. Removing
41 /// them leads to more readable code.
42 ///
43 /// **Known problems:** None.
44 ///
45 /// **Example:**
46 /// ```rust
47 /// fn unused_lifetime<'a>(x: u8) {
48 ///     ..
49 /// }
50 /// ```
51 declare_clippy_lint! {
52     pub EXTRA_UNUSED_LIFETIMES,
53     complexity,
54     "unused lifetimes in function definitions"
55 }
56
57 #[derive(Copy, Clone)]
58 pub struct LifetimePass;
59
60 impl LintPass for LifetimePass {
61     fn get_lints(&self) -> LintArray {
62         lint_array!(NEEDLESS_LIFETIMES, EXTRA_UNUSED_LIFETIMES)
63     }
64 }
65
66 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for LifetimePass {
67     fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx Item) {
68         if let ItemKind::Fn(ref decl, _, ref generics, id) = item.node {
69             check_fn_inner(cx, decl, Some(id), generics, item.span);
70         }
71     }
72
73     fn check_impl_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx ImplItem) {
74         if let ImplItemKind::Method(ref sig, id) = item.node {
75             check_fn_inner(cx, &sig.decl, Some(id), &item.generics, item.span);
76         }
77     }
78
79     fn check_trait_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx TraitItem) {
80         if let TraitItemKind::Method(ref sig, ref body) = item.node {
81             let body = match *body {
82                 TraitMethod::Required(_) => None,
83                 TraitMethod::Provided(id) => Some(id),
84             };
85             check_fn_inner(cx, &sig.decl, body, &item.generics, item.span);
86         }
87     }
88 }
89
90 /// The lifetime of a &-reference.
91 #[derive(PartialEq, Eq, Hash, Debug)]
92 enum RefLt {
93     Unnamed,
94     Static,
95     Named(Name),
96 }
97
98 fn check_fn_inner<'a, 'tcx>(
99     cx: &LateContext<'a, 'tcx>,
100     decl: &'tcx FnDecl,
101     body: Option<BodyId>,
102     generics: &'tcx Generics,
103     span: Span,
104 ) {
105     if in_external_macro(cx.sess(), span) || has_where_lifetimes(cx, &generics.where_clause) {
106         return;
107     }
108
109     let mut bounds_lts = Vec::new();
110     let types = generics.params.iter().filter(|param| match param.kind {
111         GenericParamKind::Type { .. } => true,
112         GenericParamKind::Lifetime { .. } => false,
113     });
114     for typ in types {
115         for bound in &typ.bounds {
116             let mut visitor = RefVisitor::new(cx);
117             walk_param_bound(&mut visitor, bound);
118             if visitor.lts.iter().any(|lt| matches!(lt, RefLt::Named(_))) {
119                 return;
120             }
121             if let GenericBound::Trait(ref trait_ref, _) = *bound {
122                 let params = &trait_ref
123                     .trait_ref
124                     .path
125                     .segments
126                     .last()
127                     .expect("a path must have at least one segment")
128                     .args;
129                 if let Some(ref params) = *params {
130                     let lifetimes = params.args.iter().filter_map(|arg| match arg {
131                         GenericArg::Lifetime(lt) => Some(lt),
132                         GenericArg::Type(_) => None,
133                     });
134                     for bound in lifetimes {
135                         if bound.name != LifetimeName::Static && !bound.is_elided() {
136                             return;
137                         }
138                         bounds_lts.push(bound);
139                     }
140                 }
141             }
142         }
143     }
144     if could_use_elision(cx, decl, body, &generics.params, bounds_lts) {
145         span_lint(
146             cx,
147             NEEDLESS_LIFETIMES,
148             span,
149             "explicit lifetimes given in parameter types where they could be elided \
150              (or replaced with `'_` if needed by type declaration)",
151         );
152     }
153     report_extra_lifetimes(cx, decl, generics);
154 }
155
156 fn could_use_elision<'a, 'tcx: 'a>(
157     cx: &LateContext<'a, 'tcx>,
158     func: &'tcx FnDecl,
159     body: Option<BodyId>,
160     named_generics: &'tcx [GenericParam],
161     bounds_lts: Vec<&'tcx Lifetime>,
162 ) -> bool {
163     // There are two scenarios where elision works:
164     // * no output references, all input references have different LT
165     // * output references, exactly one input reference with same LT
166     // All lifetimes must be unnamed, 'static or defined without bounds on the
167     // level of the current item.
168
169     // check named LTs
170     let allowed_lts = allowed_lts_from(named_generics);
171
172     // these will collect all the lifetimes for references in arg/return types
173     let mut input_visitor = RefVisitor::new(cx);
174     let mut output_visitor = RefVisitor::new(cx);
175
176     // extract lifetimes in input argument types
177     for arg in &func.inputs {
178         input_visitor.visit_ty(arg);
179     }
180     // extract lifetimes in output type
181     if let Return(ref ty) = func.output {
182         output_visitor.visit_ty(ty);
183     }
184
185     let input_lts = match input_visitor.into_vec() {
186         Some(lts) => lts_from_bounds(lts, bounds_lts.into_iter()),
187         None => return false,
188     };
189     let output_lts = match output_visitor.into_vec() {
190         Some(val) => val,
191         None => return false,
192     };
193
194     if let Some(body_id) = body {
195         let mut checker = BodyLifetimeChecker {
196             lifetimes_used_in_body: false,
197         };
198         checker.visit_expr(&cx.tcx.hir().body(body_id).value);
199         if checker.lifetimes_used_in_body {
200             return false;
201         }
202     }
203
204     // check for lifetimes from higher scopes
205     for lt in input_lts.iter().chain(output_lts.iter()) {
206         if !allowed_lts.contains(lt) {
207             return false;
208         }
209     }
210
211     // no input lifetimes? easy case!
212     if input_lts.is_empty() {
213         false
214     } else if output_lts.is_empty() {
215         // no output lifetimes, check distinctness of input lifetimes
216
217         // only unnamed and static, ok
218         let unnamed_and_static = input_lts.iter().all(|lt| *lt == RefLt::Unnamed || *lt == RefLt::Static);
219         if unnamed_and_static {
220             return false;
221         }
222         // we have no output reference, so we only need all distinct lifetimes
223         input_lts.len() == unique_lifetimes(&input_lts)
224     } else {
225         // we have output references, so we need one input reference,
226         // and all output lifetimes must be the same
227         if unique_lifetimes(&output_lts) > 1 {
228             return false;
229         }
230         if input_lts.len() == 1 {
231             match (&input_lts[0], &output_lts[0]) {
232                 (&RefLt::Named(n1), &RefLt::Named(n2)) if n1 == n2 => true,
233                 (&RefLt::Named(_), &RefLt::Unnamed) => true,
234                 _ => false, /* already elided, different named lifetimes
235                              * or something static going on */
236             }
237         } else {
238             false
239         }
240     }
241 }
242
243 fn allowed_lts_from(named_generics: &[GenericParam]) -> FxHashSet<RefLt> {
244     let mut allowed_lts = FxHashSet::default();
245     for par in named_generics.iter() {
246         if let GenericParamKind::Lifetime { .. } = par.kind {
247             if par.bounds.is_empty() {
248                 allowed_lts.insert(RefLt::Named(par.name.ident().name));
249             }
250         }
251     }
252     allowed_lts.insert(RefLt::Unnamed);
253     allowed_lts.insert(RefLt::Static);
254     allowed_lts
255 }
256
257 fn lts_from_bounds<'a, T: Iterator<Item = &'a Lifetime>>(mut vec: Vec<RefLt>, bounds_lts: T) -> Vec<RefLt> {
258     for lt in bounds_lts {
259         if lt.name != LifetimeName::Static {
260             vec.push(RefLt::Named(lt.name.ident().name));
261         }
262     }
263
264     vec
265 }
266
267 /// Number of unique lifetimes in the given vector.
268 fn unique_lifetimes(lts: &[RefLt]) -> usize {
269     lts.iter().collect::<FxHashSet<_>>().len()
270 }
271
272 /// A visitor usable for `rustc_front::visit::walk_ty()`.
273 struct RefVisitor<'a, 'tcx: 'a> {
274     cx: &'a LateContext<'a, 'tcx>,
275     lts: Vec<RefLt>,
276     abort: bool,
277 }
278
279 impl<'v, 't> RefVisitor<'v, 't> {
280     fn new(cx: &'v LateContext<'v, 't>) -> Self {
281         Self {
282             cx,
283             lts: Vec::new(),
284             abort: false,
285         }
286     }
287
288     fn record(&mut self, lifetime: &Option<Lifetime>) {
289         if let Some(ref lt) = *lifetime {
290             if lt.name == LifetimeName::Static {
291                 self.lts.push(RefLt::Static);
292             } else if lt.is_elided() {
293                 self.lts.push(RefLt::Unnamed);
294             } else {
295                 self.lts.push(RefLt::Named(lt.name.ident().name));
296             }
297         } else {
298             self.lts.push(RefLt::Unnamed);
299         }
300     }
301
302     fn into_vec(self) -> Option<Vec<RefLt>> {
303         if self.abort {
304             None
305         } else {
306             Some(self.lts)
307         }
308     }
309
310     fn collect_anonymous_lifetimes(&mut self, qpath: &QPath, ty: &Ty) {
311         if let Some(ref last_path_segment) = last_path_segment(qpath).args {
312             if !last_path_segment.parenthesized
313                 && !last_path_segment.args.iter().any(|arg| match arg {
314                     GenericArg::Lifetime(_) => true,
315                     GenericArg::Type(_) => false,
316                 })
317             {
318                 let hir_id = self.cx.tcx.hir().node_to_hir_id(ty.id);
319                 match self.cx.tables.qpath_def(qpath, hir_id) {
320                     Def::TyAlias(def_id) | Def::Struct(def_id) => {
321                         let generics = self.cx.tcx.generics_of(def_id);
322                         for _ in generics.params.as_slice() {
323                             self.record(&None);
324                         }
325                     },
326                     Def::Trait(def_id) => {
327                         let trait_def = self.cx.tcx.trait_def(def_id);
328                         for _ in &self.cx.tcx.generics_of(trait_def.def_id).params {
329                             self.record(&None);
330                         }
331                     },
332                     _ => (),
333                 }
334             }
335         }
336     }
337 }
338
339 impl<'a, 'tcx> Visitor<'tcx> for RefVisitor<'a, 'tcx> {
340     // for lifetimes as parameters of generics
341     fn visit_lifetime(&mut self, lifetime: &'tcx Lifetime) {
342         self.record(&Some(*lifetime));
343     }
344
345     fn visit_ty(&mut self, ty: &'tcx Ty) {
346         match ty.node {
347             TyKind::Rptr(ref lt, _) if lt.is_elided() => {
348                 self.record(&None);
349             },
350             TyKind::Path(ref path) => {
351                 self.collect_anonymous_lifetimes(path, ty);
352             },
353             TyKind::Def(item, _) => {
354                 if let ItemKind::Existential(ref exist_ty) = self.cx.tcx.hir().expect_item(item.id).node {
355                     for bound in &exist_ty.bounds {
356                         if let GenericBound::Outlives(_) = *bound {
357                             self.record(&None);
358                         }
359                     }
360                 } else {
361                     unreachable!()
362                 }
363                 walk_ty(self, ty);
364             },
365             TyKind::TraitObject(ref bounds, ref lt) => {
366                 if !lt.is_elided() {
367                     self.abort = true;
368                 }
369                 for bound in bounds {
370                     self.visit_poly_trait_ref(bound, TraitBoundModifier::None);
371                 }
372                 return;
373             },
374             _ => (),
375         }
376         walk_ty(self, ty);
377     }
378     fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
379         NestedVisitorMap::None
380     }
381 }
382
383 /// Are any lifetimes mentioned in the `where` clause? If yes, we don't try to
384 /// reason about elision.
385 fn has_where_lifetimes<'a, 'tcx: 'a>(cx: &LateContext<'a, 'tcx>, where_clause: &'tcx WhereClause) -> bool {
386     for predicate in &where_clause.predicates {
387         match *predicate {
388             WherePredicate::RegionPredicate(..) => return true,
389             WherePredicate::BoundPredicate(ref pred) => {
390                 // a predicate like F: Trait or F: for<'a> Trait<'a>
391                 let mut visitor = RefVisitor::new(cx);
392                 // walk the type F, it may not contain LT refs
393                 walk_ty(&mut visitor, &pred.bounded_ty);
394                 if !visitor.lts.is_empty() {
395                     return true;
396                 }
397                 // if the bounds define new lifetimes, they are fine to occur
398                 let allowed_lts = allowed_lts_from(&pred.bound_generic_params);
399                 // now walk the bounds
400                 for bound in pred.bounds.iter() {
401                     walk_param_bound(&mut visitor, bound);
402                 }
403                 // and check that all lifetimes are allowed
404                 match visitor.into_vec() {
405                     None => return false,
406                     Some(lts) => {
407                         for lt in lts {
408                             if !allowed_lts.contains(&lt) {
409                                 return true;
410                             }
411                         }
412                     },
413                 }
414             },
415             WherePredicate::EqPredicate(ref pred) => {
416                 let mut visitor = RefVisitor::new(cx);
417                 walk_ty(&mut visitor, &pred.lhs_ty);
418                 walk_ty(&mut visitor, &pred.rhs_ty);
419                 if !visitor.lts.is_empty() {
420                     return true;
421                 }
422             },
423         }
424     }
425     false
426 }
427
428 struct LifetimeChecker {
429     map: FxHashMap<Name, Span>,
430 }
431
432 impl<'tcx> Visitor<'tcx> for LifetimeChecker {
433     // for lifetimes as parameters of generics
434     fn visit_lifetime(&mut self, lifetime: &'tcx Lifetime) {
435         self.map.remove(&lifetime.name.ident().name);
436     }
437
438     fn visit_generic_param(&mut self, param: &'tcx GenericParam) {
439         // don't actually visit `<'a>` or `<'a: 'b>`
440         // we've already visited the `'a` declarations and
441         // don't want to spuriously remove them
442         // `'b` in `'a: 'b` is useless unless used elsewhere in
443         // a non-lifetime bound
444         if let GenericParamKind::Type { .. } = param.kind {
445             walk_generic_param(self, param)
446         }
447     }
448     fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
449         NestedVisitorMap::None
450     }
451 }
452
453 fn report_extra_lifetimes<'a, 'tcx: 'a>(cx: &LateContext<'a, 'tcx>, func: &'tcx FnDecl, generics: &'tcx Generics) {
454     let hs = generics
455         .params
456         .iter()
457         .filter_map(|par| match par.kind {
458             GenericParamKind::Lifetime { .. } => Some((par.name.ident().name, par.span)),
459             _ => None,
460         })
461         .collect();
462     let mut checker = LifetimeChecker { map: hs };
463
464     walk_generics(&mut checker, generics);
465     walk_fn_decl(&mut checker, func);
466
467     for &v in checker.map.values() {
468         span_lint(
469             cx,
470             EXTRA_UNUSED_LIFETIMES,
471             v,
472             "this lifetime isn't used in the function definition",
473         );
474     }
475 }
476
477 struct BodyLifetimeChecker {
478     lifetimes_used_in_body: bool,
479 }
480
481 impl<'tcx> Visitor<'tcx> for BodyLifetimeChecker {
482     // for lifetimes as parameters of generics
483     fn visit_lifetime(&mut self, lifetime: &'tcx Lifetime) {
484         if lifetime.name.ident().name != keywords::Invalid.name() && lifetime.name.ident().name != "'static" {
485             self.lifetimes_used_in_body = true;
486         }
487     }
488
489     fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
490         NestedVisitorMap::None
491     }
492 }