11use crate :: utils:: { both, count_eq, eq_expr_value, in_macro, search_same, SpanlessEq , SpanlessHash } ;
22use 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 ,
55} ;
66use rustc_data_structures:: fx:: FxHashSet ;
7- use rustc_errors:: Applicability ;
7+ use rustc_errors:: { Applicability , DiagnosticBuilder } ;
88use rustc_hir:: intravisit:: { self , NestedVisitorMap , Visitor } ;
99use rustc_hir:: { Block , Expr , ExprKind , HirId } ;
1010use rustc_lint:: { LateContext , LateLintPass } ;
1111use rustc_middle:: hir:: map:: Map ;
1212use 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 } ;
1414use std:: borrow:: Cow ;
1515
1616declare_clippy_lint ! {
@@ -184,7 +184,6 @@ fn lint_same_then_else<'tcx>(
184184 expr : & ' tcx Expr < ' _ > ,
185185) {
186186 // We only lint ifs with multiple blocks
187- // TODO xFrednet 2021-01-01: Check if it's an else if block
188187 if blocks. len ( ) < 2 || parent_node_is_if_expr ( expr, cx) {
189188 return ;
190189 }
@@ -242,52 +241,127 @@ fn lint_same_then_else<'tcx>(
242241
243242 // Only the start is the same
244243 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) {
247262 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 ;
250272
251273 // Scan block
252- let mut walker = SymbolFinderVisitor :: new ( cx) ;
274+ let mut block_walker = UsedValueFinderVisitor :: new ( cx) ;
253275 for stmt in block_stmts {
254- intravisit:: walk_stmt ( & mut walker , stmt) ;
276+ intravisit:: walk_stmt ( & mut block_walker , stmt) ;
255277 }
256- let mut block_defs = walker . defs ;
278+ let mut block_defs = block_walker . defs ;
257279
258280 // Scan moved stmts
259281 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) ;
263285
264- for value in & walker . uses {
286+ for value in & end_walker . uses {
265287 // Well we can't move this and all prev statements. So reset
266288 if block_defs. contains ( & value) {
267289 moved_start = Some ( index + 1 ) ;
268- walker . defs . drain ( ) . for_each ( |x| {
290+ end_walker . defs . drain ( ) . for_each ( |x| {
269291 block_defs. insert ( x) ;
270292 } ) ;
293+
294+ end_walker. def_symbols . clear ( ) ;
271295 }
272296 }
273297
274- walker . uses . clear ( ) ;
298+ end_walker . uses . clear ( ) ;
275299 }
276300
277301 if let Some ( moved_start) = moved_start {
278302 end_eq -= moved_start;
279303 }
280304
281305 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) )
284308 } else if end_eq == 0 {
285309 false
286310 } else {
287311 true
288312 } ;
289313
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
291365 }
292366}
293367
@@ -296,6 +370,7 @@ fn emit_shared_code_in_if_blocks_lint(
296370 start_stmts : usize ,
297371 end_stmts : usize ,
298372 lint_end : bool ,
373+ warn_about_moved_symbol : bool ,
299374 blocks : & [ & Block < ' tcx > ] ,
300375 if_expr : & ' tcx Expr < ' _ > ,
301376) {
@@ -305,7 +380,9 @@ fn emit_shared_code_in_if_blocks_lint(
305380
306381 // (help, span, suggestion)
307382 let mut suggestions: Vec < ( & str , Span , String ) > = vec ! [ ] ;
383+ let mut add_expr_note = false ;
308384
385+ // Construct suggestions
309386 if start_stmts > 0 {
310387 let block = blocks[ 0 ] ;
311388 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(
357434 }
358435
359436 suggestions. push ( ( "end" , span, suggestion. to_string ( ) ) ) ;
437+ add_expr_note = !cx. typeck_results ( ) . expr_ty ( if_expr) . is_unit ( )
360438 }
361439
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
362451 if suggestions. len ( ) == 1 {
363452 let ( place_str, span, sugg) = suggestions. pop ( ) . unwrap ( ) ;
364453 let msg = format ! ( "All if blocks contain the same code at the {}" , place_str) ;
365454 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+ } ) ;
375460 } else if suggestions. len ( ) == 2 {
376461 let ( _, end_span, end_sugg) = suggestions. pop ( ) . unwrap ( ) ;
377462 let ( _, start_span, start_sugg) = suggestions. pop ( ) . unwrap ( ) ;
@@ -396,28 +481,39 @@ fn emit_shared_code_in_if_blocks_lint(
396481 end_sugg,
397482 Applicability :: Unspecified ,
398483 ) ;
484+
485+ add_optional_msgs ( diag) ;
399486 } ,
400487 ) ;
401488 }
402489}
403490
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 > {
405493 cx : & ' a LateContext < ' tcx > ,
494+
495+ /// The `HirId`s of defined values in the scanned statements
406496 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
407502 uses : FxHashSet < HirId > ,
408503}
409504
410- impl < ' a , ' tcx > SymbolFinderVisitor < ' a , ' tcx > {
505+ impl < ' a , ' tcx > UsedValueFinderVisitor < ' a , ' tcx > {
411506 fn new ( cx : & ' a LateContext < ' tcx > ) -> Self {
412- SymbolFinderVisitor {
507+ UsedValueFinderVisitor {
413508 cx,
414509 defs : FxHashSet :: default ( ) ,
510+ def_symbols : FxHashSet :: default ( ) ,
415511 uses : FxHashSet :: default ( ) ,
416512 }
417513 }
418514}
419515
420- impl < ' a , ' tcx > Visitor < ' tcx > for SymbolFinderVisitor < ' a , ' tcx > {
516+ impl < ' a , ' tcx > Visitor < ' tcx > for UsedValueFinderVisitor < ' a , ' tcx > {
421517 type Map = Map < ' tcx > ;
422518
423519 fn nested_visit_map ( & mut self ) -> NestedVisitorMap < Self :: Map > {
@@ -427,6 +523,11 @@ impl<'a, 'tcx> Visitor<'tcx> for SymbolFinderVisitor<'a, 'tcx> {
427523 fn visit_local ( & mut self , l : & ' tcx rustc_hir:: Local < ' tcx > ) {
428524 let local_id = l. pat . hir_id ;
429525 self . defs . insert ( local_id) ;
526+
527+ if let Some ( sym) = l. pat . simple_ident ( ) {
528+ self . def_symbols . insert ( sym. name ) ;
529+ }
530+
430531 if let Some ( expr) = l. init {
431532 intravisit:: walk_expr ( self , expr) ;
432533 }
0 commit comments