1
- use clippy_utils:: diagnostics:: span_lint;
1
+ use clippy_utils:: diagnostics:: { span_lint, span_lint_and_then } ;
2
2
use clippy_utils:: trait_ref_of_method;
3
3
use rustc_data_structures:: fx:: { FxHashMap , FxHashSet } ;
4
4
use rustc_hir:: intravisit:: nested_filter:: { self as hir_nested_filter, NestedFilter } ;
@@ -151,6 +151,7 @@ fn check_fn_inner<'tcx>(
151
151
. params
152
152
. iter ( )
153
153
. filter ( |param| matches ! ( param. kind, GenericParamKind :: Type { .. } ) ) ;
154
+
154
155
for typ in types {
155
156
for pred in generics. bounds_for_param ( cx. tcx . hir ( ) . local_def_id ( typ. hir_id ) ) {
156
157
if pred. origin == PredicateOrigin :: WhereClause {
@@ -187,15 +188,30 @@ fn check_fn_inner<'tcx>(
187
188
}
188
189
}
189
190
}
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 (
192
203
cx,
193
204
NEEDLESS_LIFETIMES ,
194
205
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
+ } ,
197
212
) ;
198
213
}
214
+
199
215
if report_extra_lifetimes {
200
216
self :: report_extra_lifetimes ( cx, decl, generics) ;
201
217
}
@@ -226,7 +242,7 @@ fn could_use_elision<'tcx>(
226
242
body : Option < BodyId > ,
227
243
trait_sig : Option < & [ Ident ] > ,
228
244
named_generics : & ' tcx [ GenericParam < ' _ > ] ,
229
- ) -> bool {
245
+ ) -> Option < Vec < ( LocalDefId , Option < Span > ) > > {
230
246
// There are two scenarios where elision works:
231
247
// * no output references, all input references have different LT
232
248
// * output references, exactly one input reference with same LT
@@ -253,15 +269,15 @@ fn could_use_elision<'tcx>(
253
269
}
254
270
255
271
if input_visitor. abort ( ) || output_visitor. abort ( ) {
256
- return false ;
272
+ return None ;
257
273
}
258
274
259
275
let input_lts = input_visitor. lts ;
260
276
let output_lts = output_visitor. lts ;
261
277
262
278
if let Some ( trait_sig) = trait_sig {
263
279
if explicit_self_type ( cx, func, trait_sig. first ( ) . copied ( ) ) {
264
- return false ;
280
+ return None ;
265
281
}
266
282
}
267
283
@@ -270,22 +286,22 @@ fn could_use_elision<'tcx>(
270
286
271
287
let first_ident = body. params . first ( ) . and_then ( |param| param. pat . simple_ident ( ) ) ;
272
288
if explicit_self_type ( cx, func, first_ident) {
273
- return false ;
289
+ return None ;
274
290
}
275
291
276
292
let mut checker = BodyLifetimeChecker {
277
293
lifetimes_used_in_body : false ,
278
294
} ;
279
295
checker. visit_expr ( body. value ) ;
280
296
if checker. lifetimes_used_in_body {
281
- return false ;
297
+ return None ;
282
298
}
283
299
}
284
300
285
301
// check for lifetimes from higher scopes
286
302
for lt in input_lts. iter ( ) . chain ( output_lts. iter ( ) ) {
287
303
if !allowed_lts. contains ( lt) {
288
- return false ;
304
+ return None ;
289
305
}
290
306
}
291
307
@@ -301,48 +317,45 @@ fn could_use_elision<'tcx>(
301
317
for lt in input_visitor. nested_elision_site_lts {
302
318
if let RefLt :: Named ( def_id) = lt {
303
319
if allowed_lts. contains ( & cx. tcx . item_name ( def_id. to_def_id ( ) ) ) {
304
- return false ;
320
+ return None ;
305
321
}
306
322
}
307
323
}
308
324
for lt in output_visitor. nested_elision_site_lts {
309
325
if let RefLt :: Named ( def_id) = lt {
310
326
if allowed_lts. contains ( & cx. tcx . item_name ( def_id. to_def_id ( ) ) ) {
311
- return false ;
327
+ return None ;
312
328
}
313
329
}
314
330
}
315
331
}
316
332
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 < _ > > ( ) ;
322
354
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
330
357
} 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)
346
359
}
347
360
}
348
361
@@ -358,10 +371,24 @@ fn allowed_lts_from(tcx: TyCtxt<'_>, named_generics: &[GenericParam<'_>]) -> FxH
358
371
allowed_lts
359
372
}
360
373
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.
362
376
#[ 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
365
392
}
366
393
367
394
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
370
397
struct RefVisitor < ' a , ' tcx > {
371
398
cx : & ' a LateContext < ' tcx > ,
372
399
lts : Vec < RefLt > ,
400
+ lifetime_generic_arg_spans : FxHashMap < LocalDefId , Span > ,
373
401
nested_elision_site_lts : Vec < RefLt > ,
374
402
unelided_trait_object_lifetime : bool ,
375
403
}
@@ -379,6 +407,7 @@ impl<'a, 'tcx> RefVisitor<'a, 'tcx> {
379
407
Self {
380
408
cx,
381
409
lts : Vec :: new ( ) ,
410
+ lifetime_generic_arg_spans : FxHashMap :: default ( ) ,
382
411
nested_elision_site_lts : Vec :: new ( ) ,
383
412
unelided_trait_object_lifetime : false ,
384
413
}
@@ -472,6 +501,22 @@ impl<'a, 'tcx> Visitor<'tcx> for RefVisitor<'a, 'tcx> {
472
501
_ => walk_ty ( self , ty) ,
473
502
}
474
503
}
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
+ }
475
520
}
476
521
477
522
/// Are any lifetimes mentioned in the `where` clause? If so, we don't try to
0 commit comments