1
1
use crate :: utils:: { both, count_eq, eq_expr_value, in_macro, search_same, SpanlessEq , SpanlessHash } ;
2
2
use crate :: utils:: {
3
- first_line_of_span, get_parent_expr, if_sequence, indent_of, parent_node_is_if_expr, reindent_multiline , snippet ,
4
- snippet_opt, span_lint_and_note, span_lint_and_sugg , span_lint_and_then ,
3
+ first_line_of_span, get_enclosing_block , get_parent_expr, if_sequence, indent_of, parent_node_is_if_expr,
4
+ reindent_multiline , snippet , snippet_opt, span_lint_and_note, span_lint_and_then , ContainsName ,
5
5
} ;
6
6
use rustc_data_structures:: fx:: FxHashSet ;
7
- use rustc_errors:: Applicability ;
7
+ use rustc_errors:: { Applicability , DiagnosticBuilder } ;
8
8
use rustc_hir:: intravisit:: { self , NestedVisitorMap , Visitor } ;
9
9
use rustc_hir:: { Block , Expr , ExprKind , HirId } ;
10
10
use rustc_lint:: { LateContext , LateLintPass } ;
11
11
use rustc_middle:: hir:: map:: Map ;
12
12
use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
13
- use rustc_span:: { source_map:: Span , BytePos } ;
13
+ use rustc_span:: { source_map:: Span , symbol :: Symbol , BytePos } ;
14
14
use std:: borrow:: Cow ;
15
15
16
16
declare_clippy_lint ! {
@@ -184,7 +184,6 @@ fn lint_same_then_else<'tcx>(
184
184
expr : & ' tcx Expr < ' _ > ,
185
185
) {
186
186
// We only lint ifs with multiple blocks
187
- // TODO xFrednet 2021-01-01: Check if it's an else if block
188
187
if blocks. len ( ) < 2 || parent_node_is_if_expr ( expr, cx) {
189
188
return ;
190
189
}
@@ -242,52 +241,127 @@ fn lint_same_then_else<'tcx>(
242
241
243
242
// Only the start is the same
244
243
if start_eq != 0 && end_eq == 0 && ( !has_expr || !expr_eq) {
245
- emit_shared_code_in_if_blocks_lint ( cx, start_eq, 0 , false , blocks, expr) ;
246
- } else if end_eq != 0 && ( !has_expr || !expr_eq) {
244
+ let block = blocks[ 0 ] ;
245
+ let start_stmts = block. stmts . split_at ( start_eq) . 0 ;
246
+
247
+ let mut start_walker = UsedValueFinderVisitor :: new ( cx) ;
248
+ for stmt in start_stmts {
249
+ intravisit:: walk_stmt ( & mut start_walker, stmt) ;
250
+ }
251
+
252
+ emit_shared_code_in_if_blocks_lint (
253
+ cx,
254
+ start_eq,
255
+ 0 ,
256
+ false ,
257
+ check_for_warn_of_moved_symbol ( cx, & start_walker. def_symbols , expr) ,
258
+ blocks,
259
+ expr,
260
+ ) ;
261
+ } else if end_eq != 0 || ( has_expr && expr_eq) {
247
262
let block = blocks[ blocks. len ( ) - 1 ] ;
248
- let stmts = block. stmts . split_at ( start_eq) . 1 ;
249
- let ( block_stmts, moved_stmts) = stmts. split_at ( stmts. len ( ) - end_eq) ;
263
+ let ( start_stmts, block_stmts) = block. stmts . split_at ( start_eq) ;
264
+ let ( block_stmts, end_stmts) = block_stmts. split_at ( block_stmts. len ( ) - end_eq) ;
265
+
266
+ // Scan start
267
+ let mut start_walker = UsedValueFinderVisitor :: new ( cx) ;
268
+ for stmt in start_stmts {
269
+ intravisit:: walk_stmt ( & mut start_walker, stmt) ;
270
+ }
271
+ let mut moved_syms = start_walker. def_symbols ;
250
272
251
273
// Scan block
252
- let mut walker = SymbolFinderVisitor :: new ( cx) ;
274
+ let mut block_walker = UsedValueFinderVisitor :: new ( cx) ;
253
275
for stmt in block_stmts {
254
- intravisit:: walk_stmt ( & mut walker , stmt) ;
276
+ intravisit:: walk_stmt ( & mut block_walker , stmt) ;
255
277
}
256
- let mut block_defs = walker . defs ;
278
+ let mut block_defs = block_walker . defs ;
257
279
258
280
// Scan moved stmts
259
281
let mut moved_start: Option < usize > = None ;
260
- let mut walker = SymbolFinderVisitor :: new ( cx) ;
261
- for ( index, stmt) in moved_stmts . iter ( ) . enumerate ( ) {
262
- intravisit:: walk_stmt ( & mut walker , stmt) ;
282
+ let mut end_walker = UsedValueFinderVisitor :: new ( cx) ;
283
+ for ( index, stmt) in end_stmts . iter ( ) . enumerate ( ) {
284
+ intravisit:: walk_stmt ( & mut end_walker , stmt) ;
263
285
264
- for value in & walker . uses {
286
+ for value in & end_walker . uses {
265
287
// Well we can't move this and all prev statements. So reset
266
288
if block_defs. contains ( & value) {
267
289
moved_start = Some ( index + 1 ) ;
268
- walker . defs . drain ( ) . for_each ( |x| {
290
+ end_walker . defs . drain ( ) . for_each ( |x| {
269
291
block_defs. insert ( x) ;
270
292
} ) ;
293
+
294
+ end_walker. def_symbols . clear ( ) ;
271
295
}
272
296
}
273
297
274
- walker . uses . clear ( ) ;
298
+ end_walker . uses . clear ( ) ;
275
299
}
276
300
277
301
if let Some ( moved_start) = moved_start {
278
302
end_eq -= moved_start;
279
303
}
280
304
281
305
let end_linable = if let Some ( expr) = block. expr {
282
- intravisit:: walk_expr ( & mut walker , expr) ;
283
- walker . uses . iter ( ) . any ( |x| !block_defs. contains ( x) )
306
+ intravisit:: walk_expr ( & mut end_walker , expr) ;
307
+ end_walker . uses . iter ( ) . any ( |x| !block_defs. contains ( x) )
284
308
} else if end_eq == 0 {
285
309
false
286
310
} else {
287
311
true
288
312
} ;
289
313
290
- emit_shared_code_in_if_blocks_lint ( cx, start_eq, end_eq, end_linable, blocks, expr) ;
314
+ if end_linable {
315
+ end_walker. def_symbols . drain ( ) . for_each ( |x| {
316
+ moved_syms. insert ( x) ;
317
+ } ) ;
318
+ }
319
+
320
+ emit_shared_code_in_if_blocks_lint (
321
+ cx,
322
+ start_eq,
323
+ end_eq,
324
+ end_linable,
325
+ check_for_warn_of_moved_symbol ( cx, & moved_syms, expr) ,
326
+ blocks,
327
+ expr,
328
+ ) ;
329
+ }
330
+ }
331
+
332
+ fn check_for_warn_of_moved_symbol (
333
+ cx : & LateContext < ' tcx > ,
334
+ symbols : & FxHashSet < Symbol > ,
335
+ if_expr : & ' tcx Expr < ' _ > ,
336
+ ) -> bool {
337
+ // Obs true as we include the current if block
338
+ if let Some ( block) = get_enclosing_block ( cx, if_expr. hir_id ) {
339
+ let ignore_span = block. span . shrink_to_lo ( ) . to ( if_expr. span ) ;
340
+
341
+ symbols
342
+ . iter ( )
343
+ . filter ( |sym| !sym. as_str ( ) . starts_with ( '_' ) )
344
+ . any ( move |sym| {
345
+ let mut walker = ContainsName {
346
+ name : * sym,
347
+ result : false ,
348
+ } ;
349
+
350
+ // Scan block
351
+ block
352
+ . stmts
353
+ . iter ( )
354
+ . filter ( |stmt| !ignore_span. overlaps ( stmt. span ) )
355
+ . for_each ( |stmt| intravisit:: walk_stmt ( & mut walker, stmt) ) ;
356
+
357
+ if let Some ( expr) = block. expr {
358
+ intravisit:: walk_expr ( & mut walker, expr) ;
359
+ }
360
+
361
+ walker. result
362
+ } )
363
+ } else {
364
+ false
291
365
}
292
366
}
293
367
@@ -296,6 +370,7 @@ fn emit_shared_code_in_if_blocks_lint(
296
370
start_stmts : usize ,
297
371
end_stmts : usize ,
298
372
lint_end : bool ,
373
+ warn_about_moved_symbol : bool ,
299
374
blocks : & [ & Block < ' tcx > ] ,
300
375
if_expr : & ' tcx Expr < ' _ > ,
301
376
) {
@@ -305,7 +380,9 @@ fn emit_shared_code_in_if_blocks_lint(
305
380
306
381
// (help, span, suggestion)
307
382
let mut suggestions: Vec < ( & str , Span , String ) > = vec ! [ ] ;
383
+ let mut add_expr_note = false ;
308
384
385
+ // Construct suggestions
309
386
if start_stmts > 0 {
310
387
let block = blocks[ 0 ] ;
311
388
let span_start = first_line_of_span ( cx, if_expr. span ) . shrink_to_lo ( ) ;
@@ -357,21 +434,29 @@ fn emit_shared_code_in_if_blocks_lint(
357
434
}
358
435
359
436
suggestions. push ( ( "end" , span, suggestion. to_string ( ) ) ) ;
437
+ add_expr_note = !cx. typeck_results ( ) . expr_ty ( if_expr) . is_unit ( )
360
438
}
361
439
440
+ let add_optional_msgs = |diag : & mut DiagnosticBuilder < ' _ > | {
441
+ if add_expr_note {
442
+ diag. note ( "The end suggestion probably needs some adjustments to use the expression result correctly." ) ;
443
+ }
444
+
445
+ if warn_about_moved_symbol {
446
+ diag. warn ( "Some moved values might need to be renamed to avoid wrong references." ) ;
447
+ }
448
+ } ;
449
+
450
+ // Emit lint
362
451
if suggestions. len ( ) == 1 {
363
452
let ( place_str, span, sugg) = suggestions. pop ( ) . unwrap ( ) ;
364
453
let msg = format ! ( "All if blocks contain the same code at the {}" , place_str) ;
365
454
let help = format ! ( "Consider moving the {} statements out like this" , place_str) ;
366
- span_lint_and_sugg (
367
- cx,
368
- SHARED_CODE_IN_IF_BLOCKS ,
369
- span,
370
- msg. as_str ( ) ,
371
- help. as_str ( ) ,
372
- sugg,
373
- Applicability :: Unspecified ,
374
- ) ;
455
+ span_lint_and_then ( cx, SHARED_CODE_IN_IF_BLOCKS , span, msg. as_str ( ) , |diag| {
456
+ diag. span_suggestion ( span, help. as_str ( ) , sugg, Applicability :: Unspecified ) ;
457
+
458
+ add_optional_msgs ( diag) ;
459
+ } ) ;
375
460
} else if suggestions. len ( ) == 2 {
376
461
let ( _, end_span, end_sugg) = suggestions. pop ( ) . unwrap ( ) ;
377
462
let ( _, start_span, start_sugg) = suggestions. pop ( ) . unwrap ( ) ;
@@ -396,28 +481,39 @@ fn emit_shared_code_in_if_blocks_lint(
396
481
end_sugg,
397
482
Applicability :: Unspecified ,
398
483
) ;
484
+
485
+ add_optional_msgs ( diag) ;
399
486
} ,
400
487
) ;
401
488
}
402
489
}
403
490
404
- pub struct SymbolFinderVisitor < ' a , ' tcx > {
491
+ /// This visitor collects HirIds and Symbols of defined symbols and HirIds of used values.
492
+ struct UsedValueFinderVisitor < ' a , ' tcx > {
405
493
cx : & ' a LateContext < ' tcx > ,
494
+
495
+ /// The `HirId`s of defined values in the scanned statements
406
496
defs : FxHashSet < HirId > ,
497
+
498
+ /// The Symbols of the defined symbols in the scanned statements
499
+ def_symbols : FxHashSet < Symbol > ,
500
+
501
+ /// The `HirId`s of the used values
407
502
uses : FxHashSet < HirId > ,
408
503
}
409
504
410
- impl < ' a , ' tcx > SymbolFinderVisitor < ' a , ' tcx > {
505
+ impl < ' a , ' tcx > UsedValueFinderVisitor < ' a , ' tcx > {
411
506
fn new ( cx : & ' a LateContext < ' tcx > ) -> Self {
412
- SymbolFinderVisitor {
507
+ UsedValueFinderVisitor {
413
508
cx,
414
509
defs : FxHashSet :: default ( ) ,
510
+ def_symbols : FxHashSet :: default ( ) ,
415
511
uses : FxHashSet :: default ( ) ,
416
512
}
417
513
}
418
514
}
419
515
420
- impl < ' a , ' tcx > Visitor < ' tcx > for SymbolFinderVisitor < ' a , ' tcx > {
516
+ impl < ' a , ' tcx > Visitor < ' tcx > for UsedValueFinderVisitor < ' a , ' tcx > {
421
517
type Map = Map < ' tcx > ;
422
518
423
519
fn nested_visit_map ( & mut self ) -> NestedVisitorMap < Self :: Map > {
@@ -427,6 +523,11 @@ impl<'a, 'tcx> Visitor<'tcx> for SymbolFinderVisitor<'a, 'tcx> {
427
523
fn visit_local ( & mut self , l : & ' tcx rustc_hir:: Local < ' tcx > ) {
428
524
let local_id = l. pat . hir_id ;
429
525
self . defs . insert ( local_id) ;
526
+
527
+ if let Some ( sym) = l. pat . simple_ident ( ) {
528
+ self . def_symbols . insert ( sym. name ) ;
529
+ }
530
+
430
531
if let Some ( expr) = l. init {
431
532
intravisit:: walk_expr ( self , expr) ;
432
533
}
0 commit comments