3 use rustc
::hir
::def
::Def
;
5 use rustc
::hir
::intravisit
::*;
6 use std
::collections
::{HashMap, HashSet}
;
7 use syntax
::codemap
::Span
;
8 use utils
::{in_external_macro, last_path_segment, span_lint}
;
9 use syntax
::symbol
::keywords
;
11 /// **What it does:** Checks for lifetime annotations which can be removed by
12 /// relying on lifetime elision.
14 /// **Why is this bad?** The additional lifetimes make the code look more
15 /// complicated, while there is nothing out of the ordinary going on. Removing
16 /// them leads to more readable code.
18 /// **Known problems:** Potential false negatives: we bail out if the function
19 /// has a `where` clause where lifetimes are mentioned.
23 /// fn in_and_out<'a>(x: &'a u8, y: u8) -> &'a u8 { x }
25 declare_clippy_lint
! {
26 pub NEEDLESS_LIFETIMES
,
28 "using explicit lifetimes for references in function arguments when elision rules \
29 would allow omitting them"
32 /// **What it does:** Checks for lifetimes in generics that are never used
35 /// **Why is this bad?** The additional lifetimes make the code look more
36 /// complicated, while there is nothing out of the ordinary going on. Removing
37 /// them leads to more readable code.
39 /// **Known problems:** None.
43 /// fn unused_lifetime<'a>(x: u8) { .. }
45 declare_clippy_lint
! {
48 "unused lifetimes in function definitions"
51 #[derive(Copy, Clone)]
52 pub struct LifetimePass
;
54 impl LintPass
for LifetimePass
{
55 fn get_lints(&self) -> LintArray
{
56 lint_array
!(NEEDLESS_LIFETIMES
, UNUSED_LIFETIMES
)
60 impl<'a
, 'tcx
> LateLintPass
<'a
, 'tcx
> for LifetimePass
{
61 fn check_item(&mut self, cx
: &LateContext
<'a
, 'tcx
>, item
: &'tcx Item
) {
62 if let ItemFn(ref decl
, _
, _
, _
, ref generics
, id
) = item
.node
{
63 check_fn_inner(cx
, decl
, Some(id
), generics
, item
.span
);
67 fn check_impl_item(&mut self, cx
: &LateContext
<'a
, 'tcx
>, item
: &'tcx ImplItem
) {
68 if let ImplItemKind
::Method(ref sig
, id
) = item
.node
{
69 check_fn_inner(cx
, &sig
.decl
, Some(id
), &item
.generics
, item
.span
);
73 fn check_trait_item(&mut self, cx
: &LateContext
<'a
, 'tcx
>, item
: &'tcx TraitItem
) {
74 if let TraitItemKind
::Method(ref sig
, ref body
) = item
.node
{
75 let body
= match *body
{
76 TraitMethod
::Required(_
) => None
,
77 TraitMethod
::Provided(id
) => Some(id
),
79 check_fn_inner(cx
, &sig
.decl
, body
, &item
.generics
, item
.span
);
84 /// The lifetime of a &-reference.
85 #[derive(PartialEq, Eq, Hash, Debug)]
92 fn check_fn_inner
<'a
, 'tcx
>(
93 cx
: &LateContext
<'a
, 'tcx
>,
96 generics
: &'tcx Generics
,
99 if in_external_macro(cx
, span
) || has_where_lifetimes(cx
, &generics
.where_clause
) {
103 let mut bounds_lts
= Vec
::new();
104 for typ
in generics
.ty_params() {
105 for bound
in &typ
.bounds
{
106 if let TraitTyParamBound(ref trait_ref
, _
) = *bound
{
107 let params
= &trait_ref
112 .expect("a path must have at least one segment")
114 if let Some(ref params
) = *params
{
115 for bound
in ¶ms
.lifetimes
{
116 if bound
.name
.name() != "'static" && !bound
.is_elided() {
119 bounds_lts
.push(bound
);
125 if could_use_elision(cx
, decl
, body
, &generics
.params
, bounds_lts
) {
130 "explicit lifetimes given in parameter types where they could be elided",
133 report_extra_lifetimes(cx
, decl
, generics
);
136 fn could_use_elision
<'a
, 'tcx
: 'a
>(
137 cx
: &LateContext
<'a
, 'tcx
>,
139 body
: Option
<BodyId
>,
140 named_generics
: &'tcx
[GenericParam
],
141 bounds_lts
: Vec
<&'tcx Lifetime
>,
143 // There are two scenarios where elision works:
144 // * no output references, all input references have different LT
145 // * output references, exactly one input reference with same LT
146 // All lifetimes must be unnamed, 'static or defined without bounds on the
147 // level of the current item.
150 let allowed_lts
= allowed_lts_from(named_generics
);
152 // these will collect all the lifetimes for references in arg/return types
153 let mut input_visitor
= RefVisitor
::new(cx
);
154 let mut output_visitor
= RefVisitor
::new(cx
);
156 // extract lifetimes in input argument types
157 for arg
in &func
.inputs
{
158 input_visitor
.visit_ty(arg
);
160 // extract lifetimes in output type
161 if let Return(ref ty
) = func
.output
{
162 output_visitor
.visit_ty(ty
);
165 let input_lts
= match input_visitor
.into_vec() {
166 Some(lts
) => lts_from_bounds(lts
, bounds_lts
.into_iter()),
167 None
=> return false,
169 let output_lts
= match output_visitor
.into_vec() {
171 None
=> return false,
174 if let Some(body_id
) = body
{
175 let mut checker
= BodyLifetimeChecker
{
176 lifetimes_used_in_body
: false,
178 checker
.visit_expr(&cx
.tcx
.hir
.body(body_id
).value
);
179 if checker
.lifetimes_used_in_body
{
184 // check for lifetimes from higher scopes
185 for lt
in input_lts
.iter().chain(output_lts
.iter()) {
186 if !allowed_lts
.contains(lt
) {
191 // no input lifetimes? easy case!
192 if input_lts
.is_empty() {
194 } else if output_lts
.is_empty() {
195 // no output lifetimes, check distinctness of input lifetimes
197 // only unnamed and static, ok
198 let unnamed_and_static
= input_lts
200 .all(|lt
| *lt
== RefLt
::Unnamed
|| *lt
== RefLt
::Static
);
201 if unnamed_and_static
{
204 // we have no output reference, so we only need all distinct lifetimes
205 input_lts
.len() == unique_lifetimes(&input_lts
)
207 // we have output references, so we need one input reference,
208 // and all output lifetimes must be the same
209 if unique_lifetimes(&output_lts
) > 1 {
212 if input_lts
.len() == 1 {
213 match (&input_lts
[0], &output_lts
[0]) {
214 (&RefLt
::Named(n1
), &RefLt
::Named(n2
)) if n1
== n2
=> true,
215 (&RefLt
::Named(_
), &RefLt
::Unnamed
) => true,
216 _
=> false, /* already elided, different named lifetimes
217 * or something static going on */
225 fn allowed_lts_from(named_generics
: &[GenericParam
]) -> HashSet
<RefLt
> {
226 let mut allowed_lts
= HashSet
::new();
227 for par
in named_generics
.iter() {
228 if let GenericParam
::Lifetime(ref lt
) = *par
{
229 if lt
.bounds
.is_empty() {
230 allowed_lts
.insert(RefLt
::Named(lt
.lifetime
.name
.name()));
234 allowed_lts
.insert(RefLt
::Unnamed
);
235 allowed_lts
.insert(RefLt
::Static
);
239 fn lts_from_bounds
<'a
, T
: Iterator
<Item
= &'a Lifetime
>>(mut vec
: Vec
<RefLt
>, bounds_lts
: T
) -> Vec
<RefLt
> {
240 for lt
in bounds_lts
{
241 if lt
.name
.name() != "'static" {
242 vec
.push(RefLt
::Named(lt
.name
.name()));
249 /// Number of unique lifetimes in the given vector.
250 fn unique_lifetimes(lts
: &[RefLt
]) -> usize {
251 lts
.iter().collect
::<HashSet
<_
>>().len()
254 /// A visitor usable for `rustc_front::visit::walk_ty()`.
255 struct RefVisitor
<'a
, 'tcx
: 'a
> {
256 cx
: &'a LateContext
<'a
, 'tcx
>,
261 impl<'v
, 't
> RefVisitor
<'v
, 't
> {
262 fn new(cx
: &'v LateContext
<'v
, 't
>) -> Self {
270 fn record(&mut self, lifetime
: &Option
<Lifetime
>) {
271 if let Some(ref lt
) = *lifetime
{
272 if lt
.name
.name() == "'static" {
273 self.lts
.push(RefLt
::Static
);
274 } else if lt
.is_elided() {
275 self.lts
.push(RefLt
::Unnamed
);
277 self.lts
.push(RefLt
::Named(lt
.name
.name()));
280 self.lts
.push(RefLt
::Unnamed
);
284 fn into_vec(self) -> Option
<Vec
<RefLt
>> {
292 fn collect_anonymous_lifetimes(&mut self, qpath
: &QPath
, ty
: &Ty
) {
293 if let Some(ref last_path_segment
) = last_path_segment(qpath
).parameters
{
294 if !last_path_segment
.parenthesized
&& last_path_segment
.lifetimes
.is_empty() {
295 let hir_id
= self.cx
.tcx
.hir
.node_to_hir_id(ty
.id
);
296 match self.cx
.tables
.qpath_def(qpath
, hir_id
) {
297 Def
::TyAlias(def_id
) | Def
::Struct(def_id
) => {
298 let generics
= self.cx
.tcx
.generics_of(def_id
);
299 for _
in generics
.params
.as_slice() {
303 Def
::Trait(def_id
) => {
304 let trait_def
= self.cx
.tcx
.trait_def(def_id
);
305 for _
in &self.cx
.tcx
.generics_of(trait_def
.def_id
).params
{
316 impl<'a
, 'tcx
> Visitor
<'tcx
> for RefVisitor
<'a
, 'tcx
> {
317 // for lifetimes as parameters of generics
318 fn visit_lifetime(&mut self, lifetime
: &'tcx Lifetime
) {
319 self.record(&Some(*lifetime
));
322 fn visit_ty(&mut self, ty
: &'tcx Ty
) {
324 TyRptr(ref lt
, _
) if lt
.is_elided() => {
327 TyPath(ref path
) => {
328 self.collect_anonymous_lifetimes(path
, ty
);
330 TyImplTraitExistential(ref exist_ty
, _
) => {
331 for bound
in &exist_ty
.bounds
{
332 if let RegionTyParamBound(_
) = *bound
{
337 TyTraitObject(ref bounds
, ref lt
) => {
341 for bound
in bounds
{
342 self.visit_poly_trait_ref(bound
, TraitBoundModifier
::None
);
350 fn nested_visit_map
<'this
>(&'this
mut self) -> NestedVisitorMap
<'this
, 'tcx
> {
351 NestedVisitorMap
::None
355 /// Are any lifetimes mentioned in the `where` clause? If yes, we don't try to
356 /// reason about elision.
357 fn has_where_lifetimes
<'a
, 'tcx
: 'a
>(cx
: &LateContext
<'a
, 'tcx
>, where_clause
: &'tcx WhereClause
) -> bool
{
358 for predicate
in &where_clause
.predicates
{
360 WherePredicate
::RegionPredicate(..) => return true,
361 WherePredicate
::BoundPredicate(ref pred
) => {
362 // a predicate like F: Trait or F: for<'a> Trait<'a>
363 let mut visitor
= RefVisitor
::new(cx
);
364 // walk the type F, it may not contain LT refs
365 walk_ty(&mut visitor
, &pred
.bounded_ty
);
366 if !visitor
.lts
.is_empty() {
369 // if the bounds define new lifetimes, they are fine to occur
370 let allowed_lts
= allowed_lts_from(&pred
.bound_generic_params
);
371 // now walk the bounds
372 for bound
in pred
.bounds
.iter() {
373 walk_ty_param_bound(&mut visitor
, bound
);
375 // and check that all lifetimes are allowed
376 match visitor
.into_vec() {
377 None
=> return false,
378 Some(lts
) => for lt
in lts
{
379 if !allowed_lts
.contains(<
) {
385 WherePredicate
::EqPredicate(ref pred
) => {
386 let mut visitor
= RefVisitor
::new(cx
);
387 walk_ty(&mut visitor
, &pred
.lhs_ty
);
388 walk_ty(&mut visitor
, &pred
.rhs_ty
);
389 if !visitor
.lts
.is_empty() {
398 struct LifetimeChecker
{
399 map
: HashMap
<Name
, Span
>,
402 impl<'tcx
> Visitor
<'tcx
> for LifetimeChecker
{
403 // for lifetimes as parameters of generics
404 fn visit_lifetime(&mut self, lifetime
: &'tcx Lifetime
) {
405 self.map
.remove(&lifetime
.name
.name());
408 fn visit_generic_param(&mut self, param
: &'tcx GenericParam
) {
409 // don't actually visit `<'a>` or `<'a: 'b>`
410 // we've already visited the `'a` declarations and
411 // don't want to spuriously remove them
412 // `'b` in `'a: 'b` is useless unless used elsewhere in
413 // a non-lifetime bound
414 if param
.is_type_param() {
415 walk_generic_param(self, param
)
418 fn nested_visit_map
<'this
>(&'this
mut self) -> NestedVisitorMap
<'this
, 'tcx
> {
419 NestedVisitorMap
::None
423 fn report_extra_lifetimes
<'a
, 'tcx
: 'a
>(cx
: &LateContext
<'a
, 'tcx
>, func
: &'tcx FnDecl
, generics
: &'tcx Generics
) {
426 .map(|lt
| (lt
.lifetime
.name
.name(), lt
.lifetime
.span
))
428 let mut checker
= LifetimeChecker { map: hs }
;
430 walk_generics(&mut checker
, generics
);
431 walk_fn_decl(&mut checker
, func
);
433 for &v
in checker
.map
.values() {
434 span_lint(cx
, UNUSED_LIFETIMES
, v
, "this lifetime isn't used in the function definition");
438 struct BodyLifetimeChecker
{
439 lifetimes_used_in_body
: bool
,
442 impl<'tcx
> Visitor
<'tcx
> for BodyLifetimeChecker
{
443 // for lifetimes as parameters of generics
444 fn visit_lifetime(&mut self, lifetime
: &'tcx Lifetime
) {
445 if lifetime
.name
.name() != keywords
::Invalid
.name() && lifetime
.name
.name() != "'static" {
446 self.lifetimes_used_in_body
= true;
450 fn nested_visit_map
<'this
>(&'this
mut self) -> NestedVisitorMap
<'this
, 'tcx
> {
451 NestedVisitorMap
::None