@@ -199,79 +199,72 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClone {
199
199
( local, deref_clone_ret)
200
200
} ;
201
201
202
- let is_temp = mir. local_kind ( ret_local) == mir:: LocalKind :: Temp ;
203
-
204
- // 1. `local` can be moved out if it is not used later.
205
- // 2. If `ret_local` is a temporary and is neither consumed nor mutated, we can remove this `clone`
206
- // call anyway.
207
- let ( used, consumed_or_mutated) = traversal:: ReversePostorder :: new ( & mir, bb) . skip ( 1 ) . fold (
208
- ( false , !is_temp) ,
209
- |( used, consumed) , ( tbb, tdata) | {
210
- // Short-circuit
211
- if ( used && consumed) ||
212
- // Give up on loops
213
- tdata. terminator ( ) . successors ( ) . any ( |s| * s == bb)
214
- {
215
- return ( true , true ) ;
202
+ let clone_usage = if local == ret_local {
203
+ CloneUsage {
204
+ cloned_used : false ,
205
+ cloned_consume_or_mutate_loc : None ,
206
+ clone_consumed_or_mutated : true ,
207
+ }
208
+ } else {
209
+ let clone_usage = visit_clone_usage ( local, ret_local, & mir, bb) ;
210
+ if clone_usage. cloned_used && clone_usage. clone_consumed_or_mutated {
211
+ // cloned value is used, and the clone is modified or moved
212
+ continue ;
213
+ } else if let Some ( loc) = clone_usage. cloned_consume_or_mutate_loc {
214
+ // cloned value is mutated, and the clone is alive.
215
+ if possible_borrower. is_alive_at ( ret_local, loc) {
216
+ continue ;
216
217
}
218
+ }
219
+ clone_usage
220
+ } ;
217
221
218
- let mut vis = LocalUseVisitor {
219
- used : ( local, false ) ,
220
- consumed_or_mutated : ( ret_local, false ) ,
221
- } ;
222
- vis. visit_basic_block_data ( tbb, tdata) ;
223
- ( used || vis. used . 1 , consumed || vis. consumed_or_mutated . 1 )
224
- } ,
225
- ) ;
226
-
227
- if !used || !consumed_or_mutated {
228
- let span = terminator. source_info . span ;
229
- let scope = terminator. source_info . scope ;
230
- let node = mir. source_scopes [ scope]
231
- . local_data
232
- . as_ref ( )
233
- . assert_crate_local ( )
234
- . lint_root ;
235
-
236
- if_chain ! {
237
- if let Some ( snip) = snippet_opt( cx, span) ;
238
- if let Some ( dot) = snip. rfind( '.' ) ;
239
- then {
240
- let sugg_span = span. with_lo(
241
- span. lo( ) + BytePos ( u32 :: try_from( dot) . unwrap( ) )
242
- ) ;
243
- let mut app = Applicability :: MaybeIncorrect ;
244
-
245
- let call_snip = & snip[ dot + 1 ..] ;
246
- // Machine applicable when `call_snip` looks like `foobar()`
247
- if let Some ( call_snip) = call_snip. strip_suffix( "()" ) . map( str :: trim) {
248
- if call_snip. as_bytes( ) . iter( ) . all( |b| b. is_ascii_alphabetic( ) || * b == b'_' ) {
249
- app = Applicability :: MachineApplicable ;
250
- }
222
+ let span = terminator. source_info . span ;
223
+ let scope = terminator. source_info . scope ;
224
+ let node = mir. source_scopes [ scope]
225
+ . local_data
226
+ . as_ref ( )
227
+ . assert_crate_local ( )
228
+ . lint_root ;
229
+
230
+ if_chain ! {
231
+ if let Some ( snip) = snippet_opt( cx, span) ;
232
+ if let Some ( dot) = snip. rfind( '.' ) ;
233
+ then {
234
+ let sugg_span = span. with_lo(
235
+ span. lo( ) + BytePos ( u32 :: try_from( dot) . unwrap( ) )
236
+ ) ;
237
+ let mut app = Applicability :: MaybeIncorrect ;
238
+
239
+ let call_snip = & snip[ dot + 1 ..] ;
240
+ // Machine applicable when `call_snip` looks like `foobar()`
241
+ if let Some ( call_snip) = call_snip. strip_suffix( "()" ) . map( str :: trim) {
242
+ if call_snip. as_bytes( ) . iter( ) . all( |b| b. is_ascii_alphabetic( ) || * b == b'_' ) {
243
+ app = Applicability :: MachineApplicable ;
251
244
}
245
+ }
252
246
253
- span_lint_hir_and_then( cx, REDUNDANT_CLONE , node, sugg_span, "redundant clone" , |diag| {
254
- diag. span_suggestion(
255
- sugg_span,
256
- "remove this" ,
257
- String :: new( ) ,
258
- app,
247
+ span_lint_hir_and_then( cx, REDUNDANT_CLONE , node, sugg_span, "redundant clone" , |diag| {
248
+ diag. span_suggestion(
249
+ sugg_span,
250
+ "remove this" ,
251
+ String :: new( ) ,
252
+ app,
253
+ ) ;
254
+ if clone_usage. cloned_used {
255
+ diag. span_note(
256
+ span,
257
+ "cloned value is neither consumed nor mutated" ,
259
258
) ;
260
- if used {
261
- diag. span_note(
262
- span,
263
- "cloned value is neither consumed nor mutated" ,
264
- ) ;
265
- } else {
266
- diag. span_note(
267
- span. with_hi( span. lo( ) + BytePos ( u32 :: try_from( dot) . unwrap( ) ) ) ,
268
- "this value is dropped without further use" ,
269
- ) ;
270
- }
271
- } ) ;
272
- } else {
273
- span_lint_hir( cx, REDUNDANT_CLONE , node, span, "redundant clone" ) ;
274
- }
259
+ } else {
260
+ diag. span_note(
261
+ span. with_hi( span. lo( ) + BytePos ( u32 :: try_from( dot) . unwrap( ) ) ) ,
262
+ "this value is dropped without further use" ,
263
+ ) ;
264
+ }
265
+ } ) ;
266
+ } else {
267
+ span_lint_hir( cx, REDUNDANT_CLONE , node, span, "redundant clone" ) ;
275
268
}
276
269
}
277
270
}
@@ -365,49 +358,97 @@ fn base_local_and_movability<'tcx>(
365
358
( local, deref || field || slice)
366
359
}
367
360
368
- struct LocalUseVisitor {
369
- used : ( mir:: Local , bool ) ,
370
- consumed_or_mutated : ( mir:: Local , bool ) ,
361
+ #[ derive( Default ) ]
362
+ struct CloneUsage {
363
+ /// Whether the cloned value is used after the clone.
364
+ cloned_used : bool ,
365
+ /// The first location where the cloned value is consumed or mutated, if any.
366
+ cloned_consume_or_mutate_loc : Option < mir:: Location > ,
367
+ /// Whether the clone value is mutated.
368
+ clone_consumed_or_mutated : bool ,
371
369
}
372
-
373
- impl < ' tcx > mir:: visit:: Visitor < ' tcx > for LocalUseVisitor {
374
- fn visit_basic_block_data ( & mut self , block : mir:: BasicBlock , data : & mir:: BasicBlockData < ' tcx > ) {
375
- let statements = & data. statements ;
376
- for ( statement_index, statement) in statements. iter ( ) . enumerate ( ) {
377
- self . visit_statement ( statement, mir:: Location { block, statement_index } ) ;
378
- }
379
-
380
- self . visit_terminator (
381
- data. terminator ( ) ,
382
- mir:: Location {
383
- block,
384
- statement_index : statements. len ( ) ,
385
- } ,
386
- ) ;
370
+ fn visit_clone_usage ( cloned : mir:: Local , clone : mir:: Local , mir : & mir:: Body < ' _ > , bb : mir:: BasicBlock ) -> CloneUsage {
371
+ struct V {
372
+ cloned : mir:: Local ,
373
+ clone : mir:: Local ,
374
+ result : CloneUsage ,
387
375
}
376
+ impl < ' tcx > mir:: visit:: Visitor < ' tcx > for V {
377
+ fn visit_basic_block_data ( & mut self , block : mir:: BasicBlock , data : & mir:: BasicBlockData < ' tcx > ) {
378
+ let statements = & data. statements ;
379
+ for ( statement_index, statement) in statements. iter ( ) . enumerate ( ) {
380
+ self . visit_statement ( statement, mir:: Location { block, statement_index } ) ;
381
+ }
388
382
389
- fn visit_place ( & mut self , place : & mir:: Place < ' tcx > , ctx : PlaceContext , _: mir:: Location ) {
390
- let local = place. local ;
391
-
392
- if local == self . used . 0
393
- && !matches ! (
394
- ctx,
395
- PlaceContext :: MutatingUse ( MutatingUseContext :: Drop ) | PlaceContext :: NonUse ( _)
396
- )
397
- {
398
- self . used . 1 = true ;
383
+ self . visit_terminator (
384
+ data. terminator ( ) ,
385
+ mir:: Location {
386
+ block,
387
+ statement_index : statements. len ( ) ,
388
+ } ,
389
+ ) ;
399
390
}
400
391
401
- if local == self . consumed_or_mutated . 0 {
402
- match ctx {
403
- PlaceContext :: NonMutatingUse ( NonMutatingUseContext :: Move )
404
- | PlaceContext :: MutatingUse ( MutatingUseContext :: Borrow ) => {
405
- self . consumed_or_mutated . 1 = true ;
406
- } ,
407
- _ => { } ,
392
+ fn visit_place ( & mut self , place : & mir:: Place < ' tcx > , ctx : PlaceContext , loc : mir:: Location ) {
393
+ let local = place. local ;
394
+
395
+ if local == self . cloned
396
+ && !matches ! (
397
+ ctx,
398
+ PlaceContext :: MutatingUse ( MutatingUseContext :: Drop ) | PlaceContext :: NonUse ( _)
399
+ )
400
+ {
401
+ self . result . cloned_used = true ;
402
+ self . result . cloned_consume_or_mutate_loc = self . result . cloned_consume_or_mutate_loc . or_else ( || {
403
+ matches ! (
404
+ ctx,
405
+ PlaceContext :: NonMutatingUse ( NonMutatingUseContext :: Move )
406
+ | PlaceContext :: MutatingUse ( MutatingUseContext :: Borrow )
407
+ )
408
+ . then ( || loc)
409
+ } ) ;
410
+ } else if local == self . clone {
411
+ match ctx {
412
+ PlaceContext :: NonMutatingUse ( NonMutatingUseContext :: Move )
413
+ | PlaceContext :: MutatingUse ( MutatingUseContext :: Borrow ) => {
414
+ self . result . clone_consumed_or_mutated = true ;
415
+ } ,
416
+ _ => { } ,
417
+ }
408
418
}
409
419
}
410
420
}
421
+
422
+ let init = CloneUsage {
423
+ cloned_used : false ,
424
+ cloned_consume_or_mutate_loc : None ,
425
+ // Consider non-temporary clones consumed.
426
+ // TODO: Actually check for mutation of non-temporaries.
427
+ clone_consumed_or_mutated : mir. local_kind ( clone) != mir:: LocalKind :: Temp ,
428
+ } ;
429
+ traversal:: ReversePostorder :: new ( & mir, bb)
430
+ . skip ( 1 )
431
+ . fold ( init, |usage, ( tbb, tdata) | {
432
+ // Short-circuit
433
+ if ( usage. cloned_used && usage. clone_consumed_or_mutated ) ||
434
+ // Give up on loops
435
+ tdata. terminator ( ) . successors ( ) . any ( |s| * s == bb)
436
+ {
437
+ return CloneUsage {
438
+ cloned_used : true ,
439
+ clone_consumed_or_mutated : true ,
440
+ ..usage
441
+ } ;
442
+ }
443
+
444
+ let mut v = V {
445
+ cloned,
446
+ clone,
447
+ result : usage,
448
+ } ;
449
+ v. visit_basic_block_data ( tbb, tdata) ;
450
+ v. result
451
+ } )
411
452
}
412
453
413
454
/// Determines liveness of each local purely based on `StorageLive`/`Dead`.
@@ -623,4 +664,9 @@ impl PossibleBorrowerMap<'_, '_> {
623
664
624
665
self . bitset . 0 == self . bitset . 1
625
666
}
667
+
668
+ fn is_alive_at ( & mut self , local : mir:: Local , at : mir:: Location ) -> bool {
669
+ self . maybe_live . seek_after_primary_effect ( at) ;
670
+ self . maybe_live . contains ( local)
671
+ }
626
672
}
0 commit comments