Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit d431373

Browse files
committed
Auto merge of rust-lang#5978 - montrivo:needless-lifetime, r=ebroto
needless-lifetime - nested elision sites Closes rust-lang#2944 changelog: fix needless-lifetime nested elision site FPs
2 parents abce9e7 + cb2be6f commit d431373

File tree

6 files changed

+251
-93
lines changed

6 files changed

+251
-93
lines changed

clippy_lints/src/lifetimes.rs

Lines changed: 77 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +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_ty, NestedVisitorMap, Visitor,
5+
walk_fn_decl, walk_generic_param, walk_generics, walk_item, walk_param_bound, walk_poly_trait_ref, walk_ty,
6+
NestedVisitorMap, Visitor,
57
};
68
use rustc_hir::FnRetTy::Return;
79
use rustc_hir::{
8-
BodyId, FnDecl, GenericArg, GenericBound, GenericParam, GenericParamKind, Generics, ImplItem, ImplItemKind, Item,
9-
ItemKind, Lifetime, LifetimeName, ParamName, QPath, TraitBoundModifier, TraitFn, TraitItem, TraitItemKind, Ty,
10-
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,
1113
};
1214
use rustc_lint::{LateContext, LateLintPass};
1315
use rustc_middle::hir::map::Map;
1416
use rustc_session::{declare_lint_pass, declare_tool_lint};
1517
use rustc_span::source_map::Span;
1618
use rustc_span::symbol::{kw, Symbol};
17-
18-
use crate::utils::{in_macro, last_path_segment, span_lint, trait_ref_of_method};
19+
use std::iter::FromIterator;
1920

2021
declare_clippy_lint! {
2122
/// **What it does:** Checks for lifetime annotations which can be removed by
@@ -25,8 +26,11 @@ declare_clippy_lint! {
2526
/// complicated, while there is nothing out of the ordinary going on. Removing
2627
/// them leads to more readable code.
2728
///
28-
/// **Known problems:** Potential false negatives: we bail out if the function
29-
/// has a `where` clause where lifetimes are mentioned.
29+
/// **Known problems:**
30+
/// - We bail out if the function has a `where` clause where lifetimes
31+
/// are mentioned due to potenial false positives.
32+
/// - Lifetime bounds such as `impl Foo + 'a` and `T: 'a` must be elided with the
33+
/// placeholder notation `'_` because the fully elided notation leaves the type bound to `'static`.
3034
///
3135
/// **Example:**
3236
/// ```rust
@@ -108,7 +112,7 @@ impl<'tcx> LateLintPass<'tcx> for Lifetimes {
108112
}
109113

110114
/// The lifetime of a &-reference.
111-
#[derive(PartialEq, Eq, Hash, Debug)]
115+
#[derive(PartialEq, Eq, Hash, Debug, Clone)]
112116
enum RefLt {
113117
Unnamed,
114118
Static,
@@ -127,7 +131,6 @@ fn check_fn_inner<'tcx>(
127131
return;
128132
}
129133

130-
let mut bounds_lts = Vec::new();
131134
let types = generics
132135
.params
133136
.iter()
@@ -156,13 +159,12 @@ fn check_fn_inner<'tcx>(
156159
if bound.name != LifetimeName::Static && !bound.is_elided() {
157160
return;
158161
}
159-
bounds_lts.push(bound);
160162
}
161163
}
162164
}
163165
}
164166
}
165-
if could_use_elision(cx, decl, body, &generics.params, bounds_lts) {
167+
if could_use_elision(cx, decl, body, &generics.params) {
166168
span_lint(
167169
cx,
168170
NEEDLESS_LIFETIMES,
@@ -181,7 +183,6 @@ fn could_use_elision<'tcx>(
181183
func: &'tcx FnDecl<'_>,
182184
body: Option<BodyId>,
183185
named_generics: &'tcx [GenericParam<'_>],
184-
bounds_lts: Vec<&'tcx Lifetime>,
185186
) -> bool {
186187
// There are two scenarios where elision works:
187188
// * no output references, all input references have different LT
@@ -204,15 +205,31 @@ fn could_use_elision<'tcx>(
204205
if let Return(ref ty) = func.output {
205206
output_visitor.visit_ty(ty);
206207
}
208+
for lt in named_generics {
209+
input_visitor.visit_generic_param(lt)
210+
}
211+
212+
if input_visitor.abort() || output_visitor.abort() {
213+
return false;
214+
}
207215

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

217234
if let Some(body_id) = body {
218235
let mut checker = BodyLifetimeChecker {
@@ -277,35 +294,29 @@ fn allowed_lts_from(named_generics: &[GenericParam<'_>]) -> FxHashSet<RefLt> {
277294
allowed_lts
278295
}
279296

280-
fn lts_from_bounds<'a, T: Iterator<Item = &'a Lifetime>>(mut vec: Vec<RefLt>, bounds_lts: T) -> Vec<RefLt> {
281-
for lt in bounds_lts {
282-
if lt.name != LifetimeName::Static {
283-
vec.push(RefLt::Named(lt.name.ident().name));
284-
}
285-
}
286-
287-
vec
288-
}
289-
290297
/// Number of unique lifetimes in the given vector.
291298
#[must_use]
292299
fn unique_lifetimes(lts: &[RefLt]) -> usize {
293300
lts.iter().collect::<FxHashSet<_>>().len()
294301
}
295302

303+
const CLOSURE_TRAIT_BOUNDS: [&[&str]; 3] = [&paths::FN, &paths::FN_MUT, &paths::FN_ONCE];
304+
296305
/// A visitor usable for `rustc_front::visit::walk_ty()`.
297306
struct RefVisitor<'a, 'tcx> {
298307
cx: &'a LateContext<'tcx>,
299308
lts: Vec<RefLt>,
300-
abort: bool,
309+
nested_elision_site_lts: Vec<RefLt>,
310+
unelided_trait_object_lifetime: bool,
301311
}
302312

303313
impl<'a, 'tcx> RefVisitor<'a, 'tcx> {
304314
fn new(cx: &'a LateContext<'tcx>) -> Self {
305315
Self {
306316
cx,
307317
lts: Vec::new(),
308-
abort: false,
318+
nested_elision_site_lts: Vec::new(),
319+
unelided_trait_object_lifetime: false,
309320
}
310321
}
311322

@@ -325,40 +336,16 @@ impl<'a, 'tcx> RefVisitor<'a, 'tcx> {
325336
}
326337
}
327338

328-
fn into_vec(self) -> Option<Vec<RefLt>> {
329-
if self.abort {
330-
None
331-
} else {
332-
Some(self.lts)
333-
}
339+
fn all_lts(&self) -> Vec<RefLt> {
340+
self.lts
341+
.iter()
342+
.chain(self.nested_elision_site_lts.iter())
343+
.cloned()
344+
.collect::<Vec<_>>()
334345
}
335346

336-
fn collect_anonymous_lifetimes(&mut self, qpath: &QPath<'_>, ty: &Ty<'_>) {
337-
if let Some(ref last_path_segment) = last_path_segment(qpath).args {
338-
if !last_path_segment.parenthesized
339-
&& !last_path_segment
340-
.args
341-
.iter()
342-
.any(|arg| matches!(arg, GenericArg::Lifetime(_)))
343-
{
344-
let hir_id = ty.hir_id;
345-
match self.cx.qpath_res(qpath, hir_id) {
346-
Res::Def(DefKind::TyAlias | DefKind::Struct, def_id) => {
347-
let generics = self.cx.tcx.generics_of(def_id);
348-
for _ in generics.params.as_slice() {
349-
self.record(&None);
350-
}
351-
},
352-
Res::Def(DefKind::Trait, def_id) => {
353-
let trait_def = self.cx.tcx.trait_def(def_id);
354-
for _ in &self.cx.tcx.generics_of(trait_def.def_id).params {
355-
self.record(&None);
356-
}
357-
},
358-
_ => (),
359-
}
360-
}
361-
}
347+
fn abort(&self) -> bool {
348+
self.unelided_trait_object_lifetime
362349
}
363350
}
364351

@@ -370,30 +357,37 @@ impl<'a, 'tcx> Visitor<'tcx> for RefVisitor<'a, 'tcx> {
370357
self.record(&Some(*lifetime));
371358
}
372359

360+
fn visit_poly_trait_ref(&mut self, poly_tref: &'tcx PolyTraitRef<'tcx>, tbm: TraitBoundModifier) {
361+
let trait_ref = &poly_tref.trait_ref;
362+
if CLOSURE_TRAIT_BOUNDS
363+
.iter()
364+
.any(|trait_path| trait_ref.trait_def_id() == get_trait_def_id(self.cx, trait_path))
365+
{
366+
let mut sub_visitor = RefVisitor::new(self.cx);
367+
sub_visitor.visit_trait_ref(trait_ref);
368+
self.nested_elision_site_lts.append(&mut sub_visitor.all_lts());
369+
} else {
370+
walk_poly_trait_ref(self, poly_tref, tbm);
371+
}
372+
}
373+
373374
fn visit_ty(&mut self, ty: &'tcx Ty<'_>) {
374375
match ty.kind {
375-
TyKind::Rptr(ref lt, _) if lt.is_elided() => {
376-
self.record(&None);
377-
},
378-
TyKind::Path(ref path) => {
379-
self.collect_anonymous_lifetimes(path, ty);
380-
},
381376
TyKind::OpaqueDef(item, _) => {
382377
let map = self.cx.tcx.hir();
383-
if let ItemKind::OpaqueTy(ref exist_ty) = map.expect_item(item.id).kind {
384-
for bound in exist_ty.bounds {
385-
if let GenericBound::Outlives(_) = *bound {
386-
self.record(&None);
387-
}
388-
}
389-
} else {
390-
unreachable!()
391-
}
378+
let item = map.expect_item(item.id);
379+
walk_item(self, item);
392380
walk_ty(self, ty);
393381
},
382+
TyKind::BareFn(&BareFnTy { decl, .. }) => {
383+
let mut sub_visitor = RefVisitor::new(self.cx);
384+
sub_visitor.visit_fn_decl(decl);
385+
self.nested_elision_site_lts.append(&mut sub_visitor.all_lts());
386+
return;
387+
},
394388
TyKind::TraitObject(bounds, ref lt) => {
395389
if !lt.is_elided() {
396-
self.abort = true;
390+
self.unelided_trait_object_lifetime = true;
397391
}
398392
for bound in bounds {
399393
self.visit_poly_trait_ref(bound, TraitBoundModifier::None);
@@ -430,16 +424,7 @@ fn has_where_lifetimes<'tcx>(cx: &LateContext<'tcx>, where_clause: &'tcx WhereCl
430424
walk_param_bound(&mut visitor, bound);
431425
}
432426
// and check that all lifetimes are allowed
433-
match visitor.into_vec() {
434-
None => return false,
435-
Some(lts) => {
436-
for lt in lts {
437-
if !allowed_lts.contains(&lt) {
438-
return true;
439-
}
440-
}
441-
},
442-
}
427+
return visitor.all_lts().iter().any(|it| !allowed_lts.contains(it));
443428
},
444429
WherePredicate::EqPredicate(ref pred) => {
445430
let mut visitor = RefVisitor::new(cx);

clippy_lints/src/utils/paths.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ pub const FILE_TYPE: [&str; 3] = ["std", "fs", "FileType"];
4242
pub const FMT_ARGUMENTS_NEW_V1: [&str; 4] = ["core", "fmt", "Arguments", "new_v1"];
4343
pub const FMT_ARGUMENTS_NEW_V1_FORMATTED: [&str; 4] = ["core", "fmt", "Arguments", "new_v1_formatted"];
4444
pub const FMT_ARGUMENTV1_NEW: [&str; 4] = ["core", "fmt", "ArgumentV1", "new"];
45+
pub const FN: [&str; 3] = ["core", "ops", "Fn"];
46+
pub const FN_MUT: [&str; 3] = ["core", "ops", "FnMut"];
47+
pub const FN_ONCE: [&str; 3] = ["core", "ops", "FnOnce"];
4548
pub const FROM_FROM: [&str; 4] = ["core", "convert", "From", "from"];
4649
pub const FROM_TRAIT: [&str; 3] = ["core", "convert", "From"];
4750
pub const FUTURE_FROM_GENERATOR: [&str; 3] = ["core", "future", "from_generator"];

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:15: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:15: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:1:9
9+
|
10+
LL | #![deny(clippy::needless_lifetimes)]
11+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
12+
13+
error: aborting due to previous error
14+

0 commit comments

Comments
 (0)