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