]> git.lizzy.rs Git - rust.git/blobdiff - clippy_lints/src/lifetimes.rs
Merge branch 'macro-use' into HEAD
[rust.git] / clippy_lints / src / lifetimes.rs
index 90fcc79dec4500ee22f88c6c53f2eb7d982299af..7762c4d1cb589a530aebda5b4c2689e5fcfa3647 100644 (file)
@@ -1,11 +1,14 @@
-use reexport::*;
+use crate::reexport::*;
+use matches::matches;
 use rustc::lint::*;
+use rustc::{declare_lint, lint_array};
 use rustc::hir::def::Def;
 use rustc::hir::*;
-use rustc::hir::intravisit::{Visitor, walk_ty, walk_ty_param_bound, walk_fn_decl, walk_generics, NestedVisitorMap};
-use std::collections::{HashSet, HashMap};
+use rustc::hir::intravisit::*;
+use std::collections::{HashMap, HashSet};
 use syntax::codemap::Span;
-use utils::{in_external_macro, span_lint, last_path_segment};
+use crate::utils::{in_external_macro, last_path_segment, span_lint};
+use syntax::symbol::keywords;
 
 /// **What it does:** Checks for lifetime annotations which can be removed by
 /// relying on lifetime elision.
@@ -21,9 +24,9 @@
 /// ```rust
 /// fn in_and_out<'a>(x: &'a u8, y: u8) -> &'a u8 { x }
 /// ```
-declare_lint! {
+declare_clippy_lint! {
     pub NEEDLESS_LIFETIMES,
-    Warn,
+    complexity,
     "using explicit lifetimes for references in function arguments when elision rules \
      would allow omitting them"
 }
 /// ```rust
 /// fn unused_lifetime<'a>(x: u8) { .. }
 /// ```
-declare_lint! {
-    pub UNUSED_LIFETIMES,
-    Warn,
+declare_clippy_lint! {
+    pub EXTRA_UNUSED_LIFETIMES,
+    complexity,
     "unused lifetimes in function definitions"
 }
 
-#[derive(Copy,Clone)]
+#[derive(Copy, Clone)]
 pub struct LifetimePass;
 
 impl LintPass for LifetimePass {
     fn get_lints(&self) -> LintArray {
-        lint_array!(NEEDLESS_LIFETIMES, UNUSED_LIFETIMES)
+        lint_array!(NEEDLESS_LIFETIMES, EXTRA_UNUSED_LIFETIMES)
     }
 }
 
 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for LifetimePass {
     fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx Item) {
-        if let ItemFn(ref decl, _, _, _, ref generics, _) = item.node {
-            check_fn_inner(cx, decl, generics, item.span);
+        if let ItemKind::Fn(ref decl, _, ref generics, id) = item.node {
+            check_fn_inner(cx, decl, Some(id), generics, item.span);
         }
     }
 
     fn check_impl_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx ImplItem) {
-        if let ImplItemKind::Method(ref sig, _) = item.node {
-            check_fn_inner(cx, &sig.decl, &sig.generics, item.span);
+        if let ImplItemKind::Method(ref sig, id) = item.node {
+            check_fn_inner(cx, &sig.decl, Some(id), &item.generics, item.span);
         }
     }
 
     fn check_trait_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx TraitItem) {
-        if let TraitItemKind::Method(ref sig, _) = item.node {
-            check_fn_inner(cx, &sig.decl, &sig.generics, item.span);
+        if let TraitItemKind::Method(ref sig, ref body) = item.node {
+            let body = match *body {
+                TraitMethod::Required(_) => None,
+                TraitMethod::Provided(id) => Some(id),
+            };
+            check_fn_inner(cx, &sig.decl, body, &item.generics, item.span);
         }
     }
 }
@@ -84,43 +91,69 @@ enum RefLt {
     Named(Name),
 }
 
-fn bound_lifetimes(bound: &TyParamBound) -> HirVec<&Lifetime> {
-    if let TraitTyParamBound(ref trait_ref, _) = *bound {
-        trait_ref.trait_ref
-            .path
-            .segments
-            .last()
-            .expect("a path must have at least one segment")
-            .parameters
-            .lifetimes()
-    } else {
-        HirVec::new()
-    }
-}
-
-fn check_fn_inner<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, decl: &'tcx FnDecl, generics: &'tcx Generics, span: Span) {
+fn check_fn_inner<'a, 'tcx>(
+    cx: &LateContext<'a, 'tcx>,
+    decl: &'tcx FnDecl,
+    body: Option<BodyId>,
+    generics: &'tcx Generics,
+    span: Span,
+) {
     if in_external_macro(cx, span) || has_where_lifetimes(cx, &generics.where_clause) {
         return;
     }
 
-    let bounds_lts = generics.ty_params
-        .iter()
-        .flat_map(|typ| typ.bounds.iter().flat_map(bound_lifetimes));
-
-    if could_use_elision(cx, decl, &generics.lifetimes, bounds_lts) {
-        span_lint(cx,
-                  NEEDLESS_LIFETIMES,
-                  span,
-                  "explicit lifetimes given in parameter types where they could be elided");
+    let mut bounds_lts = Vec::new();
+    let types = generics.params.iter().filter_map(|param| match param.kind {
+        GenericParamKind::Type { .. } => Some(param),
+        GenericParamKind::Lifetime { .. } => None,
+    });
+    for typ in types {
+        for bound in &typ.bounds {
+            let mut visitor = RefVisitor::new(cx);
+            walk_param_bound(&mut visitor, bound);
+            if visitor.lts.iter().any(|lt| matches!(lt, RefLt::Named(_))) {
+                return;
+            }
+            if let GenericBound::Trait(ref trait_ref, _) = *bound {
+                let params = &trait_ref
+                    .trait_ref
+                    .path
+                    .segments
+                    .last()
+                    .expect("a path must have at least one segment")
+                    .args;
+                if let Some(ref params) = *params {
+                    let lifetimes = params.args.iter().filter_map(|arg| match arg {
+                        GenericArg::Lifetime(lt) => Some(lt),
+                        GenericArg::Type(_) => None,
+                    });
+                    for bound in lifetimes {
+                        if bound.name != LifetimeName::Static && !bound.is_elided() {
+                            return;
+                        }
+                        bounds_lts.push(bound);
+                    }
+                }
+            }
+        }
+    }
+    if could_use_elision(cx, decl, body, &generics.params, bounds_lts) {
+        span_lint(
+            cx,
+            NEEDLESS_LIFETIMES,
+            span,
+            "explicit lifetimes given in parameter types where they could be elided",
+        );
     }
     report_extra_lifetimes(cx, decl, generics);
 }
 
-fn could_use_elision<'a, 'tcx: 'a, T: Iterator<Item = &'tcx Lifetime>>(
+fn could_use_elision<'a, 'tcx: 'a>(
     cx: &LateContext<'a, 'tcx>,
     func: &'tcx FnDecl,
-    named_lts: &'tcx [LifetimeDef],
-    bounds_lts: T
+    body: Option<BodyId>,
+    named_generics: &'tcx [GenericParam],
+    bounds_lts: Vec<&'tcx Lifetime>,
 ) -> bool {
     // There are two scenarios where elision works:
     // * no output references, all input references have different LT
@@ -129,7 +162,7 @@ fn could_use_elision<'a, 'tcx: 'a, T: Iterator<Item = &'tcx Lifetime>>(
     // level of the current item.
 
     // check named LTs
-    let allowed_lts = allowed_lts_from(named_lts);
+    let allowed_lts = allowed_lts_from(named_generics);
 
     // these will collect all the lifetimes for references in arg/return types
     let mut input_visitor = RefVisitor::new(cx);
@@ -144,8 +177,24 @@ fn could_use_elision<'a, 'tcx: 'a, T: Iterator<Item = &'tcx Lifetime>>(
         output_visitor.visit_ty(ty);
     }
 
-    let input_lts = lts_from_bounds(input_visitor.into_vec(), bounds_lts);
-    let output_lts = output_visitor.into_vec();
+    let input_lts = match input_visitor.into_vec() {
+        Some(lts) => lts_from_bounds(lts, bounds_lts.into_iter()),
+        None => return false,
+    };
+    let output_lts = match output_visitor.into_vec() {
+        Some(val) => val,
+        None => return false,
+    };
+
+    if let Some(body_id) = body {
+        let mut checker = BodyLifetimeChecker {
+            lifetimes_used_in_body: false,
+        };
+        checker.visit_expr(&cx.tcx.hir.body(body_id).value);
+        if checker.lifetimes_used_in_body {
+            return false;
+        }
+    }
 
     // check for lifetimes from higher scopes
     for lt in input_lts.iter().chain(output_lts.iter()) {
@@ -161,7 +210,10 @@ fn could_use_elision<'a, 'tcx: 'a, T: Iterator<Item = &'tcx Lifetime>>(
         // no output lifetimes, check distinctness of input lifetimes
 
         // only unnamed and static, ok
-        if input_lts.iter().all(|lt| *lt == RefLt::Unnamed || *lt == RefLt::Static) {
+        let unnamed_and_static = input_lts
+            .iter()
+            .all(|lt| *lt == RefLt::Unnamed || *lt == RefLt::Static);
+        if unnamed_and_static {
             return false;
         }
         // we have no output reference, so we only need all distinct lifetimes
@@ -176,8 +228,8 @@ fn could_use_elision<'a, 'tcx: 'a, T: Iterator<Item = &'tcx Lifetime>>(
             match (&input_lts[0], &output_lts[0]) {
                 (&RefLt::Named(n1), &RefLt::Named(n2)) if n1 == n2 => true,
                 (&RefLt::Named(_), &RefLt::Unnamed) => true,
-                _ => false, // already elided, different named lifetimes
-                // or something static going on
+                _ => false, /* already elided, different named lifetimes
+                             * or something static going on */
             }
         } else {
             false
@@ -185,11 +237,13 @@ fn could_use_elision<'a, 'tcx: 'a, T: Iterator<Item = &'tcx Lifetime>>(
     }
 }
 
-fn allowed_lts_from(named_lts: &[LifetimeDef]) -> HashSet<RefLt> {
+fn allowed_lts_from(named_generics: &[GenericParam]) -> HashSet<RefLt> {
     let mut allowed_lts = HashSet::new();
-    for lt in named_lts {
-        if lt.bounds.is_empty() {
-            allowed_lts.insert(RefLt::Named(lt.lifetime.name));
+    for par in named_generics.iter() {
+        if let GenericParamKind::Lifetime { .. } = par.kind {
+            if par.bounds.is_empty() {
+                allowed_lts.insert(RefLt::Named(par.name.ident().name));
+            }
         }
     }
     allowed_lts.insert(RefLt::Unnamed);
@@ -199,8 +253,8 @@ fn allowed_lts_from(named_lts: &[LifetimeDef]) -> HashSet<RefLt> {
 
 fn lts_from_bounds<'a, T: Iterator<Item = &'a Lifetime>>(mut vec: Vec<RefLt>, bounds_lts: T) -> Vec<RefLt> {
     for lt in bounds_lts {
-        if &*lt.name.as_str() != "'static" {
-            vec.push(RefLt::Named(lt.name));
+        if lt.name != LifetimeName::Static {
+            vec.push(RefLt::Named(lt.name.ident().name));
         }
     }
 
@@ -216,50 +270,58 @@ fn unique_lifetimes(lts: &[RefLt]) -> usize {
 struct RefVisitor<'a, 'tcx: 'a> {
     cx: &'a LateContext<'a, 'tcx>,
     lts: Vec<RefLt>,
+    abort: bool,
 }
 
 impl<'v, 't> RefVisitor<'v, 't> {
-    fn new(cx: &'v LateContext<'v, 't>) -> RefVisitor<'v, 't> {
-        RefVisitor {
-            cx: cx,
+    fn new(cx: &'v LateContext<'v, 't>) -> Self {
+        Self {
+            cx,
             lts: Vec::new(),
+            abort: false,
         }
     }
 
     fn record(&mut self, lifetime: &Option<Lifetime>) {
         if let Some(ref lt) = *lifetime {
-            if &*lt.name.as_str() == "'static" {
+            if lt.name == LifetimeName::Static {
                 self.lts.push(RefLt::Static);
             } else if lt.is_elided() {
-                // TODO: investigate
                 self.lts.push(RefLt::Unnamed);
             } else {
-                self.lts.push(RefLt::Named(lt.name));
+                self.lts.push(RefLt::Named(lt.name.ident().name));
             }
         } else {
             self.lts.push(RefLt::Unnamed);
         }
     }
 
-    fn into_vec(self) -> Vec<RefLt> {
-        self.lts
+    fn into_vec(self) -> Option<Vec<RefLt>> {
+        if self.abort {
+            None
+        } else {
+            Some(self.lts)
+        }
     }
 
     fn collect_anonymous_lifetimes(&mut self, qpath: &QPath, ty: &Ty) {
-        let last_path_segment = &last_path_segment(qpath).parameters;
-        if let AngleBracketedParameters(ref params) = *last_path_segment {
-            if params.lifetimes.is_empty() {
-                match self.cx.tables.qpath_def(qpath, ty.id) {
-                    Def::TyAlias(def_id) |
-                    Def::Struct(def_id) => {
-                        let generics = self.cx.tcx.item_generics(def_id);
-                        for _ in generics.regions.as_slice() {
+        if let Some(ref last_path_segment) = last_path_segment(qpath).args {
+            if !last_path_segment.parenthesized
+                && !last_path_segment.args.iter().any(|arg| match arg {
+                    GenericArg::Lifetime(_) => true,
+                    GenericArg::Type(_) => false,
+                }) {
+                let hir_id = self.cx.tcx.hir.node_to_hir_id(ty.id);
+                match self.cx.tables.qpath_def(qpath, hir_id) {
+                    Def::TyAlias(def_id) | Def::Struct(def_id) => {
+                        let generics = self.cx.tcx.generics_of(def_id);
+                        for _ in generics.params.as_slice() {
                             self.record(&None);
                         }
                     },
                     Def::Trait(def_id) => {
-                        let trait_def = self.cx.tcx.trait_defs.borrow()[&def_id];
-                        for _ in &self.cx.tcx.item_generics(trait_def.def_id).regions {
+                        let trait_def = self.cx.tcx.trait_def(def_id);
+                        for _ in &self.cx.tcx.generics_of(trait_def.def_id).params {
                             self.record(&None);
                         }
                     },
@@ -278,18 +340,36 @@ fn visit_lifetime(&mut self, lifetime: &'tcx Lifetime) {
 
     fn visit_ty(&mut self, ty: &'tcx Ty) {
         match ty.node {
-            TyRptr(ref lt, _) if lt.is_elided() => {
+            TyKind::Rptr(ref lt, _) if lt.is_elided() => {
                 self.record(&None);
             },
-            TyPath(ref path) => {
-                self.collect_anonymous_lifetimes(path, ty);
-            },
-            TyImplTrait(ref param_bounds) => {
-                for bound in param_bounds {
-                    if let RegionTyParamBound(_) = *bound {
-                        self.record(&None);
+            TyKind::Path(ref path) => {
+                if let QPath::Resolved(_, ref path) = *path {
+                    if let Def::Existential(def_id) = path.def {
+                        let node_id = self.cx.tcx.hir.as_local_node_id(def_id).unwrap();
+                        if let ItemKind::Existential(ref exist_ty) = self.cx.tcx.hir.expect_item(node_id).node {
+                            for bound in &exist_ty.bounds {
+                                if let GenericBound::Outlives(_) = *bound {
+                                    self.record(&None);
+                                }
+                            }
+                        } else {
+                            unreachable!()
+                        }
+                        walk_ty(self, ty);
+                        return;
                     }
                 }
+                self.collect_anonymous_lifetimes(path, ty);
+            }
+            TyKind::TraitObject(ref bounds, ref lt) => {
+                if !lt.is_elided() {
+                    self.abort = true;
+                }
+                for bound in bounds {
+                    self.visit_poly_trait_ref(bound, TraitBoundModifier::None);
+                }
+                return;
             },
             _ => (),
         }
@@ -315,16 +395,19 @@ fn has_where_lifetimes<'a, 'tcx: 'a>(cx: &LateContext<'a, 'tcx>, where_clause: &
                     return true;
                 }
                 // if the bounds define new lifetimes, they are fine to occur
-                let allowed_lts = allowed_lts_from(&pred.bound_lifetimes);
+                let allowed_lts = allowed_lts_from(&pred.bound_generic_params);
                 // now walk the bounds
                 for bound in pred.bounds.iter() {
-                    walk_ty_param_bound(&mut visitor, bound);
+                    walk_param_bound(&mut visitor, bound);
                 }
                 // and check that all lifetimes are allowed
-                for lt in visitor.into_vec() {
-                    if !allowed_lts.contains(&lt) {
-                        return true;
-                    }
+                match visitor.into_vec() {
+                    None => return false,
+                    Some(lts) => for lt in lts {
+                        if !allowed_lts.contains(&lt) {
+                            return true;
+                        }
+                    },
                 }
             },
             WherePredicate::EqPredicate(ref pred) => {
@@ -347,15 +430,18 @@ struct LifetimeChecker {
 impl<'tcx> Visitor<'tcx> for LifetimeChecker {
     // for lifetimes as parameters of generics
     fn visit_lifetime(&mut self, lifetime: &'tcx Lifetime) {
-        self.map.remove(&lifetime.name);
+        self.map.remove(&lifetime.name.ident().name);
     }
 
-    fn visit_lifetime_def(&mut self, _: &'tcx LifetimeDef) {
+    fn visit_generic_param(&mut self, param: &'tcx GenericParam) {
         // don't actually visit `<'a>` or `<'a: 'b>`
         // we've already visited the `'a` declarations and
         // don't want to spuriously remove them
         // `'b` in `'a: 'b` is useless unless used elsewhere in
         // a non-lifetime bound
+        if let GenericParamKind::Type { .. } = param.kind {
+            walk_generic_param(self, param)
+        }
     }
     fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
         NestedVisitorMap::None
@@ -363,9 +449,11 @@ fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
 }
 
 fn report_extra_lifetimes<'a, 'tcx: 'a>(cx: &LateContext<'a, 'tcx>, func: &'tcx FnDecl, generics: &'tcx Generics) {
-    let hs = generics.lifetimes
-        .iter()
-        .map(|lt| (lt.lifetime.name, lt.lifetime.span))
+    let hs = generics.params.iter()
+        .filter_map(|par| match par.kind {
+            GenericParamKind::Lifetime { .. } => Some((par.name.ident().name, par.span)),
+            _ => None,
+        })
         .collect();
     let mut checker = LifetimeChecker { map: hs };
 
@@ -373,6 +461,23 @@ fn report_extra_lifetimes<'a, 'tcx: 'a>(cx: &LateContext<'a, 'tcx>, func: &'tcx
     walk_fn_decl(&mut checker, func);
 
     for &v in checker.map.values() {
-        span_lint(cx, UNUSED_LIFETIMES, v, "this lifetime isn't used in the function definition");
+        span_lint(cx, EXTRA_UNUSED_LIFETIMES, v, "this lifetime isn't used in the function definition");
+    }
+}
+
+struct BodyLifetimeChecker {
+    lifetimes_used_in_body: bool,
+}
+
+impl<'tcx> Visitor<'tcx> for BodyLifetimeChecker {
+    // for lifetimes as parameters of generics
+    fn visit_lifetime(&mut self, lifetime: &'tcx Lifetime) {
+        if lifetime.name.ident().name != keywords::Invalid.name() && lifetime.name.ident().name != "'static" {
+            self.lifetimes_used_in_body = true;
+        }
+    }
+
+    fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
+        NestedVisitorMap::None
     }
 }