]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/lifetimes.rs
Rollup merge of #103584 - ouz-a:issue-102303, r=oli-obk
[rust.git] / src / tools / clippy / 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::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     for typ in types {
156         for pred in generics.bounds_for_param(cx.tcx.hir().local_def_id(typ.hir_id)) {
157             if pred.origin == PredicateOrigin::WhereClause {
158                 // has_where_lifetimes checked that this predicate contains no lifetime.
159                 continue;
160             }
161
162             for bound in pred.bounds {
163                 let mut visitor = RefVisitor::new(cx);
164                 walk_param_bound(&mut visitor, bound);
165                 if visitor.lts.iter().any(|lt| matches!(lt, RefLt::Named(_))) {
166                     return;
167                 }
168                 if let GenericBound::Trait(ref trait_ref, _) = *bound {
169                     let params = &trait_ref
170                         .trait_ref
171                         .path
172                         .segments
173                         .last()
174                         .expect("a path must have at least one segment")
175                         .args;
176                     if let Some(params) = *params {
177                         let lifetimes = params.args.iter().filter_map(|arg| match arg {
178                             GenericArg::Lifetime(lt) => Some(lt),
179                             _ => None,
180                         });
181                         for bound in lifetimes {
182                             if bound.name != LifetimeName::Static && !bound.is_elided() {
183                                 return;
184                             }
185                         }
186                     }
187                 }
188             }
189         }
190     }
191     if could_use_elision(cx, decl, body, trait_sig, generics.params) {
192         span_lint(
193             cx,
194             NEEDLESS_LIFETIMES,
195             span.with_hi(decl.output.span().hi()),
196             "explicit lifetimes given in parameter types where they could be elided \
197              (or replaced with `'_` if needed by type declaration)",
198         );
199     }
200     if report_extra_lifetimes {
201         self::report_extra_lifetimes(cx, decl, generics);
202     }
203 }
204
205 // elision doesn't work for explicit self types, see rust-lang/rust#69064
206 fn explicit_self_type<'tcx>(cx: &LateContext<'tcx>, func: &FnDecl<'tcx>, ident: Option<Ident>) -> bool {
207     if_chain! {
208         if let Some(ident) = ident;
209         if ident.name == kw::SelfLower;
210         if !func.implicit_self.has_implicit_self();
211
212         if let Some(self_ty) = func.inputs.first();
213         then {
214             let mut visitor = RefVisitor::new(cx);
215             visitor.visit_ty(self_ty);
216
217             !visitor.all_lts().is_empty()
218         } else {
219             false
220         }
221     }
222 }
223
224 fn could_use_elision<'tcx>(
225     cx: &LateContext<'tcx>,
226     func: &'tcx FnDecl<'_>,
227     body: Option<BodyId>,
228     trait_sig: Option<&[Ident]>,
229     named_generics: &'tcx [GenericParam<'_>],
230 ) -> bool {
231     // There are two scenarios where elision works:
232     // * no output references, all input references have different LT
233     // * output references, exactly one input reference with same LT
234     // All lifetimes must be unnamed, 'static or defined without bounds on the
235     // level of the current item.
236
237     // check named LTs
238     let allowed_lts = allowed_lts_from(cx.tcx, named_generics);
239
240     // these will collect all the lifetimes for references in arg/return types
241     let mut input_visitor = RefVisitor::new(cx);
242     let mut output_visitor = RefVisitor::new(cx);
243
244     // extract lifetimes in input argument types
245     for arg in func.inputs {
246         input_visitor.visit_ty(arg);
247     }
248     // extract lifetimes in output type
249     if let Return(ty) = func.output {
250         output_visitor.visit_ty(ty);
251     }
252     for lt in named_generics {
253         input_visitor.visit_generic_param(lt);
254     }
255
256     if input_visitor.abort() || output_visitor.abort() {
257         return false;
258     }
259
260     let input_lts = input_visitor.lts;
261     let output_lts = output_visitor.lts;
262
263     if let Some(trait_sig) = trait_sig {
264         if explicit_self_type(cx, func, trait_sig.first().copied()) {
265             return false;
266         }
267     }
268
269     if let Some(body_id) = body {
270         let body = cx.tcx.hir().body(body_id);
271
272         let first_ident = body.params.first().and_then(|param| param.pat.simple_ident());
273         if explicit_self_type(cx, func, first_ident) {
274             return false;
275         }
276
277         let mut checker = BodyLifetimeChecker {
278             lifetimes_used_in_body: false,
279         };
280         checker.visit_expr(body.value);
281         if checker.lifetimes_used_in_body {
282             return false;
283         }
284     }
285
286     // check for lifetimes from higher scopes
287     for lt in input_lts.iter().chain(output_lts.iter()) {
288         if !allowed_lts.contains(lt) {
289             return false;
290         }
291     }
292
293     // check for higher-ranked trait bounds
294     if !input_visitor.nested_elision_site_lts.is_empty() || !output_visitor.nested_elision_site_lts.is_empty() {
295         let allowed_lts: FxHashSet<_> = allowed_lts
296             .iter()
297             .filter_map(|lt| match lt {
298                 RefLt::Named(def_id) => Some(cx.tcx.item_name(def_id.to_def_id())),
299                 _ => None,
300             })
301             .collect();
302         for lt in input_visitor.nested_elision_site_lts {
303             if let RefLt::Named(def_id) = lt {
304                 if allowed_lts.contains(&cx.tcx.item_name(def_id.to_def_id())) {
305                     return false;
306                 }
307             }
308         }
309         for lt in output_visitor.nested_elision_site_lts {
310             if let RefLt::Named(def_id) = lt {
311                 if allowed_lts.contains(&cx.tcx.item_name(def_id.to_def_id())) {
312                     return false;
313                 }
314             }
315         }
316     }
317
318     // no input lifetimes? easy case!
319     if input_lts.is_empty() {
320         false
321     } else if output_lts.is_empty() {
322         // no output lifetimes, check distinctness of input lifetimes
323
324         // only unnamed and static, ok
325         let unnamed_and_static = input_lts.iter().all(|lt| *lt == RefLt::Unnamed || *lt == RefLt::Static);
326         if unnamed_and_static {
327             return false;
328         }
329         // we have no output reference, so we only need all distinct lifetimes
330         input_lts.len() == unique_lifetimes(&input_lts)
331     } else {
332         // we have output references, so we need one input reference,
333         // and all output lifetimes must be the same
334         if unique_lifetimes(&output_lts) > 1 {
335             return false;
336         }
337         if input_lts.len() == 1 {
338             match (&input_lts[0], &output_lts[0]) {
339                 (&RefLt::Named(n1), &RefLt::Named(n2)) if n1 == n2 => true,
340                 (&RefLt::Named(_), &RefLt::Unnamed) => true,
341                 _ => false, /* already elided, different named lifetimes
342                              * or something static going on */
343             }
344         } else {
345             false
346         }
347     }
348 }
349
350 fn allowed_lts_from(tcx: TyCtxt<'_>, named_generics: &[GenericParam<'_>]) -> FxHashSet<RefLt> {
351     let mut allowed_lts = FxHashSet::default();
352     for par in named_generics.iter() {
353         if let GenericParamKind::Lifetime { .. } = par.kind {
354             allowed_lts.insert(RefLt::Named(tcx.hir().local_def_id(par.hir_id)));
355         }
356     }
357     allowed_lts.insert(RefLt::Unnamed);
358     allowed_lts.insert(RefLt::Static);
359     allowed_lts
360 }
361
362 /// Number of unique lifetimes in the given vector.
363 #[must_use]
364 fn unique_lifetimes(lts: &[RefLt]) -> usize {
365     lts.iter().collect::<FxHashSet<_>>().len()
366 }
367
368 /// A visitor usable for `rustc_front::visit::walk_ty()`.
369 struct RefVisitor<'a, 'tcx> {
370     cx: &'a LateContext<'tcx>,
371     lts: Vec<RefLt>,
372     nested_elision_site_lts: Vec<RefLt>,
373     unelided_trait_object_lifetime: bool,
374 }
375
376 impl<'a, 'tcx> RefVisitor<'a, 'tcx> {
377     fn new(cx: &'a LateContext<'tcx>) -> Self {
378         Self {
379             cx,
380             lts: Vec::new(),
381             nested_elision_site_lts: Vec::new(),
382             unelided_trait_object_lifetime: false,
383         }
384     }
385
386     fn record(&mut self, lifetime: &Option<Lifetime>) {
387         if let Some(ref lt) = *lifetime {
388             if lt.name == LifetimeName::Static {
389                 self.lts.push(RefLt::Static);
390             } else if let LifetimeName::Param(_, ParamName::Fresh) = lt.name {
391                 // Fresh lifetimes generated should be ignored.
392                 self.lts.push(RefLt::Unnamed);
393             } else if lt.is_elided() {
394                 self.lts.push(RefLt::Unnamed);
395             } else if let LifetimeName::Param(def_id, _) = lt.name {
396                 self.lts.push(RefLt::Named(def_id));
397             } else {
398                 self.lts.push(RefLt::Unnamed);
399             }
400         } else {
401             self.lts.push(RefLt::Unnamed);
402         }
403     }
404
405     fn all_lts(&self) -> Vec<RefLt> {
406         self.lts
407             .iter()
408             .chain(self.nested_elision_site_lts.iter())
409             .cloned()
410             .collect::<Vec<_>>()
411     }
412
413     fn abort(&self) -> bool {
414         self.unelided_trait_object_lifetime
415     }
416 }
417
418 impl<'a, 'tcx> Visitor<'tcx> for RefVisitor<'a, 'tcx> {
419     // for lifetimes as parameters of generics
420     fn visit_lifetime(&mut self, lifetime: &'tcx Lifetime) {
421         self.record(&Some(*lifetime));
422     }
423
424     fn visit_poly_trait_ref(&mut self, poly_tref: &'tcx PolyTraitRef<'tcx>) {
425         let trait_ref = &poly_tref.trait_ref;
426         if let Some(id) = trait_ref.trait_def_id() && lang_items::FN_TRAITS.iter().any(|&item| {
427             self.cx.tcx.lang_items().get(item) == Some(id)
428         }) {
429             let mut sub_visitor = RefVisitor::new(self.cx);
430             sub_visitor.visit_trait_ref(trait_ref);
431             self.nested_elision_site_lts.append(&mut sub_visitor.all_lts());
432         } else {
433             walk_poly_trait_ref(self, poly_tref);
434         }
435     }
436
437     fn visit_ty(&mut self, ty: &'tcx Ty<'_>) {
438         match ty.kind {
439             TyKind::OpaqueDef(item, bounds, _) => {
440                 let map = self.cx.tcx.hir();
441                 let item = map.item(item);
442                 let len = self.lts.len();
443                 walk_item(self, item);
444                 self.lts.truncate(len);
445                 self.lts.extend(bounds.iter().filter_map(|bound| match bound {
446                     GenericArg::Lifetime(l) => Some(if let LifetimeName::Param(def_id, _) = l.name {
447                         RefLt::Named(def_id)
448                     } else {
449                         RefLt::Unnamed
450                     }),
451                     _ => None,
452                 }));
453             },
454             TyKind::BareFn(&BareFnTy { decl, .. }) => {
455                 let mut sub_visitor = RefVisitor::new(self.cx);
456                 sub_visitor.visit_fn_decl(decl);
457                 self.nested_elision_site_lts.append(&mut sub_visitor.all_lts());
458             },
459             TyKind::TraitObject(bounds, ref lt, _) => {
460                 if !lt.is_elided() {
461                     self.unelided_trait_object_lifetime = true;
462                 }
463                 for bound in bounds {
464                     self.visit_poly_trait_ref(bound);
465                 }
466             },
467             _ => walk_ty(self, ty),
468         }
469     }
470 }
471
472 /// Are any lifetimes mentioned in the `where` clause? If so, we don't try to
473 /// reason about elision.
474 fn has_where_lifetimes<'tcx>(cx: &LateContext<'tcx>, generics: &'tcx Generics<'_>) -> bool {
475     for predicate in generics.predicates {
476         match *predicate {
477             WherePredicate::RegionPredicate(..) => return true,
478             WherePredicate::BoundPredicate(ref pred) => {
479                 // a predicate like F: Trait or F: for<'a> Trait<'a>
480                 let mut visitor = RefVisitor::new(cx);
481                 // walk the type F, it may not contain LT refs
482                 walk_ty(&mut visitor, pred.bounded_ty);
483                 if !visitor.all_lts().is_empty() {
484                     return true;
485                 }
486                 // if the bounds define new lifetimes, they are fine to occur
487                 let allowed_lts = allowed_lts_from(cx.tcx, pred.bound_generic_params);
488                 // now walk the bounds
489                 for bound in pred.bounds.iter() {
490                     walk_param_bound(&mut visitor, bound);
491                 }
492                 // and check that all lifetimes are allowed
493                 if visitor.all_lts().iter().any(|it| !allowed_lts.contains(it)) {
494                     return true;
495                 }
496             },
497             WherePredicate::EqPredicate(ref pred) => {
498                 let mut visitor = RefVisitor::new(cx);
499                 walk_ty(&mut visitor, pred.lhs_ty);
500                 walk_ty(&mut visitor, pred.rhs_ty);
501                 if !visitor.lts.is_empty() {
502                     return true;
503                 }
504             },
505         }
506     }
507     false
508 }
509
510 struct LifetimeChecker<'cx, 'tcx, F> {
511     cx: &'cx LateContext<'tcx>,
512     map: FxHashMap<Symbol, Span>,
513     phantom: std::marker::PhantomData<F>,
514 }
515
516 impl<'cx, 'tcx, F> LifetimeChecker<'cx, 'tcx, F> {
517     fn new(cx: &'cx LateContext<'tcx>, map: FxHashMap<Symbol, Span>) -> LifetimeChecker<'cx, 'tcx, F> {
518         Self {
519             cx,
520             map,
521             phantom: std::marker::PhantomData,
522         }
523     }
524 }
525
526 impl<'cx, 'tcx, F> Visitor<'tcx> for LifetimeChecker<'cx, 'tcx, F>
527 where
528     F: NestedFilter<'tcx>,
529 {
530     type Map = rustc_middle::hir::map::Map<'tcx>;
531     type NestedFilter = F;
532
533     // for lifetimes as parameters of generics
534     fn visit_lifetime(&mut self, lifetime: &'tcx Lifetime) {
535         self.map.remove(&lifetime.name.ident().name);
536     }
537
538     fn visit_generic_param(&mut self, param: &'tcx GenericParam<'_>) {
539         // don't actually visit `<'a>` or `<'a: 'b>`
540         // we've already visited the `'a` declarations and
541         // don't want to spuriously remove them
542         // `'b` in `'a: 'b` is useless unless used elsewhere in
543         // a non-lifetime bound
544         if let GenericParamKind::Type { .. } = param.kind {
545             walk_generic_param(self, param);
546         }
547     }
548
549     fn nested_visit_map(&mut self) -> Self::Map {
550         self.cx.tcx.hir()
551     }
552 }
553
554 fn report_extra_lifetimes<'tcx>(cx: &LateContext<'tcx>, func: &'tcx FnDecl<'_>, generics: &'tcx Generics<'_>) {
555     let hs = generics
556         .params
557         .iter()
558         .filter_map(|par| match par.kind {
559             GenericParamKind::Lifetime { .. } => Some((par.name.ident().name, par.span)),
560             _ => None,
561         })
562         .collect();
563     let mut checker = LifetimeChecker::<hir_nested_filter::None>::new(cx, hs);
564
565     walk_generics(&mut checker, generics);
566     walk_fn_decl(&mut checker, func);
567
568     for &v in checker.map.values() {
569         span_lint(
570             cx,
571             EXTRA_UNUSED_LIFETIMES,
572             v,
573             "this lifetime isn't used in the function definition",
574         );
575     }
576 }
577
578 fn report_extra_impl_lifetimes<'tcx>(cx: &LateContext<'tcx>, impl_: &'tcx Impl<'_>) {
579     let hs = impl_
580         .generics
581         .params
582         .iter()
583         .filter_map(|par| match par.kind {
584             GenericParamKind::Lifetime { .. } => Some((par.name.ident().name, par.span)),
585             _ => None,
586         })
587         .collect();
588     let mut checker = LifetimeChecker::<middle_nested_filter::All>::new(cx, hs);
589
590     walk_generics(&mut checker, impl_.generics);
591     if let Some(ref trait_ref) = impl_.of_trait {
592         walk_trait_ref(&mut checker, trait_ref);
593     }
594     walk_ty(&mut checker, impl_.self_ty);
595     for item in impl_.items {
596         walk_impl_item_ref(&mut checker, item);
597     }
598
599     for &v in checker.map.values() {
600         span_lint(cx, EXTRA_UNUSED_LIFETIMES, v, "this lifetime isn't used in the impl");
601     }
602 }
603
604 struct BodyLifetimeChecker {
605     lifetimes_used_in_body: bool,
606 }
607
608 impl<'tcx> Visitor<'tcx> for BodyLifetimeChecker {
609     // for lifetimes as parameters of generics
610     fn visit_lifetime(&mut self, lifetime: &'tcx Lifetime) {
611         if lifetime.name.ident().name != kw::UnderscoreLifetime && lifetime.name.ident().name != kw::StaticLifetime {
612             self.lifetimes_used_in_body = true;
613         }
614     }
615 }