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