Skip to content

Commit ea03e68

Browse files
committed
Extend needless_lifetimes to suggest eliding impl lifetimes
1 parent 43e3384 commit ea03e68

File tree

1 file changed

+159
-70
lines changed

1 file changed

+159
-70
lines changed

clippy_lints/src/lifetimes.rs

Lines changed: 159 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ use rustc_errors::Applicability;
66
use rustc_hir::FnRetTy::Return;
77
use rustc_hir::intravisit::nested_filter::{self as hir_nested_filter, NestedFilter};
88
use rustc_hir::intravisit::{
9-
Visitor, walk_fn_decl, walk_generic_param, walk_generics, walk_impl_item_ref, walk_item, walk_param_bound,
10-
walk_poly_trait_ref, walk_trait_ref, walk_ty,
9+
Visitor, walk_fn_decl, walk_generic_arg, walk_generics, walk_impl_item_ref, walk_item, walk_param_bound,
10+
walk_poly_trait_ref, walk_trait_ref, walk_ty, walk_where_predicate,
1111
};
1212
use rustc_hir::{
1313
BareFnTy, BodyId, FnDecl, FnSig, GenericArg, GenericBound, GenericParam, GenericParamKind, Generics, Impl,
@@ -21,7 +21,7 @@ use rustc_middle::lint::in_external_macro;
2121
use rustc_session::declare_lint_pass;
2222
use rustc_span::Span;
2323
use rustc_span::def_id::LocalDefId;
24-
use rustc_span::symbol::{Ident, Symbol, kw};
24+
use rustc_span::symbol::{Ident, kw};
2525
use std::ops::ControlFlow;
2626

2727
declare_clippy_lint! {
@@ -191,52 +191,63 @@ fn check_fn_inner<'tcx>(
191191
if usages.iter().any(|usage| !usage.ident.span.eq_ctxt(span)) {
192192
return;
193193
}
194-
195-
let lts = elidable_lts
196-
.iter()
197-
// In principle, the result of the call to `Node::ident` could be `unwrap`ped, as `DefId` should refer to a
198-
// `Node::GenericParam`.
199-
.filter_map(|&def_id| cx.tcx.hir_node_by_def_id(def_id).ident())
200-
.map(|ident| ident.to_string())
201-
.collect::<Vec<_>>()
202-
.join(", ");
203-
204-
span_lint_and_then(
205-
cx,
206-
NEEDLESS_LIFETIMES,
207-
elidable_lts
208-
.iter()
209-
.map(|&lt| cx.tcx.def_span(lt))
210-
.chain(usages.iter().filter_map(|usage| {
211-
if let LifetimeName::Param(def_id) = usage.res
212-
&& elidable_lts.contains(&def_id)
213-
{
214-
return Some(usage.ident.span);
215-
}
216-
217-
None
218-
}))
219-
.collect_vec(),
220-
format!("the following explicit lifetimes could be elided: {lts}"),
221-
|diag| {
222-
if sig.header.is_async() {
223-
// async functions have usages whose spans point at the lifetime declaration which messes up
224-
// suggestions
225-
return;
226-
};
227-
228-
if let Some(suggestions) = elision_suggestions(cx, generics, &elidable_lts, &usages) {
229-
diag.multipart_suggestion("elide the lifetimes", suggestions, Applicability::MachineApplicable);
230-
}
231-
},
232-
);
194+
// async functions have usages whose spans point at the lifetime declaration which messes up
195+
// suggestions
196+
let include_suggestions = !sig.header.is_async();
197+
report_elidable_lifetimes(cx, generics, &elidable_lts, &usages, include_suggestions);
233198
}
234199

235200
if report_extra_lifetimes {
236201
self::report_extra_lifetimes(cx, sig.decl, generics);
237202
}
238203
}
239204

205+
/// Generate diagnostic messages for elidable lifetimes.
206+
fn report_elidable_lifetimes(
207+
cx: &LateContext<'_>,
208+
generics: &Generics<'_>,
209+
elidable_lts: &[LocalDefId],
210+
usages: &[Lifetime],
211+
include_suggestions: bool,
212+
) {
213+
let lts = elidable_lts
214+
.iter()
215+
// In principle, the result of the call to `Node::ident` could be `unwrap`ped, as `DefId` should refer to a
216+
// `Node::GenericParam`.
217+
.filter_map(|&def_id| cx.tcx.hir_node_by_def_id(def_id).ident())
218+
.map(|ident| ident.to_string())
219+
.collect::<Vec<_>>()
220+
.join(", ");
221+
222+
span_lint_and_then(
223+
cx,
224+
NEEDLESS_LIFETIMES,
225+
elidable_lts
226+
.iter()
227+
.map(|&lt| cx.tcx.def_span(lt))
228+
.chain(usages.iter().filter_map(|usage| {
229+
if let LifetimeName::Param(def_id) = usage.res
230+
&& elidable_lts.contains(&def_id)
231+
{
232+
return Some(usage.ident.span);
233+
}
234+
235+
None
236+
}))
237+
.collect_vec(),
238+
format!("the following explicit lifetimes could be elided: {lts}"),
239+
|diag| {
240+
if !include_suggestions {
241+
return;
242+
};
243+
244+
if let Some(suggestions) = elision_suggestions(cx, generics, elidable_lts, usages) {
245+
diag.multipart_suggestion("elide the lifetimes", suggestions, Applicability::MachineApplicable);
246+
}
247+
},
248+
);
249+
}
250+
240251
fn elision_suggestions(
241252
cx: &LateContext<'_>,
242253
generics: &Generics<'_>,
@@ -594,23 +605,34 @@ fn has_where_lifetimes<'tcx>(cx: &LateContext<'tcx>, generics: &'tcx Generics<'_
594605
false
595606
}
596607

608+
struct Usage {
609+
lifetime: Lifetime,
610+
in_where_predicate: bool,
611+
in_generic_arg: bool,
612+
}
613+
597614
struct LifetimeChecker<'cx, 'tcx, F> {
598615
cx: &'cx LateContext<'tcx>,
599-
map: FxHashMap<Symbol, Span>,
616+
map: FxHashMap<LocalDefId, Vec<Usage>>,
617+
where_predicate_depth: usize,
618+
generic_arg_depth: usize,
600619
phantom: std::marker::PhantomData<F>,
601620
}
602621

603622
impl<'cx, 'tcx, F> LifetimeChecker<'cx, 'tcx, F> {
604-
fn new(cx: &'cx LateContext<'tcx>, map: FxHashMap<Symbol, Span>) -> LifetimeChecker<'cx, 'tcx, F> {
623+
fn new(cx: &'cx LateContext<'tcx>, def_ids: Vec<LocalDefId>) -> LifetimeChecker<'cx, 'tcx, F> {
624+
let map = def_ids.into_iter().map(|def_id| (def_id, Vec::new())).collect();
605625
Self {
606626
cx,
607627
map,
628+
where_predicate_depth: 0,
629+
generic_arg_depth: 0,
608630
phantom: std::marker::PhantomData,
609631
}
610632
}
611633
}
612634

613-
impl<'cx, 'tcx, F> Visitor<'tcx> for LifetimeChecker<'cx, 'tcx, F>
635+
impl<'tcx, F> Visitor<'tcx> for LifetimeChecker<'_, 'tcx, F>
614636
where
615637
F: NestedFilter<'tcx>,
616638
{
@@ -619,18 +641,27 @@ where
619641

620642
// for lifetimes as parameters of generics
621643
fn visit_lifetime(&mut self, lifetime: &'tcx Lifetime) {
622-
self.map.remove(&lifetime.ident.name);
644+
if let LifetimeName::Param(def_id) = lifetime.res
645+
&& let Some(usages) = self.map.get_mut(&def_id)
646+
{
647+
usages.push(Usage {
648+
lifetime: *lifetime,
649+
in_where_predicate: self.where_predicate_depth != 0,
650+
in_generic_arg: self.generic_arg_depth != 0,
651+
});
652+
}
623653
}
624654

625-
fn visit_generic_param(&mut self, param: &'tcx GenericParam<'_>) {
626-
// don't actually visit `<'a>` or `<'a: 'b>`
627-
// we've already visited the `'a` declarations and
628-
// don't want to spuriously remove them
629-
// `'b` in `'a: 'b` is useless unless used elsewhere in
630-
// a non-lifetime bound
631-
if let GenericParamKind::Type { .. } = param.kind {
632-
walk_generic_param(self, param);
633-
}
655+
fn visit_where_predicate(&mut self, predicate: &'tcx WherePredicate<'tcx>) {
656+
self.where_predicate_depth += 1;
657+
walk_where_predicate(self, predicate);
658+
self.where_predicate_depth -= 1;
659+
}
660+
661+
fn visit_generic_arg(&mut self, generic_arg: &'tcx GenericArg<'tcx>) -> Self::Result {
662+
self.generic_arg_depth += 1;
663+
walk_generic_arg(self, generic_arg);
664+
self.generic_arg_depth -= 1;
634665
}
635666

636667
fn nested_visit_map(&mut self) -> Self::Map {
@@ -639,44 +670,49 @@ where
639670
}
640671

641672
fn report_extra_lifetimes<'tcx>(cx: &LateContext<'tcx>, func: &'tcx FnDecl<'_>, generics: &'tcx Generics<'_>) {
642-
let hs = generics
673+
let def_ids = generics
643674
.params
644675
.iter()
645676
.filter_map(|par| match par.kind {
646677
GenericParamKind::Lifetime {
647678
kind: LifetimeParamKind::Explicit,
648-
} => Some((par.name.ident().name, par.span)),
679+
} => Some(par.def_id),
649680
_ => None,
650681
})
651682
.collect();
652-
let mut checker = LifetimeChecker::<hir_nested_filter::None>::new(cx, hs);
683+
let mut checker = LifetimeChecker::<hir_nested_filter::None>::new(cx, def_ids);
653684

654685
walk_generics(&mut checker, generics);
655686
walk_fn_decl(&mut checker, func);
656687

657-
for &v in checker.map.values() {
658-
span_lint(
659-
cx,
660-
EXTRA_UNUSED_LIFETIMES,
661-
v,
662-
"this lifetime isn't used in the function definition",
663-
);
688+
for (def_id, usages) in checker.map {
689+
if usages
690+
.iter()
691+
.all(|usage| usage.in_where_predicate && !usage.in_generic_arg)
692+
{
693+
span_lint(
694+
cx,
695+
EXTRA_UNUSED_LIFETIMES,
696+
cx.tcx.def_span(def_id),
697+
"this lifetime isn't used in the function definition",
698+
);
699+
}
664700
}
665701
}
666702

667703
fn report_extra_impl_lifetimes<'tcx>(cx: &LateContext<'tcx>, impl_: &'tcx Impl<'_>) {
668-
let hs = impl_
704+
let def_ids = impl_
669705
.generics
670706
.params
671707
.iter()
672708
.filter_map(|par| match par.kind {
673709
GenericParamKind::Lifetime {
674710
kind: LifetimeParamKind::Explicit,
675-
} => Some((par.name.ident().name, par.span)),
711+
} => Some(par.def_id),
676712
_ => None,
677713
})
678714
.collect();
679-
let mut checker = LifetimeChecker::<middle_nested_filter::All>::new(cx, hs);
715+
let mut checker = LifetimeChecker::<middle_nested_filter::All>::new(cx, def_ids);
680716

681717
walk_generics(&mut checker, impl_.generics);
682718
if let Some(ref trait_ref) = impl_.of_trait {
@@ -687,9 +723,62 @@ fn report_extra_impl_lifetimes<'tcx>(cx: &LateContext<'tcx>, impl_: &'tcx Impl<'
687723
walk_impl_item_ref(&mut checker, item);
688724
}
689725

690-
for &v in checker.map.values() {
691-
span_lint(cx, EXTRA_UNUSED_LIFETIMES, v, "this lifetime isn't used in the impl");
726+
for (&def_id, usages) in &checker.map {
727+
if usages
728+
.iter()
729+
.all(|usage| usage.in_where_predicate && !usage.in_generic_arg)
730+
{
731+
span_lint(
732+
cx,
733+
EXTRA_UNUSED_LIFETIMES,
734+
cx.tcx.def_span(def_id),
735+
"this lifetime isn't used in the impl",
736+
);
737+
}
692738
}
739+
740+
report_elidable_impl_lifetimes(cx, impl_, &checker.map);
741+
}
742+
743+
// An `impl` lifetime is elidable if it satisfies the following conditions:
744+
// - It is used exactly once.
745+
// - That single use is not in a `GenericArg` in a `WherePredicate`. (Note that a `GenericArg` is
746+
// different from a `GenericParam`.)
747+
fn report_elidable_impl_lifetimes<'tcx>(
748+
cx: &LateContext<'tcx>,
749+
impl_: &'tcx Impl<'_>,
750+
map: &FxHashMap<LocalDefId, Vec<Usage>>,
751+
) {
752+
let single_usages = map
753+
.iter()
754+
.filter_map(|(def_id, usages)| {
755+
if let [
756+
Usage {
757+
lifetime,
758+
in_where_predicate: false,
759+
..
760+
}
761+
| Usage {
762+
lifetime,
763+
in_generic_arg: false,
764+
..
765+
},
766+
] = usages.as_slice()
767+
{
768+
Some((def_id, lifetime))
769+
} else {
770+
None
771+
}
772+
})
773+
.collect::<Vec<_>>();
774+
775+
if single_usages.is_empty() {
776+
return;
777+
}
778+
779+
let (elidable_lts, usages): (Vec<_>, Vec<_>) = single_usages.into_iter().unzip();
780+
781+
report_elidable_lifetimes(cx, impl_.generics, &elidable_lts, &usages, true);
693782
}
694783

695784
struct BodyLifetimeChecker;

0 commit comments

Comments
 (0)