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