summaryrefslogtreecommitdiffstats
path: root/src/tools/clippy/clippy_lints/src/lifetimes.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/clippy/clippy_lints/src/lifetimes.rs')
-rw-r--r--src/tools/clippy/clippy_lints/src/lifetimes.rs268
1 files changed, 158 insertions, 110 deletions
diff --git a/src/tools/clippy/clippy_lints/src/lifetimes.rs b/src/tools/clippy/clippy_lints/src/lifetimes.rs
index 7cf1a6b80..986ffcad8 100644
--- a/src/tools/clippy/clippy_lints/src/lifetimes.rs
+++ b/src/tools/clippy/clippy_lints/src/lifetimes.rs
@@ -1,21 +1,21 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
use clippy_utils::trait_ref_of_method;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_errors::Applicability;
use rustc_hir::intravisit::nested_filter::{self as hir_nested_filter, NestedFilter};
use rustc_hir::intravisit::{
- walk_fn_decl, walk_generic_arg, walk_generic_param, walk_generics, walk_impl_item_ref, walk_item, walk_param_bound,
+ walk_fn_decl, walk_generic_param, walk_generics, walk_impl_item_ref, walk_item, walk_param_bound,
walk_poly_trait_ref, walk_trait_ref, walk_ty, Visitor,
};
-use rustc_hir::lang_items;
use rustc_hir::FnRetTy::Return;
use rustc_hir::{
- BareFnTy, BodyId, FnDecl, GenericArg, GenericBound, GenericParam, GenericParamKind, Generics, Impl, ImplItem,
- ImplItemKind, Item, ItemKind, Lifetime, LifetimeName, LifetimeParamKind, PolyTraitRef, PredicateOrigin, TraitFn,
- TraitItem, TraitItemKind, Ty, TyKind, WherePredicate,
+ lang_items, BareFnTy, BodyId, FnDecl, FnSig, GenericArg, GenericBound, GenericParam, GenericParamKind, Generics,
+ Impl, ImplItem, ImplItemKind, Item, ItemKind, Lifetime, LifetimeName, LifetimeParamKind, Node, PolyTraitRef,
+ PredicateOrigin, TraitFn, TraitItem, TraitItemKind, Ty, TyKind, WherePredicate,
};
-use rustc_lint::{LateContext, LateLintPass};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::hir::nested_filter as middle_nested_filter;
-use rustc_middle::ty::TyCtxt;
+use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::def_id::LocalDefId;
use rustc_span::source_map::Span;
@@ -34,8 +34,6 @@ declare_clippy_lint! {
/// ### Known problems
/// - We bail out if the function has a `where` clause where lifetimes
/// are mentioned due to potential false positives.
- /// - Lifetime bounds such as `impl Foo + 'a` and `T: 'a` must be elided with the
- /// placeholder notation `'_` because the fully elided notation leaves the type bound to `'static`.
///
/// ### Example
/// ```rust
@@ -93,7 +91,7 @@ declare_lint_pass!(Lifetimes => [NEEDLESS_LIFETIMES, EXTRA_UNUSED_LIFETIMES]);
impl<'tcx> LateLintPass<'tcx> for Lifetimes {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
if let ItemKind::Fn(ref sig, generics, id) = item.kind {
- check_fn_inner(cx, sig.decl, Some(id), None, generics, item.span, true);
+ check_fn_inner(cx, sig, Some(id), None, generics, item.span, true);
} else if let ItemKind::Impl(impl_) = item.kind {
if !item.span.from_expansion() {
report_extra_impl_lifetimes(cx, impl_);
@@ -106,7 +104,7 @@ impl<'tcx> LateLintPass<'tcx> for Lifetimes {
let report_extra_lifetimes = trait_ref_of_method(cx, item.owner_id.def_id).is_none();
check_fn_inner(
cx,
- sig.decl,
+ sig,
Some(id),
None,
item.generics,
@@ -122,29 +120,21 @@ impl<'tcx> LateLintPass<'tcx> for Lifetimes {
TraitFn::Required(sig) => (None, Some(sig)),
TraitFn::Provided(id) => (Some(id), None),
};
- check_fn_inner(cx, sig.decl, body, trait_sig, item.generics, item.span, true);
+ check_fn_inner(cx, sig, body, trait_sig, item.generics, item.span, true);
}
}
}
-/// The lifetime of a &-reference.
-#[derive(PartialEq, Eq, Hash, Debug, Clone)]
-enum RefLt {
- Unnamed,
- Static,
- Named(LocalDefId),
-}
-
fn check_fn_inner<'tcx>(
cx: &LateContext<'tcx>,
- decl: &'tcx FnDecl<'_>,
+ sig: &'tcx FnSig<'_>,
body: Option<BodyId>,
trait_sig: Option<&[Ident]>,
generics: &'tcx Generics<'_>,
span: Span,
report_extra_lifetimes: bool,
) {
- if span.from_expansion() || has_where_lifetimes(cx, generics) {
+ if in_external_macro(cx.sess(), span) || has_where_lifetimes(cx, generics) {
return;
}
@@ -154,7 +144,11 @@ fn check_fn_inner<'tcx>(
.filter(|param| matches!(param.kind, GenericParamKind::Type { .. }));
for typ in types {
- for pred in generics.bounds_for_param(cx.tcx.hir().local_def_id(typ.hir_id)) {
+ if !typ.span.eq_ctxt(span) {
+ return;
+ }
+
+ for pred in generics.bounds_for_param(typ.def_id) {
if pred.origin == PredicateOrigin::WhereClause {
// has_where_lifetimes checked that this predicate contains no lifetime.
continue;
@@ -163,7 +157,7 @@ fn check_fn_inner<'tcx>(
for bound in pred.bounds {
let mut visitor = RefVisitor::new(cx);
walk_param_bound(&mut visitor, bound);
- if visitor.lts.iter().any(|lt| matches!(lt, RefLt::Named(_))) {
+ if visitor.lts.iter().any(|lt| matches!(lt.res, LifetimeName::Param(_))) {
return;
}
if let GenericBound::Trait(ref trait_ref, _) = *bound {
@@ -190,12 +184,16 @@ fn check_fn_inner<'tcx>(
}
}
- if let Some(elidable_lts) = could_use_elision(cx, decl, body, trait_sig, generics.params) {
+ if let Some((elidable_lts, usages)) = could_use_elision(cx, sig.decl, body, trait_sig, generics.params) {
+ if usages.iter().any(|usage| !usage.ident.span.eq_ctxt(span)) {
+ return;
+ }
+
let lts = elidable_lts
.iter()
// In principle, the result of the call to `Node::ident` could be `unwrap`ped, as `DefId` should refer to a
// `Node::GenericParam`.
- .filter_map(|&(def_id, _)| cx.tcx.hir().get_by_def_id(def_id).ident())
+ .filter_map(|&def_id| cx.tcx.hir().get_by_def_id(def_id).ident())
.map(|ident| ident.to_string())
.collect::<Vec<_>>()
.join(", ");
@@ -203,21 +201,99 @@ fn check_fn_inner<'tcx>(
span_lint_and_then(
cx,
NEEDLESS_LIFETIMES,
- span.with_hi(decl.output.span().hi()),
+ span.with_hi(sig.decl.output.span().hi()),
&format!("the following explicit lifetimes could be elided: {lts}"),
|diag| {
- if let Some(span) = elidable_lts.iter().find_map(|&(_, span)| span) {
- diag.span_help(span, "replace with `'_` in generic arguments such as here");
+ if sig.header.is_async() {
+ // async functions have usages whose spans point at the lifetime declaration which messes up
+ // suggestions
+ return;
+ };
+
+ if let Some(suggestions) = elision_suggestions(cx, generics, &elidable_lts, &usages) {
+ diag.multipart_suggestion("elide the lifetimes", suggestions, Applicability::MachineApplicable);
}
},
);
}
if report_extra_lifetimes {
- self::report_extra_lifetimes(cx, decl, generics);
+ self::report_extra_lifetimes(cx, sig.decl, generics);
}
}
+fn elision_suggestions(
+ cx: &LateContext<'_>,
+ generics: &Generics<'_>,
+ elidable_lts: &[LocalDefId],
+ usages: &[Lifetime],
+) -> Option<Vec<(Span, String)>> {
+ let explicit_params = generics
+ .params
+ .iter()
+ .filter(|param| !param.is_elided_lifetime() && !param.is_impl_trait())
+ .collect::<Vec<_>>();
+
+ let mut suggestions = if elidable_lts.len() == explicit_params.len() {
+ // if all the params are elided remove the whole generic block
+ //
+ // fn x<'a>() {}
+ // ^^^^
+ vec![(generics.span, String::new())]
+ } else {
+ elidable_lts
+ .iter()
+ .map(|&id| {
+ let pos = explicit_params.iter().position(|param| param.def_id == id)?;
+ let param = explicit_params.get(pos)?;
+
+ let span = if let Some(next) = explicit_params.get(pos + 1) {
+ // fn x<'prev, 'a, 'next>() {}
+ // ^^^^
+ param.span.until(next.span)
+ } else {
+ // `pos` should be at least 1 here, because the param in position 0 would either have a `next`
+ // param or would have taken the `elidable_lts.len() == explicit_params.len()` branch.
+ let prev = explicit_params.get(pos - 1)?;
+
+ // fn x<'prev, 'a>() {}
+ // ^^^^
+ param.span.with_lo(prev.span.hi())
+ };
+
+ Some((span, String::new()))
+ })
+ .collect::<Option<Vec<_>>>()?
+ };
+
+ suggestions.extend(
+ usages
+ .iter()
+ .filter(|usage| named_lifetime(usage).map_or(false, |id| elidable_lts.contains(&id)))
+ .map(|usage| {
+ match cx.tcx.hir().get_parent(usage.hir_id) {
+ Node::Ty(Ty {
+ kind: TyKind::Ref(..), ..
+ }) => {
+ // expand `&'a T` to `&'a T`
+ // ^^ ^^^
+ let span = cx
+ .sess()
+ .source_map()
+ .span_extend_while(usage.ident.span, |ch| ch.is_ascii_whitespace())
+ .unwrap_or(usage.ident.span);
+
+ (span, String::new())
+ },
+ // `T<'a>` and `impl Foo + 'a` should be replaced by `'_`
+ _ => (usage.ident.span, String::from("'_")),
+ }
+ }),
+ );
+
+ Some(suggestions)
+}
+
// elision doesn't work for explicit self types, see rust-lang/rust#69064
fn explicit_self_type<'tcx>(cx: &LateContext<'tcx>, func: &FnDecl<'tcx>, ident: Option<Ident>) -> bool {
if_chain! {
@@ -237,13 +313,20 @@ fn explicit_self_type<'tcx>(cx: &LateContext<'tcx>, func: &FnDecl<'tcx>, ident:
}
}
+fn named_lifetime(lt: &Lifetime) -> Option<LocalDefId> {
+ match lt.res {
+ LifetimeName::Param(id) if !lt.is_anonymous() => Some(id),
+ _ => None,
+ }
+}
+
fn could_use_elision<'tcx>(
cx: &LateContext<'tcx>,
func: &'tcx FnDecl<'_>,
body: Option<BodyId>,
trait_sig: Option<&[Ident]>,
named_generics: &'tcx [GenericParam<'_>],
-) -> Option<Vec<(LocalDefId, Option<Span>)>> {
+) -> Option<(Vec<LocalDefId>, Vec<Lifetime>)> {
// There are two scenarios where elision works:
// * no output references, all input references have different LT
// * output references, exactly one input reference with same LT
@@ -251,7 +334,7 @@ fn could_use_elision<'tcx>(
// level of the current item.
// check named LTs
- let allowed_lts = allowed_lts_from(cx.tcx, named_generics);
+ 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);
@@ -301,32 +384,24 @@ fn could_use_elision<'tcx>(
// check for lifetimes from higher scopes
for lt in input_lts.iter().chain(output_lts.iter()) {
- if !allowed_lts.contains(lt) {
+ if let Some(id) = named_lifetime(lt)
+ && !allowed_lts.contains(&id)
+ {
return None;
}
}
// check for higher-ranked trait bounds
if !input_visitor.nested_elision_site_lts.is_empty() || !output_visitor.nested_elision_site_lts.is_empty() {
- let allowed_lts: FxHashSet<_> = allowed_lts
- .iter()
- .filter_map(|lt| match lt {
- RefLt::Named(def_id) => Some(cx.tcx.item_name(def_id.to_def_id())),
- _ => None,
- })
- .collect();
+ let allowed_lts: FxHashSet<_> = allowed_lts.iter().map(|id| cx.tcx.item_name(id.to_def_id())).collect();
for lt in input_visitor.nested_elision_site_lts {
- if let RefLt::Named(def_id) = lt {
- if allowed_lts.contains(&cx.tcx.item_name(def_id.to_def_id())) {
- return None;
- }
+ if allowed_lts.contains(&lt.ident.name) {
+ return None;
}
}
for lt in output_visitor.nested_elision_site_lts {
- if let RefLt::Named(def_id) = lt {
- if allowed_lts.contains(&cx.tcx.item_name(def_id.to_def_id())) {
- return None;
- }
+ if allowed_lts.contains(&lt.ident.name) {
+ return None;
}
}
}
@@ -338,15 +413,10 @@ fn could_use_elision<'tcx>(
let elidable_lts = named_lifetime_occurrences(&input_lts)
.into_iter()
.filter_map(|(def_id, occurrences)| {
- if occurrences == 1 && (input_lts.len() == 1 || !output_lts.contains(&RefLt::Named(def_id))) {
- Some((
- def_id,
- input_visitor
- .lifetime_generic_arg_spans
- .get(&def_id)
- .or_else(|| output_visitor.lifetime_generic_arg_spans.get(&def_id))
- .copied(),
- ))
+ if occurrences == 1
+ && (input_lts.len() == 1 || !output_lts.iter().any(|lt| named_lifetime(lt) == Some(def_id)))
+ {
+ Some(def_id)
} else {
None
}
@@ -354,31 +424,34 @@ fn could_use_elision<'tcx>(
.collect::<Vec<_>>();
if elidable_lts.is_empty() {
- None
- } else {
- Some(elidable_lts)
+ return None;
}
+
+ let usages = itertools::chain(input_lts, output_lts).collect();
+
+ Some((elidable_lts, usages))
}
-fn allowed_lts_from(tcx: TyCtxt<'_>, named_generics: &[GenericParam<'_>]) -> FxHashSet<RefLt> {
- let mut allowed_lts = FxHashSet::default();
- for par in named_generics.iter() {
- if let GenericParamKind::Lifetime { .. } = par.kind {
- allowed_lts.insert(RefLt::Named(tcx.hir().local_def_id(par.hir_id)));
- }
- }
- allowed_lts.insert(RefLt::Unnamed);
- allowed_lts.insert(RefLt::Static);
- allowed_lts
+fn allowed_lts_from(named_generics: &[GenericParam<'_>]) -> FxHashSet<LocalDefId> {
+ named_generics
+ .iter()
+ .filter_map(|par| {
+ if let GenericParamKind::Lifetime { .. } = par.kind {
+ Some(par.def_id)
+ } else {
+ None
+ }
+ })
+ .collect()
}
/// Number of times each named lifetime occurs in the given slice. Returns a vector to preserve
/// relative order.
#[must_use]
-fn named_lifetime_occurrences(lts: &[RefLt]) -> Vec<(LocalDefId, usize)> {
+fn named_lifetime_occurrences(lts: &[Lifetime]) -> Vec<(LocalDefId, usize)> {
let mut occurrences = Vec::new();
for lt in lts {
- if let &RefLt::Named(curr_def_id) = lt {
+ if let Some(curr_def_id) = named_lifetime(lt) {
if let Some(pair) = occurrences
.iter_mut()
.find(|(prev_def_id, _)| *prev_def_id == curr_def_id)
@@ -392,12 +465,10 @@ fn named_lifetime_occurrences(lts: &[RefLt]) -> Vec<(LocalDefId, usize)> {
occurrences
}
-/// A visitor usable for `rustc_front::visit::walk_ty()`.
struct RefVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
- lts: Vec<RefLt>,
- lifetime_generic_arg_spans: FxHashMap<LocalDefId, Span>,
- nested_elision_site_lts: Vec<RefLt>,
+ lts: Vec<Lifetime>,
+ nested_elision_site_lts: Vec<Lifetime>,
unelided_trait_object_lifetime: bool,
}
@@ -406,32 +477,16 @@ impl<'a, 'tcx> RefVisitor<'a, 'tcx> {
Self {
cx,
lts: Vec::new(),
- lifetime_generic_arg_spans: FxHashMap::default(),
nested_elision_site_lts: Vec::new(),
unelided_trait_object_lifetime: false,
}
}
- fn record(&mut self, lifetime: &Option<Lifetime>) {
- if let Some(ref lt) = *lifetime {
- if lt.is_static() {
- self.lts.push(RefLt::Static);
- } else if lt.is_anonymous() {
- // Fresh lifetimes generated should be ignored.
- self.lts.push(RefLt::Unnamed);
- } else if let LifetimeName::Param(def_id) = lt.res {
- self.lts.push(RefLt::Named(def_id));
- }
- } else {
- self.lts.push(RefLt::Unnamed);
- }
- }
-
- fn all_lts(&self) -> Vec<RefLt> {
+ fn all_lts(&self) -> Vec<Lifetime> {
self.lts
.iter()
.chain(self.nested_elision_site_lts.iter())
- .cloned()
+ .copied()
.collect::<Vec<_>>()
}
@@ -443,7 +498,7 @@ impl<'a, 'tcx> RefVisitor<'a, 'tcx> {
impl<'a, 'tcx> Visitor<'tcx> for RefVisitor<'a, 'tcx> {
// for lifetimes as parameters of generics
fn visit_lifetime(&mut self, lifetime: &'tcx Lifetime) {
- self.record(&Some(*lifetime));
+ self.lts.push(*lifetime);
}
fn visit_poly_trait_ref(&mut self, poly_tref: &'tcx PolyTraitRef<'tcx>) {
@@ -468,11 +523,7 @@ impl<'a, 'tcx> Visitor<'tcx> for RefVisitor<'a, 'tcx> {
walk_item(self, item);
self.lts.truncate(len);
self.lts.extend(bounds.iter().filter_map(|bound| match bound {
- GenericArg::Lifetime(l) => Some(if let LifetimeName::Param(def_id) = l.res {
- RefLt::Named(def_id)
- } else {
- RefLt::Unnamed
- }),
+ GenericArg::Lifetime(&l) => Some(l),
_ => None,
}));
},
@@ -492,13 +543,6 @@ impl<'a, 'tcx> Visitor<'tcx> for RefVisitor<'a, 'tcx> {
_ => walk_ty(self, ty),
}
}
-
- fn visit_generic_arg(&mut self, generic_arg: &'tcx GenericArg<'tcx>) {
- if let GenericArg::Lifetime(l) = generic_arg && let LifetimeName::Param(def_id) = l.res {
- self.lifetime_generic_arg_spans.entry(def_id).or_insert(l.ident.span);
- }
- walk_generic_arg(self, generic_arg);
- }
}
/// Are any lifetimes mentioned in the `where` clause? If so, we don't try to
@@ -516,14 +560,18 @@ fn has_where_lifetimes<'tcx>(cx: &LateContext<'tcx>, generics: &'tcx Generics<'_
return true;
}
// if the bounds define new lifetimes, they are fine to occur
- let allowed_lts = allowed_lts_from(cx.tcx, pred.bound_generic_params);
+ let allowed_lts = allowed_lts_from(pred.bound_generic_params);
// now walk the bounds
for bound in pred.bounds.iter() {
walk_param_bound(&mut visitor, bound);
}
// and check that all lifetimes are allowed
- if visitor.all_lts().iter().any(|it| !allowed_lts.contains(it)) {
- return true;
+ for lt in visitor.all_lts() {
+ if let Some(id) = named_lifetime(&lt)
+ && !allowed_lts.contains(&id)
+ {
+ return true;
+ }
}
},
WherePredicate::EqPredicate(ref pred) => {