Skip to content

Commit a60e5de

Browse files
committed
needless-lifetime / nested elision sites / PR remarks
1 parent 390a13b commit a60e5de

File tree

4 files changed

+102
-152
lines changed

4 files changed

+102
-152
lines changed

clippy_lints/src/lifetimes.rs

Lines changed: 71 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,22 @@
1+
use crate::utils::paths;
2+
use crate::utils::{get_trait_def_id, in_macro, span_lint, trait_ref_of_method};
13
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
2-
use rustc_hir::def::{DefKind, Res};
34
use rustc_hir::intravisit::{
4-
walk_fn_decl, walk_generic_param, walk_generics, walk_param_bound, walk_trait_ref, walk_ty, NestedVisitorMap,
5-
Visitor,
5+
walk_fn_decl, walk_generic_param, walk_generics, walk_item, walk_param_bound, walk_poly_trait_ref, walk_ty,
6+
NestedVisitorMap, Visitor,
67
};
78
use rustc_hir::FnRetTy::Return;
89
use rustc_hir::{
9-
BodyId, FnDecl, GenericArg, GenericBound, GenericParam, GenericParamKind, Generics, ImplItem, ImplItemKind, Item,
10-
ItemKind, Lifetime, LifetimeName, ParamName, QPath, TraitBoundModifier, TraitFn, TraitItem, TraitItemKind,
11-
TraitRef, Ty, TyKind, WhereClause, WherePredicate,
10+
BareFnTy, BodyId, FnDecl, GenericArg, GenericBound, GenericParam, GenericParamKind, Generics, ImplItem,
11+
ImplItemKind, Item, ItemKind, Lifetime, LifetimeName, ParamName, PolyTraitRef, TraitBoundModifier, TraitFn,
12+
TraitItem, TraitItemKind, Ty, TyKind, WhereClause, WherePredicate,
1213
};
1314
use rustc_lint::{LateContext, LateLintPass};
1415
use rustc_middle::hir::map::Map;
1516
use rustc_session::{declare_lint_pass, declare_tool_lint};
1617
use rustc_span::source_map::Span;
1718
use rustc_span::symbol::{kw, Symbol};
18-
19-
use crate::utils::paths;
20-
use crate::utils::{get_trait_def_id, in_macro, last_path_segment, span_lint, trait_ref_of_method};
19+
use std::iter::FromIterator;
2120

2221
declare_clippy_lint! {
2322
/// **What it does:** Checks for lifetime annotations which can be removed by
@@ -110,7 +109,7 @@ impl<'tcx> LateLintPass<'tcx> for Lifetimes {
110109
}
111110

112111
/// The lifetime of a &-reference.
113-
#[derive(PartialEq, Eq, Hash, Debug)]
112+
#[derive(PartialEq, Eq, Hash, Debug, Clone)]
114113
enum RefLt {
115114
Unnamed,
116115
Static,
@@ -129,15 +128,6 @@ fn check_fn_inner<'tcx>(
129128
return;
130129
}
131130

132-
// fn pointers and closure trait bounds are also lifetime elision sites. This lint does not
133-
// support nested elision sites in a fn item.
134-
if FnPointerOrClosureTraitBoundFinder::find_in_generics(cx, generics)
135-
|| FnPointerOrClosureTraitBoundFinder::find_in_fn_decl(cx, decl)
136-
{
137-
return;
138-
}
139-
140-
let mut bounds_lts = Vec::new();
141131
let types = generics
142132
.params
143133
.iter()
@@ -166,13 +156,12 @@ fn check_fn_inner<'tcx>(
166156
if bound.name != LifetimeName::Static && !bound.is_elided() {
167157
return;
168158
}
169-
bounds_lts.push(bound);
170159
}
171160
}
172161
}
173162
}
174163
}
175-
if could_use_elision(cx, decl, body, &generics.params, bounds_lts) {
164+
if could_use_elision(cx, decl, body, &generics.params) {
176165
span_lint(
177166
cx,
178167
NEEDLESS_LIFETIMES,
@@ -191,7 +180,6 @@ fn could_use_elision<'tcx>(
191180
func: &'tcx FnDecl<'_>,
192181
body: Option<BodyId>,
193182
named_generics: &'tcx [GenericParam<'_>],
194-
bounds_lts: Vec<&'tcx Lifetime>,
195183
) -> bool {
196184
// There are two scenarios where elision works:
197185
// * no output references, all input references have different LT
@@ -214,15 +202,31 @@ fn could_use_elision<'tcx>(
214202
if let Return(ref ty) = func.output {
215203
output_visitor.visit_ty(ty);
216204
}
205+
for lt in named_generics {
206+
input_visitor.visit_generic_param(lt)
207+
}
217208

218-
let input_lts = match input_visitor.into_vec() {
219-
Some(lts) => lts_from_bounds(lts, bounds_lts.into_iter()),
220-
None => return false,
221-
};
222-
let output_lts = match output_visitor.into_vec() {
223-
Some(val) => val,
224-
None => return false,
225-
};
209+
if input_visitor.abort() || output_visitor.abort() {
210+
return false;
211+
}
212+
213+
if allowed_lts
214+
.intersection(&FxHashSet::from_iter(
215+
input_visitor
216+
.nested_elision_site_lts
217+
.iter()
218+
.chain(output_visitor.nested_elision_site_lts.iter())
219+
.cloned()
220+
.filter(|v| matches!(v, RefLt::Named(_))),
221+
))
222+
.next()
223+
.is_some()
224+
{
225+
return false;
226+
}
227+
228+
let input_lts = input_visitor.lts;
229+
let output_lts = output_visitor.lts;
226230

227231
if let Some(body_id) = body {
228232
let mut checker = BodyLifetimeChecker {
@@ -287,35 +291,29 @@ fn allowed_lts_from(named_generics: &[GenericParam<'_>]) -> FxHashSet<RefLt> {
287291
allowed_lts
288292
}
289293

290-
fn lts_from_bounds<'a, T: Iterator<Item = &'a Lifetime>>(mut vec: Vec<RefLt>, bounds_lts: T) -> Vec<RefLt> {
291-
for lt in bounds_lts {
292-
if lt.name != LifetimeName::Static {
293-
vec.push(RefLt::Named(lt.name.ident().name));
294-
}
295-
}
296-
297-
vec
298-
}
299-
300294
/// Number of unique lifetimes in the given vector.
301295
#[must_use]
302296
fn unique_lifetimes(lts: &[RefLt]) -> usize {
303297
lts.iter().collect::<FxHashSet<_>>().len()
304298
}
305299

300+
const CLOSURE_TRAIT_BOUNDS: [&[&str]; 3] = [&paths::FN, &paths::FN_MUT, &paths::FN_ONCE];
301+
306302
/// A visitor usable for `rustc_front::visit::walk_ty()`.
307303
struct RefVisitor<'a, 'tcx> {
308304
cx: &'a LateContext<'tcx>,
309305
lts: Vec<RefLt>,
310-
abort: bool,
306+
nested_elision_site_lts: Vec<RefLt>,
307+
unelided_trait_object_lifetime: bool,
311308
}
312309

313310
impl<'a, 'tcx> RefVisitor<'a, 'tcx> {
314311
fn new(cx: &'a LateContext<'tcx>) -> Self {
315312
Self {
316313
cx,
317314
lts: Vec::new(),
318-
abort: false,
315+
nested_elision_site_lts: Vec::new(),
316+
unelided_trait_object_lifetime: false,
319317
}
320318
}
321319

@@ -335,40 +333,16 @@ impl<'a, 'tcx> RefVisitor<'a, 'tcx> {
335333
}
336334
}
337335

338-
fn into_vec(self) -> Option<Vec<RefLt>> {
339-
if self.abort {
340-
None
341-
} else {
342-
Some(self.lts)
343-
}
336+
fn all_lts(&self) -> Vec<RefLt> {
337+
self.lts
338+
.iter()
339+
.chain(self.nested_elision_site_lts.iter())
340+
.cloned()
341+
.collect::<Vec<_>>()
344342
}
345343

346-
fn collect_anonymous_lifetimes(&mut self, qpath: &QPath<'_>, ty: &Ty<'_>) {
347-
if let Some(ref last_path_segment) = last_path_segment(qpath).args {
348-
if !last_path_segment.parenthesized
349-
&& !last_path_segment
350-
.args
351-
.iter()
352-
.any(|arg| matches!(arg, GenericArg::Lifetime(_)))
353-
{
354-
let hir_id = ty.hir_id;
355-
match self.cx.qpath_res(qpath, hir_id) {
356-
Res::Def(DefKind::TyAlias | DefKind::Struct, def_id) => {
357-
let generics = self.cx.tcx.generics_of(def_id);
358-
for _ in generics.params.as_slice() {
359-
self.record(&None);
360-
}
361-
},
362-
Res::Def(DefKind::Trait, def_id) => {
363-
let trait_def = self.cx.tcx.trait_def(def_id);
364-
for _ in &self.cx.tcx.generics_of(trait_def.def_id).params {
365-
self.record(&None);
366-
}
367-
},
368-
_ => (),
369-
}
370-
}
371-
}
344+
fn abort(&self) -> bool {
345+
self.unelided_trait_object_lifetime
372346
}
373347
}
374348

@@ -380,30 +354,36 @@ impl<'a, 'tcx> Visitor<'tcx> for RefVisitor<'a, 'tcx> {
380354
self.record(&Some(*lifetime));
381355
}
382356

357+
fn visit_poly_trait_ref(&mut self, poly_tref: &'tcx PolyTraitRef<'tcx>, tbm: TraitBoundModifier) {
358+
let trait_ref = &poly_tref.trait_ref;
359+
if CLOSURE_TRAIT_BOUNDS
360+
.iter()
361+
.any(|trait_path| trait_ref.trait_def_id() == get_trait_def_id(self.cx, trait_path))
362+
{
363+
let mut sub_visitor = RefVisitor::new(self.cx);
364+
sub_visitor.visit_trait_ref(trait_ref);
365+
self.nested_elision_site_lts.append(&mut sub_visitor.all_lts());
366+
} else {
367+
walk_poly_trait_ref(self, poly_tref, tbm);
368+
}
369+
}
370+
383371
fn visit_ty(&mut self, ty: &'tcx Ty<'_>) {
384372
match ty.kind {
385-
TyKind::Rptr(ref lt, _) if lt.is_elided() => {
386-
self.record(&None);
387-
},
388-
TyKind::Path(ref path) => {
389-
self.collect_anonymous_lifetimes(path, ty);
390-
},
391373
TyKind::OpaqueDef(item, _) => {
392374
let map = self.cx.tcx.hir();
393-
if let ItemKind::OpaqueTy(ref exist_ty) = map.expect_item(item.id).kind {
394-
for bound in exist_ty.bounds {
395-
if let GenericBound::Outlives(_) = *bound {
396-
self.record(&None);
397-
}
398-
}
399-
} else {
400-
unreachable!()
401-
}
375+
let item = map.expect_item(item.id);
376+
walk_item(self, item);
402377
walk_ty(self, ty);
403378
},
379+
TyKind::BareFn(&BareFnTy { decl, .. }) => {
380+
let mut sub_visitor = RefVisitor::new(self.cx);
381+
sub_visitor.visit_fn_decl(decl);
382+
self.nested_elision_site_lts.append(&mut sub_visitor.all_lts());
383+
},
404384
TyKind::TraitObject(bounds, ref lt) => {
405385
if !lt.is_elided() {
406-
self.abort = true;
386+
self.unelided_trait_object_lifetime = true;
407387
}
408388
for bound in bounds {
409389
self.visit_poly_trait_ref(bound, TraitBoundModifier::None);
@@ -440,16 +420,7 @@ fn has_where_lifetimes<'tcx>(cx: &LateContext<'tcx>, where_clause: &'tcx WhereCl
440420
walk_param_bound(&mut visitor, bound);
441421
}
442422
// and check that all lifetimes are allowed
443-
match visitor.into_vec() {
444-
None => return false,
445-
Some(lts) => {
446-
for lt in lts {
447-
if !allowed_lts.contains(&lt) {
448-
return true;
449-
}
450-
}
451-
},
452-
}
423+
return visitor.all_lts().iter().any(|it| !allowed_lts.contains(it));
453424
},
454425
WherePredicate::EqPredicate(ref pred) => {
455426
let mut visitor = RefVisitor::new(cx);
@@ -533,54 +504,3 @@ impl<'tcx> Visitor<'tcx> for BodyLifetimeChecker {
533504
NestedVisitorMap::None
534505
}
535506
}
536-
537-
const CLOSURE_TRAIT_BOUNDS: [&[&str]; 3] = [&paths::FN, &paths::FN_MUT, &paths::FN_ONCE];
538-
539-
struct FnPointerOrClosureTraitBoundFinder<'a, 'tcx> {
540-
cx: &'a LateContext<'tcx>,
541-
found: bool,
542-
}
543-
544-
impl<'a, 'tcx> FnPointerOrClosureTraitBoundFinder<'a, 'tcx> {
545-
fn find_in_generics(cx: &'a LateContext<'tcx>, generics: &'tcx Generics<'tcx>) -> bool {
546-
let mut finder = Self { cx, found: false };
547-
finder.visit_generics(generics);
548-
finder.found
549-
}
550-
551-
fn find_in_fn_decl(cx: &'a LateContext<'tcx>, generics: &'tcx FnDecl<'tcx>) -> bool {
552-
let mut finder = Self { cx, found: false };
553-
finder.visit_fn_decl(generics);
554-
finder.found
555-
}
556-
}
557-
558-
impl<'a, 'tcx> Visitor<'tcx> for FnPointerOrClosureTraitBoundFinder<'a, 'tcx> {
559-
type Map = Map<'tcx>;
560-
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
561-
NestedVisitorMap::None
562-
}
563-
564-
fn visit_trait_ref(&mut self, tref: &'tcx TraitRef<'tcx>) {
565-
if CLOSURE_TRAIT_BOUNDS
566-
.iter()
567-
.any(|trait_path| tref.trait_def_id() == get_trait_def_id(self.cx, trait_path))
568-
{
569-
self.found = true;
570-
}
571-
walk_trait_ref(self, tref);
572-
}
573-
574-
fn visit_ty(&mut self, ty: &'tcx Ty<'tcx>) {
575-
match ty.kind {
576-
TyKind::BareFn(..) => self.found = true,
577-
TyKind::OpaqueDef(item_id, _) => {
578-
let map = self.cx.tcx.hir();
579-
let item = map.expect_item(item_id.id);
580-
self.visit_item(item);
581-
},
582-
_ => (),
583-
}
584-
walk_ty(self, ty);
585-
}
586-
}

tests/ui/crashes/ice-2774.stderr

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)
2+
--> $DIR/ice-2774.rs:17:1
3+
|
4+
LL | pub fn add_barfoos_to_foos<'a>(bars: &HashSet<&'a Bar>) {
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: `-D clippy::needless-lifetimes` implied by `-D warnings`
8+
9+
error: aborting due to previous error
10+
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)
2+
--> $DIR/needless_lifetimes_impl_trait.rs:17:5
3+
|
4+
LL | fn baz<'a>(&'a self) -> impl Foo + 'a {
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
note: the lint level is defined here
8+
--> $DIR/needless_lifetimes_impl_trait.rs:3:9
9+
|
10+
LL | #![deny(clippy::needless_lifetimes)]
11+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
12+
13+
error: aborting due to previous error
14+

tests/ui/needless_lifetimes.stderr

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ error: explicit lifetimes given in parameter types where they could be elided (o
3636
LL | fn lifetime_param_2<'a, 'b>(_x: Ref<'a>, _y: &'b u8) {}
3737
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
3838

39+
error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)
40+
--> $DIR/needless_lifetimes.rs:86:1
41+
|
42+
LL | fn fn_bound_2<'a, F, I>(_m: Lt<'a, I>, _f: F) -> Lt<'a, I>
43+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
44+
3945
error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)
4046
--> $DIR/needless_lifetimes.rs:120:5
4147
|
@@ -96,5 +102,5 @@ error: explicit lifetimes given in parameter types where they could be elided (o
96102
LL | fn needless_lt<'a>(_x: &'a u8) {}
97103
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
98104

99-
error: aborting due to 16 previous errors
105+
error: aborting due to 17 previous errors
100106

0 commit comments

Comments
 (0)