]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/lifetimes.rs
Merge commit 'f4850f7292efa33759b4f7f9b7621268979e9914' into clippyup
[rust.git] / clippy_lints / src / lifetimes.rs
1 use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
2 use clippy_utils::trait_ref_of_method;
3 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
4 use rustc_hir::intravisit::nested_filter::{self as hir_nested_filter, NestedFilter};
5 use rustc_hir::intravisit::{
6     walk_fn_decl, walk_generic_param, walk_generics, walk_impl_item_ref, walk_item, walk_param_bound,
7     walk_poly_trait_ref, walk_trait_ref, walk_ty, Visitor,
8 };
9 use rustc_hir::lang_items;
10 use rustc_hir::FnRetTy::Return;
11 use rustc_hir::{
12     BareFnTy, BodyId, FnDecl, GenericArg, GenericBound, GenericParam, GenericParamKind, Generics, Impl, ImplItem,
13     ImplItemKind, Item, ItemKind, Lifetime, LifetimeName, ParamName, PolyTraitRef, PredicateOrigin, TraitFn, TraitItem,
14     TraitItemKind, Ty, TyKind, WherePredicate,
15 };
16 use rustc_lint::{LateContext, LateLintPass};
17 use rustc_middle::hir::nested_filter as middle_nested_filter;
18 use rustc_middle::ty::TyCtxt;
19 use rustc_session::{declare_lint_pass, declare_tool_lint};
20 use rustc_span::def_id::LocalDefId;
21 use rustc_span::source_map::Span;
22 use rustc_span::symbol::{kw, Ident, Symbol};
23
24 declare_clippy_lint! {
25     /// ### What it does
26     /// Checks for lifetime annotations which can be removed by
27     /// relying on lifetime elision.
28     ///
29     /// ### Why is this bad?
30     /// The additional lifetimes make the code look more
31     /// complicated, while there is nothing out of the ordinary going on. Removing
32     /// them leads to more readable code.
33     ///
34     /// ### Known problems
35     /// - We bail out if the function has a `where` clause where lifetimes
36     /// are mentioned due to potential false positives.
37     /// - Lifetime bounds such as `impl Foo + 'a` and `T: 'a` must be elided with the
38     /// placeholder notation `'_` because the fully elided notation leaves the type bound to `'static`.
39     ///
40     /// ### Example
41     /// ```rust
42     /// // Unnecessary lifetime annotations
43     /// fn in_and_out<'a>(x: &'a u8, y: u8) -> &'a u8 {
44     ///     x
45     /// }
46     /// ```
47     ///
48     /// Use instead:
49     /// ```rust
50     /// fn elided(x: &u8, y: u8) -> &u8 {
51     ///     x
52     /// }
53     /// ```
54     #[clippy::version = "pre 1.29.0"]
55     pub NEEDLESS_LIFETIMES,
56     complexity,
57     "using explicit lifetimes for references in function arguments when elision rules \
58      would allow omitting them"
59 }
60
61 declare_clippy_lint! {
62     /// ### What it does
63     /// Checks for lifetimes in generics that are never used
64     /// anywhere else.
65     ///
66     /// ### Why is this bad?
67     /// The additional lifetimes make the code look more
68     /// complicated, while there is nothing out of the ordinary going on. Removing
69     /// them leads to more readable code.
70     ///
71     /// ### Example
72     /// ```rust
73     /// // unnecessary lifetimes
74     /// fn unused_lifetime<'a>(x: u8) {
75     ///     // ..
76     /// }
77     /// ```
78     ///
79     /// Use instead:
80     /// ```rust
81     /// fn no_lifetime(x: u8) {
82     ///     // ...
83     /// }
84     /// ```
85     #[clippy::version = "pre 1.29.0"]
86     pub EXTRA_UNUSED_LIFETIMES,
87     complexity,
88     "unused lifetimes in function definitions"
89 }
90
91 declare_lint_pass!(Lifetimes => [NEEDLESS_LIFETIMES, EXTRA_UNUSED_LIFETIMES]);
92
93 impl<'tcx> LateLintPass<'tcx> for Lifetimes {
94     fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
95         if let ItemKind::Fn(ref sig, generics, id) = item.kind {
96             check_fn_inner(cx, sig.decl, Some(id), None, generics, item.span, true);
97         } else if let ItemKind::Impl(impl_) = item.kind {
98             if !item.span.from_expansion() {
99                 report_extra_impl_lifetimes(cx, impl_);
100             }
101         }
102     }
103
104     fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) {
105         if let ImplItemKind::Fn(ref sig, id) = item.kind {
106             let report_extra_lifetimes = trait_ref_of_method(cx, item.owner_id.def_id).is_none();
107             check_fn_inner(
108                 cx,
109                 sig.decl,
110                 Some(id),
111                 None,
112                 item.generics,
113                 item.span,
114                 report_extra_lifetimes,
115             );
116         }
117     }
118
119     fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) {
120         if let TraitItemKind::Fn(ref sig, ref body) = item.kind {
121             let (body, trait_sig) = match *body {
122                 TraitFn::Required(sig) => (None, Some(sig)),
123                 TraitFn::Provided(id) => (Some(id), None),
124             };
125             check_fn_inner(cx, sig.decl, body, trait_sig, item.generics, item.span, true);
126         }
127     }
128 }
129
130 /// The lifetime of a &-reference.
131 #[derive(PartialEq, Eq, Hash, Debug, Clone)]
132 enum RefLt {
133     Unnamed,
134     Static,
135     Named(LocalDefId),
136 }
137
138 fn check_fn_inner<'tcx>(
139     cx: &LateContext<'tcx>,
140     decl: &'tcx FnDecl<'_>,
141     body: Option<BodyId>,
142     trait_sig: Option<&[Ident]>,
143     generics: &'tcx Generics<'_>,
144     span: Span,
145     report_extra_lifetimes: bool,
146 ) {
147     if span.from_expansion() || has_where_lifetimes(cx, generics) {
148         return;
149     }
150
151     let types = generics
152         .params
153         .iter()
154         .filter(|param| matches!(param.kind, GenericParamKind::Type { .. }));
155
156     for typ in types {
157         for pred in generics.bounds_for_param(cx.tcx.hir().local_def_id(typ.hir_id)) {
158             if pred.origin == PredicateOrigin::WhereClause {
159                 // has_where_lifetimes checked that this predicate contains no lifetime.
160                 continue;
161             }
162
163             for bound in pred.bounds {
164                 let mut visitor = RefVisitor::new(cx);
165                 walk_param_bound(&mut visitor, bound);
166                 if visitor.lts.iter().any(|lt| matches!(lt, RefLt::Named(_))) {
167                     return;
168                 }
169                 if let GenericBound::Trait(ref trait_ref, _) = *bound {
170                     let params = &trait_ref
171                         .trait_ref
172                         .path
173                         .segments
174                         .last()
175                         .expect("a path must have at least one segment")
176                         .args;
177                     if let Some(params) = *params {
178                         let lifetimes = params.args.iter().filter_map(|arg| match arg {
179                             GenericArg::Lifetime(lt) => Some(lt),
180                             _ => None,
181                         });
182                         for bound in lifetimes {
183                             if bound.name != LifetimeName::Static && !bound.is_elided() {
184                                 return;
185                             }
186                         }
187                     }
188                 }
189             }
190         }
191     }
192
193     if let Some(elidable_lts) = could_use_elision(cx, decl, body, trait_sig, generics.params) {
194         let lts = elidable_lts
195             .iter()
196             // In principle, the result of the call to `Node::ident` could be `unwrap`ped, as `DefId` should refer to a
197             // `Node::GenericParam`.
198             .filter_map(|&(def_id, _)| cx.tcx.hir().get_by_def_id(def_id).ident())
199             .map(|ident| ident.to_string())
200             .collect::<Vec<_>>()
201             .join(", ");
202
203         span_lint_and_then(
204             cx,
205             NEEDLESS_LIFETIMES,
206             span.with_hi(decl.output.span().hi()),
207             &format!("the following explicit lifetimes could be elided: {lts}"),
208             |diag| {
209                 if let Some(span) = elidable_lts.iter().find_map(|&(_, span)| span) {
210                     diag.span_help(span, "replace with `'_` in generic arguments such as here");
211                 }
212             },
213         );
214     }
215
216     if report_extra_lifetimes {
217         self::report_extra_lifetimes(cx, decl, generics);
218     }
219 }
220
221 // elision doesn't work for explicit self types, see rust-lang/rust#69064
222 fn explicit_self_type<'tcx>(cx: &LateContext<'tcx>, func: &FnDecl<'tcx>, ident: Option<Ident>) -> bool {
223     if_chain! {
224         if let Some(ident) = ident;
225         if ident.name == kw::SelfLower;
226         if !func.implicit_self.has_implicit_self();
227
228         if let Some(self_ty) = func.inputs.first();
229         then {
230             let mut visitor = RefVisitor::new(cx);
231             visitor.visit_ty(self_ty);
232
233             !visitor.all_lts().is_empty()
234         } else {
235             false
236         }
237     }
238 }
239
240 fn could_use_elision<'tcx>(
241     cx: &LateContext<'tcx>,
242     func: &'tcx FnDecl<'_>,
243     body: Option<BodyId>,
244     trait_sig: Option<&[Ident]>,
245     named_generics: &'tcx [GenericParam<'_>],
246 ) -> Option<Vec<(LocalDefId, Option<Span>)>> {
247     // There are two scenarios where elision works:
248     // * no output references, all input references have different LT
249     // * output references, exactly one input reference with same LT
250     // All lifetimes must be unnamed, 'static or defined without bounds on the
251     // level of the current item.
252
253     // check named LTs
254     let allowed_lts = allowed_lts_from(cx.tcx, named_generics);
255
256     // these will collect all the lifetimes for references in arg/return types
257     let mut input_visitor = RefVisitor::new(cx);
258     let mut output_visitor = RefVisitor::new(cx);
259
260     // extract lifetimes in input argument types
261     for arg in func.inputs {
262         input_visitor.visit_ty(arg);
263     }
264     // extract lifetimes in output type
265     if let Return(ty) = func.output {
266         output_visitor.visit_ty(ty);
267     }
268     for lt in named_generics {
269         input_visitor.visit_generic_param(lt);
270     }
271
272     if input_visitor.abort() || output_visitor.abort() {
273         return None;
274     }
275
276     let input_lts = input_visitor.lts;
277     let output_lts = output_visitor.lts;
278
279     if let Some(trait_sig) = trait_sig {
280         if explicit_self_type(cx, func, trait_sig.first().copied()) {
281             return None;
282         }
283     }
284
285     if let Some(body_id) = body {
286         let body = cx.tcx.hir().body(body_id);
287
288         let first_ident = body.params.first().and_then(|param| param.pat.simple_ident());
289         if explicit_self_type(cx, func, first_ident) {
290             return None;
291         }
292
293         let mut checker = BodyLifetimeChecker {
294             lifetimes_used_in_body: false,
295         };
296         checker.visit_expr(body.value);
297         if checker.lifetimes_used_in_body {
298             return None;
299         }
300     }
301
302     // check for lifetimes from higher scopes
303     for lt in input_lts.iter().chain(output_lts.iter()) {
304         if !allowed_lts.contains(lt) {
305             return None;
306         }
307     }
308
309     // check for higher-ranked trait bounds
310     if !input_visitor.nested_elision_site_lts.is_empty() || !output_visitor.nested_elision_site_lts.is_empty() {
311         let allowed_lts: FxHashSet<_> = allowed_lts
312             .iter()
313             .filter_map(|lt| match lt {
314                 RefLt::Named(def_id) => Some(cx.tcx.item_name(def_id.to_def_id())),
315                 _ => None,
316             })
317             .collect();
318         for lt in input_visitor.nested_elision_site_lts {
319             if let RefLt::Named(def_id) = lt {
320                 if allowed_lts.contains(&cx.tcx.item_name(def_id.to_def_id())) {
321                     return None;
322                 }
323             }
324         }
325         for lt in output_visitor.nested_elision_site_lts {
326             if let RefLt::Named(def_id) = lt {
327                 if allowed_lts.contains(&cx.tcx.item_name(def_id.to_def_id())) {
328                     return None;
329                 }
330             }
331         }
332     }
333
334     // A lifetime can be newly elided if:
335     // - It occurs only once among the inputs.
336     // - If there are multiple input lifetimes, then the newly elided lifetime does not occur among the
337     //   outputs (because eliding such an lifetime would create an ambiguity).
338     let elidable_lts = named_lifetime_occurrences(&input_lts)
339         .into_iter()
340         .filter_map(|(def_id, occurrences)| {
341             if occurrences == 1 && (input_lts.len() == 1 || !output_lts.contains(&RefLt::Named(def_id))) {
342                 Some((
343                     def_id,
344                     input_visitor
345                         .lifetime_generic_arg_spans
346                         .get(&def_id)
347                         .or_else(|| output_visitor.lifetime_generic_arg_spans.get(&def_id))
348                         .copied(),
349                 ))
350             } else {
351                 None
352             }
353         })
354         .collect::<Vec<_>>();
355
356     if elidable_lts.is_empty() {
357         None
358     } else {
359         Some(elidable_lts)
360     }
361 }
362
363 fn allowed_lts_from(tcx: TyCtxt<'_>, named_generics: &[GenericParam<'_>]) -> FxHashSet<RefLt> {
364     let mut allowed_lts = FxHashSet::default();
365     for par in named_generics.iter() {
366         if let GenericParamKind::Lifetime { .. } = par.kind {
367             allowed_lts.insert(RefLt::Named(tcx.hir().local_def_id(par.hir_id)));
368         }
369     }
370     allowed_lts.insert(RefLt::Unnamed);
371     allowed_lts.insert(RefLt::Static);
372     allowed_lts
373 }
374
375 /// Number of times each named lifetime occurs in the given slice. Returns a vector to preserve
376 /// relative order.
377 #[must_use]
378 fn named_lifetime_occurrences(lts: &[RefLt]) -> Vec<(LocalDefId, usize)> {
379     let mut occurrences = Vec::new();
380     for lt in lts {
381         if let &RefLt::Named(curr_def_id) = lt {
382             if let Some(pair) = occurrences
383                 .iter_mut()
384                 .find(|(prev_def_id, _)| *prev_def_id == curr_def_id)
385             {
386                 pair.1 += 1;
387             } else {
388                 occurrences.push((curr_def_id, 1));
389             }
390         }
391     }
392     occurrences
393 }
394
395 /// A visitor usable for `rustc_front::visit::walk_ty()`.
396 struct RefVisitor<'a, 'tcx> {
397     cx: &'a LateContext<'tcx>,
398     lts: Vec<RefLt>,
399     lifetime_generic_arg_spans: FxHashMap<LocalDefId, Span>,
400     nested_elision_site_lts: Vec<RefLt>,
401     unelided_trait_object_lifetime: bool,
402 }
403
404 impl<'a, 'tcx> RefVisitor<'a, 'tcx> {
405     fn new(cx: &'a LateContext<'tcx>) -> Self {
406         Self {
407             cx,
408             lts: Vec::new(),
409             lifetime_generic_arg_spans: FxHashMap::default(),
410             nested_elision_site_lts: Vec::new(),
411             unelided_trait_object_lifetime: false,
412         }
413     }
414
415     fn record(&mut self, lifetime: &Option<Lifetime>) {
416         if let Some(ref lt) = *lifetime {
417             if lt.name == LifetimeName::Static {
418                 self.lts.push(RefLt::Static);
419             } else if let LifetimeName::Param(_, ParamName::Fresh) = lt.name {
420                 // Fresh lifetimes generated should be ignored.
421                 self.lts.push(RefLt::Unnamed);
422             } else if lt.is_elided() {
423                 self.lts.push(RefLt::Unnamed);
424             } else if let LifetimeName::Param(def_id, _) = lt.name {
425                 self.lts.push(RefLt::Named(def_id));
426             } else {
427                 self.lts.push(RefLt::Unnamed);
428             }
429         } else {
430             self.lts.push(RefLt::Unnamed);
431         }
432     }
433
434     fn all_lts(&self) -> Vec<RefLt> {
435         self.lts
436             .iter()
437             .chain(self.nested_elision_site_lts.iter())
438             .cloned()
439             .collect::<Vec<_>>()
440     }
441
442     fn abort(&self) -> bool {
443         self.unelided_trait_object_lifetime
444     }
445 }
446
447 impl<'a, 'tcx> Visitor<'tcx> for RefVisitor<'a, 'tcx> {
448     // for lifetimes as parameters of generics
449     fn visit_lifetime(&mut self, lifetime: &'tcx Lifetime) {
450         self.record(&Some(*lifetime));
451     }
452
453     fn visit_poly_trait_ref(&mut self, poly_tref: &'tcx PolyTraitRef<'tcx>) {
454         let trait_ref = &poly_tref.trait_ref;
455         if let Some(id) = trait_ref.trait_def_id() && lang_items::FN_TRAITS.iter().any(|&item| {
456             self.cx.tcx.lang_items().get(item) == Some(id)
457         }) {
458             let mut sub_visitor = RefVisitor::new(self.cx);
459             sub_visitor.visit_trait_ref(trait_ref);
460             self.nested_elision_site_lts.append(&mut sub_visitor.all_lts());
461         } else {
462             walk_poly_trait_ref(self, poly_tref);
463         }
464     }
465
466     fn visit_ty(&mut self, ty: &'tcx Ty<'_>) {
467         match ty.kind {
468             TyKind::OpaqueDef(item, bounds, _) => {
469                 let map = self.cx.tcx.hir();
470                 let item = map.item(item);
471                 let len = self.lts.len();
472                 walk_item(self, item);
473                 self.lts.truncate(len);
474                 self.lts.extend(bounds.iter().filter_map(|bound| match bound {
475                     GenericArg::Lifetime(l) => Some(if let LifetimeName::Param(def_id, _) = l.name {
476                         RefLt::Named(def_id)
477                     } else {
478                         RefLt::Unnamed
479                     }),
480                     _ => None,
481                 }));
482             },
483             TyKind::BareFn(&BareFnTy { decl, .. }) => {
484                 let mut sub_visitor = RefVisitor::new(self.cx);
485                 sub_visitor.visit_fn_decl(decl);
486                 self.nested_elision_site_lts.append(&mut sub_visitor.all_lts());
487             },
488             TyKind::TraitObject(bounds, ref lt, _) => {
489                 if !lt.is_elided() {
490                     self.unelided_trait_object_lifetime = true;
491                 }
492                 for bound in bounds {
493                     self.visit_poly_trait_ref(bound);
494                 }
495             },
496             _ => walk_ty(self, ty),
497         }
498     }
499
500     fn visit_generic_arg(&mut self, generic_arg: &'tcx GenericArg<'tcx>) {
501         if let GenericArg::Lifetime(l) = generic_arg
502             && let LifetimeName::Param(def_id, _) = l.name
503         {
504             self.lifetime_generic_arg_spans.entry(def_id).or_insert(l.span);
505         }
506         // Replace with `walk_generic_arg` if/when https://github.com/rust-lang/rust/pull/103692 lands.
507         // walk_generic_arg(self, generic_arg);
508         match generic_arg {
509             GenericArg::Lifetime(lt) => self.visit_lifetime(lt),
510             GenericArg::Type(ty) => self.visit_ty(ty),
511             GenericArg::Const(ct) => self.visit_anon_const(&ct.value),
512             GenericArg::Infer(inf) => self.visit_infer(inf),
513         }
514     }
515 }
516
517 /// Are any lifetimes mentioned in the `where` clause? If so, we don't try to
518 /// reason about elision.
519 fn has_where_lifetimes<'tcx>(cx: &LateContext<'tcx>, generics: &'tcx Generics<'_>) -> bool {
520     for predicate in generics.predicates {
521         match *predicate {
522             WherePredicate::RegionPredicate(..) => return true,
523             WherePredicate::BoundPredicate(ref pred) => {
524                 // a predicate like F: Trait or F: for<'a> Trait<'a>
525                 let mut visitor = RefVisitor::new(cx);
526                 // walk the type F, it may not contain LT refs
527                 walk_ty(&mut visitor, pred.bounded_ty);
528                 if !visitor.all_lts().is_empty() {
529                     return true;
530                 }
531                 // if the bounds define new lifetimes, they are fine to occur
532                 let allowed_lts = allowed_lts_from(cx.tcx, pred.bound_generic_params);
533                 // now walk the bounds
534                 for bound in pred.bounds.iter() {
535                     walk_param_bound(&mut visitor, bound);
536                 }
537                 // and check that all lifetimes are allowed
538                 if visitor.all_lts().iter().any(|it| !allowed_lts.contains(it)) {
539                     return true;
540                 }
541             },
542             WherePredicate::EqPredicate(ref pred) => {
543                 let mut visitor = RefVisitor::new(cx);
544                 walk_ty(&mut visitor, pred.lhs_ty);
545                 walk_ty(&mut visitor, pred.rhs_ty);
546                 if !visitor.lts.is_empty() {
547                     return true;
548                 }
549             },
550         }
551     }
552     false
553 }
554
555 struct LifetimeChecker<'cx, 'tcx, F> {
556     cx: &'cx LateContext<'tcx>,
557     map: FxHashMap<Symbol, Span>,
558     phantom: std::marker::PhantomData<F>,
559 }
560
561 impl<'cx, 'tcx, F> LifetimeChecker<'cx, 'tcx, F> {
562     fn new(cx: &'cx LateContext<'tcx>, map: FxHashMap<Symbol, Span>) -> LifetimeChecker<'cx, 'tcx, F> {
563         Self {
564             cx,
565             map,
566             phantom: std::marker::PhantomData,
567         }
568     }
569 }
570
571 impl<'cx, 'tcx, F> Visitor<'tcx> for LifetimeChecker<'cx, 'tcx, F>
572 where
573     F: NestedFilter<'tcx>,
574 {
575     type Map = rustc_middle::hir::map::Map<'tcx>;
576     type NestedFilter = F;
577
578     // for lifetimes as parameters of generics
579     fn visit_lifetime(&mut self, lifetime: &'tcx Lifetime) {
580         self.map.remove(&lifetime.name.ident().name);
581     }
582
583     fn visit_generic_param(&mut self, param: &'tcx GenericParam<'_>) {
584         // don't actually visit `<'a>` or `<'a: 'b>`
585         // we've already visited the `'a` declarations and
586         // don't want to spuriously remove them
587         // `'b` in `'a: 'b` is useless unless used elsewhere in
588         // a non-lifetime bound
589         if let GenericParamKind::Type { .. } = param.kind {
590             walk_generic_param(self, param);
591         }
592     }
593
594     fn nested_visit_map(&mut self) -> Self::Map {
595         self.cx.tcx.hir()
596     }
597 }
598
599 fn report_extra_lifetimes<'tcx>(cx: &LateContext<'tcx>, func: &'tcx FnDecl<'_>, generics: &'tcx Generics<'_>) {
600     let hs = generics
601         .params
602         .iter()
603         .filter_map(|par| match par.kind {
604             GenericParamKind::Lifetime { .. } => Some((par.name.ident().name, par.span)),
605             _ => None,
606         })
607         .collect();
608     let mut checker = LifetimeChecker::<hir_nested_filter::None>::new(cx, hs);
609
610     walk_generics(&mut checker, generics);
611     walk_fn_decl(&mut checker, func);
612
613     for &v in checker.map.values() {
614         span_lint(
615             cx,
616             EXTRA_UNUSED_LIFETIMES,
617             v,
618             "this lifetime isn't used in the function definition",
619         );
620     }
621 }
622
623 fn report_extra_impl_lifetimes<'tcx>(cx: &LateContext<'tcx>, impl_: &'tcx Impl<'_>) {
624     let hs = impl_
625         .generics
626         .params
627         .iter()
628         .filter_map(|par| match par.kind {
629             GenericParamKind::Lifetime { .. } => Some((par.name.ident().name, par.span)),
630             _ => None,
631         })
632         .collect();
633     let mut checker = LifetimeChecker::<middle_nested_filter::All>::new(cx, hs);
634
635     walk_generics(&mut checker, impl_.generics);
636     if let Some(ref trait_ref) = impl_.of_trait {
637         walk_trait_ref(&mut checker, trait_ref);
638     }
639     walk_ty(&mut checker, impl_.self_ty);
640     for item in impl_.items {
641         walk_impl_item_ref(&mut checker, item);
642     }
643
644     for &v in checker.map.values() {
645         span_lint(cx, EXTRA_UNUSED_LIFETIMES, v, "this lifetime isn't used in the impl");
646     }
647 }
648
649 struct BodyLifetimeChecker {
650     lifetimes_used_in_body: bool,
651 }
652
653 impl<'tcx> Visitor<'tcx> for BodyLifetimeChecker {
654     // for lifetimes as parameters of generics
655     fn visit_lifetime(&mut self, lifetime: &'tcx Lifetime) {
656         if lifetime.name.ident().name != kw::UnderscoreLifetime && lifetime.name.ident().name != kw::StaticLifetime {
657             self.lifetimes_used_in_body = true;
658         }
659     }
660 }