Skip to content

Commit 55fcd46

Browse files
committed
Extend needless_lifetimes to suggest eliding impl lifetimes
1 parent 2a61f59 commit 55fcd46

1 file changed

Lines changed: 159 additions & 70 deletions

File tree

‎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::{selfas 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-
ifletLifetimeName::Param(def_id) = usage.res
212-
&& elidable_lts.contains(&def_id)
213-
{
214-
returnSome(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-
ifletSome(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+
fnreport_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+
ifletLifetimeName::Param(def_id) = usage.res
230+
&& elidable_lts.contains(&def_id)
231+
{
232+
returnSome(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+
ifletSome(suggestions) = elision_suggestions(cx, generics, elidable_lts, usages){
245+
diag.multipart_suggestion("elide the lifetimes", suggestions,Applicability::MachineApplicable);
246+
}
247+
},
248+
);
249+
}
250+
240251
fnelision_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+
structUsage{
609+
lifetime:Lifetime,
610+
in_where_predicate:bool,
611+
in_generic_arg:bool,
612+
}
613+
597614
structLifetimeChecker<'cx,'tcx,F>{
598615
cx:&'cxLateContext<'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-
fnnew(cx:&'cxLateContext<'tcx>,map:FxHashMap<Symbol,Span>) -> LifetimeChecker<'cx,'tcx,F>{
623+
fnnew(cx:&'cxLateContext<'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>forLifetimeChecker<'cx,'tcx,F>
635+
impl<'tcx,F>Visitor<'tcx>forLifetimeChecker<'_,'tcx,F>
614636
where
615637
F:NestedFilter<'tcx>,
616638
{
@@ -619,18 +641,27 @@ where
619641

620642
// for lifetimes as parameters of generics
621643
fnvisit_lifetime(&mutself,lifetime:&'tcxLifetime){
622-
self.map.remove(&lifetime.ident.name);
644+
ifletLifetimeName::Param(def_id) = lifetime.res
645+
&& letSome(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-
fnvisit_generic_param(&mutself,param:&'tcxGenericParam<'_>){
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-
ifletGenericParamKind::Type{ .. } = param.kind{
632-
walk_generic_param(self, param);
633-
}
655+
fnvisit_where_predicate(&mutself,predicate:&'tcxWherePredicate<'tcx>){
656+
self.where_predicate_depth += 1;
657+
walk_where_predicate(self, predicate);
658+
self.where_predicate_depth -= 1;
659+
}
660+
661+
fnvisit_generic_arg(&mutself,generic_arg:&'tcxGenericArg<'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
fnnested_visit_map(&mutself) -> Self::Map{
@@ -639,44 +670,49 @@ where
639670
}
640671

641672
fnreport_extra_lifetimes<'tcx>(cx:&LateContext<'tcx>,func:&'tcxFnDecl<'_>,generics:&'tcxGenerics<'_>){
642-
leths = generics
673+
letdef_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-
letmut checker = LifetimeChecker::<hir_nested_filter::None>::new(cx,hs);
683+
letmut 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
fnreport_extra_impl_lifetimes<'tcx>(cx:&LateContext<'tcx>,impl_:&'tcxImpl<'_>){
668-
leths = impl_
704+
letdef_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-
letmut checker = LifetimeChecker::<middle_nested_filter::All>::new(cx,hs);
715+
letmut checker = LifetimeChecker::<middle_nested_filter::All>::new(cx,def_ids);
680716

681717
walk_generics(&mut checker, impl_.generics);
682718
ifletSome(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+
fnreport_elidable_impl_lifetimes<'tcx>(
748+
cx:&LateContext<'tcx>,
749+
impl_:&'tcxImpl<'_>,
750+
map:&FxHashMap<LocalDefId,Vec<Usage>>,
751+
){
752+
let single_usages = map
753+
.iter()
754+
.filter_map(|(def_id, usages)| {
755+
iflet[
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
structBodyLifetimeChecker;

0 commit comments

Comments
 (0)