]>
Commit | Line | Data |
---|---|---|
f20569fa XL |
1 | use crate::utils::{in_macro, span_lint, trait_ref_of_method}; |
2 | use rustc_data_structures::fx::{FxHashMap, FxHashSet}; | |
3 | use 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 | }; | |
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_middle::hir::map::Map; | |
15 | use rustc_session::{declare_lint_pass, declare_tool_lint}; | |
16 | use rustc_span::source_map::Span; | |
17 | use rustc_span::symbol::{kw, Symbol}; | |
18 | ||
19 | declare_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 | ||
51 | declare_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 | ||
78 | declare_lint_pass!(Lifetimes => [NEEDLESS_LIFETIMES, EXTRA_UNUSED_LIFETIMES]); | |
79 | ||
80 | impl<'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)] | |
114 | enum RefLt { | |
115 | Unnamed, | |
116 | Static, | |
117 | Named(Symbol), | |
118 | } | |
119 | ||
120 | fn 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 | ||
179 | fn 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 | ||
282 | fn 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] | |
298 | fn unique_lifetimes(lts: &[RefLt]) -> usize { | |
299 | lts.iter().collect::<FxHashSet<_>>().len() | |
300 | } | |
301 | ||
302 | const CLOSURE_TRAIT_BOUNDS: [LangItem; 3] = [LangItem::Fn, LangItem::FnMut, LangItem::FnOnce]; | |
303 | ||
304 | /// A visitor usable for `rustc_front::visit::walk_ty()`. | |
305 | struct 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 | ||
312 | impl<'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 | ||
351 | impl<'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. | |
410 | fn 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 | ||
446 | struct LifetimeChecker { | |
447 | map: FxHashMap<Symbol, Span>, | |
448 | } | |
449 | ||
450 | impl<'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 | ||
473 | fn 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 | ||
497 | struct BodyLifetimeChecker { | |
498 | lifetimes_used_in_body: bool, | |
499 | } | |
500 | ||
501 | impl<'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 | } |