Skip to content

Commit 7600535

Browse files
committed
Auto merge of #9743 - smoelius:improve-needless-lifetimes, r=Alexendoo
Improve `needless_lifetimes` This PR makes the following improvements to `needless_lifetimes`. * It fixes the following false negative, where `foo` is flagged but `bar` is not: ```rust fn foo<'a>(x: &'a u8, y: &'_ u8) {} fn bar<'a>(x: &'a u8, y: &'_ u8, z: &'_ u8) {} ``` * It flags more cases, generally. Previously, `needless_borrow` required *all* lifetimes to be used only once. With the changes, individual lifetimes are flagged for being used only once, even if not all lifetimes are. * Finally, it tries to produce more clear error messages. changelog: fix `needless_lifetimes` false negative involving functions with multiple unnamed lifetimes changelog: in `needless_lifetimes`, flag individual lifetimes used only once, rather than require all lifetimes to be used only once changelog: in `needless_lifetimes`, emit "replace with `'_`" warnings only when applicable, and point to a generic argument
2 parents 37d338c + c0d9285 commit 7600535

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+457
-213
lines changed

clippy_lints/src/booleans.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -481,7 +481,7 @@ impl<'a, 'tcx> Visitor<'tcx> for NonminimalBoolVisitor<'a, 'tcx> {
481481
}
482482
}
483483

484-
fn implements_ord<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> bool {
484+
fn implements_ord(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
485485
let ty = cx.typeck_results().expr_ty(expr);
486486
cx.tcx
487487
.get_diagnostic_item(sym::Ord)

clippy_lints/src/dereference.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1232,8 +1232,8 @@ fn is_mixed_projection_predicate<'tcx>(
12321232
}
12331233
}
12341234

1235-
fn referent_used_exactly_once<'a, 'tcx>(
1236-
cx: &'a LateContext<'tcx>,
1235+
fn referent_used_exactly_once<'tcx>(
1236+
cx: &LateContext<'tcx>,
12371237
possible_borrowers: &mut Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>,
12381238
reference: &Expr<'tcx>,
12391239
) -> bool {

clippy_lints/src/doc.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -336,8 +336,8 @@ impl<'tcx> LateLintPass<'tcx> for DocMarkdown {
336336
}
337337
}
338338

339-
fn lint_for_missing_headers<'tcx>(
340-
cx: &LateContext<'tcx>,
339+
fn lint_for_missing_headers(
340+
cx: &LateContext<'_>,
341341
def_id: LocalDefId,
342342
span: impl Into<MultiSpan> + Copy,
343343
sig: &hir::FnSig<'_>,
@@ -467,7 +467,7 @@ struct DocHeaders {
467467
panics: bool,
468468
}
469469

470-
fn check_attrs<'a>(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &'a [Attribute]) -> DocHeaders {
470+
fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[Attribute]) -> DocHeaders {
471471
use pulldown_cmark::{BrokenLink, CowStr, Options};
472472
/// We don't want the parser to choke on intra doc links. Since we don't
473473
/// actually care about rendering them, just pretend that all broken links are

clippy_lints/src/fallible_impl_from.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ impl<'tcx> LateLintPass<'tcx> for FallibleImplFrom {
6464
}
6565
}
6666

67-
fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, impl_items: &[hir::ImplItemRef]) {
67+
fn lint_impl_body(cx: &LateContext<'_>, impl_span: Span, impl_items: &[hir::ImplItemRef]) {
6868
use rustc_hir::intravisit::{self, Visitor};
6969
use rustc_hir::{Expr, ImplItemKind};
7070

clippy_lints/src/implicit_hasher.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitHasher {
6666
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
6767
use rustc_span::BytePos;
6868

69-
fn suggestion<'tcx>(
70-
cx: &LateContext<'tcx>,
69+
fn suggestion(
70+
cx: &LateContext<'_>,
7171
diag: &mut Diagnostic,
7272
generics_span: Span,
7373
generics_suggestion_span: Span,

clippy_lints/src/index_refutable_slice.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -207,8 +207,8 @@ impl SliceLintInformation {
207207
}
208208
}
209209

210-
fn filter_lintable_slices<'a, 'tcx>(
211-
cx: &'a LateContext<'tcx>,
210+
fn filter_lintable_slices<'tcx>(
211+
cx: &LateContext<'tcx>,
212212
slice_lint_info: FxIndexMap<hir::HirId, SliceLintInformation>,
213213
max_suggested_slice: u64,
214214
scope: &'tcx hir::Expr<'tcx>,

clippy_lints/src/indexing_slicing.rs

+1-5
Original file line numberDiff line numberDiff line change
@@ -171,11 +171,7 @@ impl<'tcx> LateLintPass<'tcx> for IndexingSlicing {
171171

172172
/// Returns a tuple of options with the start and end (exclusive) values of
173173
/// the range. If the start or end is not constant, None is returned.
174-
fn to_const_range<'tcx>(
175-
cx: &LateContext<'tcx>,
176-
range: higher::Range<'_>,
177-
array_size: u128,
178-
) -> (Option<u128>, Option<u128>) {
174+
fn to_const_range(cx: &LateContext<'_>, range: higher::Range<'_>, array_size: u128) -> (Option<u128>, Option<u128>) {
179175
let s = range
180176
.start
181177
.map(|expr| constant(cx, cx.typeck_results(), expr).map(|(c, _)| c));

clippy_lints/src/invalid_upcast_comparisons.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ declare_clippy_lint! {
3838

3939
declare_lint_pass!(InvalidUpcastComparisons => [INVALID_UPCAST_COMPARISONS]);
4040

41-
fn numeric_cast_precast_bounds<'a>(cx: &LateContext<'_>, expr: &'a Expr<'_>) -> Option<(FullInt, FullInt)> {
41+
fn numeric_cast_precast_bounds(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<(FullInt, FullInt)> {
4242
if let ExprKind::Cast(cast_exp, _) = expr.kind {
4343
let pre_cast_ty = cx.typeck_results().expr_ty(cast_exp);
4444
let cast_ty = cx.typeck_results().expr_ty(expr);

clippy_lints/src/lifetimes.rs

+88-43
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use clippy_utils::diagnostics::span_lint;
1+
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
22
use clippy_utils::trait_ref_of_method;
33
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
44
use rustc_hir::intravisit::nested_filter::{self as hir_nested_filter, NestedFilter};
@@ -151,6 +151,7 @@ fn check_fn_inner<'tcx>(
151151
.params
152152
.iter()
153153
.filter(|param| matches!(param.kind, GenericParamKind::Type { .. }));
154+
154155
for typ in types {
155156
for pred in generics.bounds_for_param(cx.tcx.hir().local_def_id(typ.hir_id)) {
156157
if pred.origin == PredicateOrigin::WhereClause {
@@ -187,15 +188,30 @@ fn check_fn_inner<'tcx>(
187188
}
188189
}
189190
}
190-
if could_use_elision(cx, decl, body, trait_sig, generics.params) {
191-
span_lint(
191+
192+
if let Some(elidable_lts) = could_use_elision(cx, decl, body, trait_sig, generics.params) {
193+
let lts = elidable_lts
194+
.iter()
195+
// In principle, the result of the call to `Node::ident` could be `unwrap`ped, as `DefId` should refer to a
196+
// `Node::GenericParam`.
197+
.filter_map(|&(def_id, _)| cx.tcx.hir().get_by_def_id(def_id).ident())
198+
.map(|ident| ident.to_string())
199+
.collect::<Vec<_>>()
200+
.join(", ");
201+
202+
span_lint_and_then(
192203
cx,
193204
NEEDLESS_LIFETIMES,
194205
span.with_hi(decl.output.span().hi()),
195-
"explicit lifetimes given in parameter types where they could be elided \
196-
(or replaced with `'_` if needed by type declaration)",
206+
&format!("the following explicit lifetimes could be elided: {lts}"),
207+
|diag| {
208+
if let Some(span) = elidable_lts.iter().find_map(|&(_, span)| span) {
209+
diag.span_help(span, "replace with `'_` in generic arguments such as here");
210+
}
211+
},
197212
);
198213
}
214+
199215
if report_extra_lifetimes {
200216
self::report_extra_lifetimes(cx, decl, generics);
201217
}
@@ -226,7 +242,7 @@ fn could_use_elision<'tcx>(
226242
body: Option<BodyId>,
227243
trait_sig: Option<&[Ident]>,
228244
named_generics: &'tcx [GenericParam<'_>],
229-
) -> bool {
245+
) -> Option<Vec<(LocalDefId, Option<Span>)>> {
230246
// There are two scenarios where elision works:
231247
// * no output references, all input references have different LT
232248
// * output references, exactly one input reference with same LT
@@ -253,15 +269,15 @@ fn could_use_elision<'tcx>(
253269
}
254270

255271
if input_visitor.abort() || output_visitor.abort() {
256-
return false;
272+
return None;
257273
}
258274

259275
let input_lts = input_visitor.lts;
260276
let output_lts = output_visitor.lts;
261277

262278
if let Some(trait_sig) = trait_sig {
263279
if explicit_self_type(cx, func, trait_sig.first().copied()) {
264-
return false;
280+
return None;
265281
}
266282
}
267283

@@ -270,22 +286,22 @@ fn could_use_elision<'tcx>(
270286

271287
let first_ident = body.params.first().and_then(|param| param.pat.simple_ident());
272288
if explicit_self_type(cx, func, first_ident) {
273-
return false;
289+
return None;
274290
}
275291

276292
let mut checker = BodyLifetimeChecker {
277293
lifetimes_used_in_body: false,
278294
};
279295
checker.visit_expr(body.value);
280296
if checker.lifetimes_used_in_body {
281-
return false;
297+
return None;
282298
}
283299
}
284300

285301
// check for lifetimes from higher scopes
286302
for lt in input_lts.iter().chain(output_lts.iter()) {
287303
if !allowed_lts.contains(lt) {
288-
return false;
304+
return None;
289305
}
290306
}
291307

@@ -301,48 +317,45 @@ fn could_use_elision<'tcx>(
301317
for lt in input_visitor.nested_elision_site_lts {
302318
if let RefLt::Named(def_id) = lt {
303319
if allowed_lts.contains(&cx.tcx.item_name(def_id.to_def_id())) {
304-
return false;
320+
return None;
305321
}
306322
}
307323
}
308324
for lt in output_visitor.nested_elision_site_lts {
309325
if let RefLt::Named(def_id) = lt {
310326
if allowed_lts.contains(&cx.tcx.item_name(def_id.to_def_id())) {
311-
return false;
327+
return None;
312328
}
313329
}
314330
}
315331
}
316332

317-
// no input lifetimes? easy case!
318-
if input_lts.is_empty() {
319-
false
320-
} else if output_lts.is_empty() {
321-
// no output lifetimes, check distinctness of input lifetimes
333+
// A lifetime can be newly elided if:
334+
// - It occurs only once among the inputs.
335+
// - If there are multiple input lifetimes, then the newly elided lifetime does not occur among the
336+
// outputs (because eliding such an lifetime would create an ambiguity).
337+
let elidable_lts = named_lifetime_occurrences(&input_lts)
338+
.into_iter()
339+
.filter_map(|(def_id, occurrences)| {
340+
if occurrences == 1 && (input_lts.len() == 1 || !output_lts.contains(&RefLt::Named(def_id))) {
341+
Some((
342+
def_id,
343+
input_visitor
344+
.lifetime_generic_arg_spans
345+
.get(&def_id)
346+
.or_else(|| output_visitor.lifetime_generic_arg_spans.get(&def_id))
347+
.copied(),
348+
))
349+
} else {
350+
None
351+
}
352+
})
353+
.collect::<Vec<_>>();
322354

323-
// only unnamed and static, ok
324-
let unnamed_and_static = input_lts.iter().all(|lt| *lt == RefLt::Unnamed || *lt == RefLt::Static);
325-
if unnamed_and_static {
326-
return false;
327-
}
328-
// we have no output reference, so we only need all distinct lifetimes
329-
input_lts.len() == unique_lifetimes(&input_lts)
355+
if elidable_lts.is_empty() {
356+
None
330357
} else {
331-
// we have output references, so we need one input reference,
332-
// and all output lifetimes must be the same
333-
if unique_lifetimes(&output_lts) > 1 {
334-
return false;
335-
}
336-
if input_lts.len() == 1 {
337-
match (&input_lts[0], &output_lts[0]) {
338-
(&RefLt::Named(n1), &RefLt::Named(n2)) if n1 == n2 => true,
339-
(&RefLt::Named(_), &RefLt::Unnamed) => true,
340-
_ => false, /* already elided, different named lifetimes
341-
* or something static going on */
342-
}
343-
} else {
344-
false
345-
}
358+
Some(elidable_lts)
346359
}
347360
}
348361

@@ -358,10 +371,24 @@ fn allowed_lts_from(tcx: TyCtxt<'_>, named_generics: &[GenericParam<'_>]) -> FxH
358371
allowed_lts
359372
}
360373

361-
/// Number of unique lifetimes in the given vector.
374+
/// Number of times each named lifetime occurs in the given slice. Returns a vector to preserve
375+
/// relative order.
362376
#[must_use]
363-
fn unique_lifetimes(lts: &[RefLt]) -> usize {
364-
lts.iter().collect::<FxHashSet<_>>().len()
377+
fn named_lifetime_occurrences(lts: &[RefLt]) -> Vec<(LocalDefId, usize)> {
378+
let mut occurrences = Vec::new();
379+
for lt in lts {
380+
if let &RefLt::Named(curr_def_id) = lt {
381+
if let Some(pair) = occurrences
382+
.iter_mut()
383+
.find(|(prev_def_id, _)| *prev_def_id == curr_def_id)
384+
{
385+
pair.1 += 1;
386+
} else {
387+
occurrences.push((curr_def_id, 1));
388+
}
389+
}
390+
}
391+
occurrences
365392
}
366393

367394
const CLOSURE_TRAIT_BOUNDS: [LangItem; 3] = [LangItem::Fn, LangItem::FnMut, LangItem::FnOnce];
@@ -370,6 +397,7 @@ const CLOSURE_TRAIT_BOUNDS: [LangItem; 3] = [LangItem::Fn, LangItem::FnMut, Lang
370397
struct RefVisitor<'a, 'tcx> {
371398
cx: &'a LateContext<'tcx>,
372399
lts: Vec<RefLt>,
400+
lifetime_generic_arg_spans: FxHashMap<LocalDefId, Span>,
373401
nested_elision_site_lts: Vec<RefLt>,
374402
unelided_trait_object_lifetime: bool,
375403
}
@@ -379,6 +407,7 @@ impl<'a, 'tcx> RefVisitor<'a, 'tcx> {
379407
Self {
380408
cx,
381409
lts: Vec::new(),
410+
lifetime_generic_arg_spans: FxHashMap::default(),
382411
nested_elision_site_lts: Vec::new(),
383412
unelided_trait_object_lifetime: false,
384413
}
@@ -472,6 +501,22 @@ impl<'a, 'tcx> Visitor<'tcx> for RefVisitor<'a, 'tcx> {
472501
_ => walk_ty(self, ty),
473502
}
474503
}
504+
505+
fn visit_generic_arg(&mut self, generic_arg: &'tcx GenericArg<'tcx>) {
506+
if let GenericArg::Lifetime(l) = generic_arg
507+
&& let LifetimeName::Param(def_id, _) = l.name
508+
{
509+
self.lifetime_generic_arg_spans.entry(def_id).or_insert(l.span);
510+
}
511+
// Replace with `walk_generic_arg` if/when https://github.com/rust-lang/rust/pull/103692 lands.
512+
// walk_generic_arg(self, generic_arg);
513+
match generic_arg {
514+
GenericArg::Lifetime(lt) => self.visit_lifetime(lt),
515+
GenericArg::Type(ty) => self.visit_ty(ty),
516+
GenericArg::Const(ct) => self.visit_anon_const(&ct.value),
517+
GenericArg::Infer(inf) => self.visit_infer(inf),
518+
}
519+
}
475520
}
476521

477522
/// Are any lifetimes mentioned in the `where` clause? If so, we don't try to

clippy_lints/src/loops/mut_range_bound.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ fn check_for_mutability(cx: &LateContext<'_>, bound: &Expr<'_>) -> Option<HirId>
5252
None
5353
}
5454

55-
fn check_for_mutation<'tcx>(
56-
cx: &LateContext<'tcx>,
55+
fn check_for_mutation(
56+
cx: &LateContext<'_>,
5757
body: &Expr<'_>,
5858
bound_id_start: Option<HirId>,
5959
bound_id_end: Option<HirId>,

clippy_lints/src/map_unit_fn.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ fn is_unit_expression(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
119119
/// semicolons, which causes problems when generating a suggestion. Given an
120120
/// expression that evaluates to '()' or '!', recursively remove useless braces
121121
/// and semi-colons until is suitable for including in the suggestion template
122-
fn reduce_unit_expression<'a>(cx: &LateContext<'_>, expr: &'a hir::Expr<'_>) -> Option<Span> {
122+
fn reduce_unit_expression(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<Span> {
123123
if !is_unit_expression(cx, expr) {
124124
return None;
125125
}

clippy_lints/src/matches/manual_filter.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ fn peels_blocks_incl_unsafe<'a>(expr: &'a Expr<'a>) -> &'a Expr<'a> {
6262
// <expr>
6363
// }
6464
// Returns true if <expr> resolves to `Some(x)`, `false` otherwise
65-
fn is_some_expr<'tcx>(cx: &LateContext<'_>, target: HirId, ctxt: SyntaxContext, expr: &'tcx Expr<'_>) -> bool {
65+
fn is_some_expr(cx: &LateContext<'_>, target: HirId, ctxt: SyntaxContext, expr: &Expr<'_>) -> bool {
6666
if let Some(inner_expr) = peels_blocks_incl_unsafe_opt(expr) {
6767
// there can be not statements in the block as they would be removed when switching to `.filter`
6868
if let ExprKind::Call(callee, [arg]) = inner_expr.kind {

clippy_lints/src/matches/significant_drop_in_scrutinee.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ fn set_diagnostic<'tcx>(diag: &mut Diagnostic, cx: &LateContext<'tcx>, expr: &'t
8383

8484
/// If the expression is an `ExprKind::Match`, check if the scrutinee has a significant drop that
8585
/// may have a surprising lifetime.
86-
fn has_significant_drop_in_scrutinee<'tcx, 'a>(
87-
cx: &'a LateContext<'tcx>,
86+
fn has_significant_drop_in_scrutinee<'tcx>(
87+
cx: &LateContext<'tcx>,
8888
scrutinee: &'tcx Expr<'tcx>,
8989
source: MatchSource,
9090
) -> Option<(Vec<FoundSigDrop>, &'static str)> {
@@ -377,7 +377,7 @@ impl<'a, 'tcx> ArmSigDropHelper<'a, 'tcx> {
377377
}
378378
}
379379

380-
fn has_significant_drop_in_arms<'tcx, 'a>(cx: &'a LateContext<'tcx>, arms: &'tcx [Arm<'_>]) -> FxHashSet<Span> {
380+
fn has_significant_drop_in_arms<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) -> FxHashSet<Span> {
381381
let mut helper = ArmSigDropHelper::new(cx);
382382
for arm in arms {
383383
helper.visit_expr(arm.body);

clippy_lints/src/matches/single_match.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ fn pat_in_candidate_enum<'a>(cx: &LateContext<'a>, ty: Ty<'a>, pat: &Pat<'_>) ->
153153
}
154154

155155
/// Returns `true` if the given type is an enum we know won't be expanded in the future
156-
fn in_candidate_enum<'a>(cx: &LateContext<'a>, ty: Ty<'_>) -> bool {
156+
fn in_candidate_enum(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
157157
// list of candidate `Enum`s we know will never get any more members
158158
let candidates = [sym::Cow, sym::Option, sym::Result];
159159

0 commit comments

Comments
 (0)