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