1- use crate :: utils:: { eq_expr_value, in_macro, search_same, SpanlessEq , SpanlessHash } ;
2- use crate :: utils:: { get_parent_expr, higher, if_sequence, span_lint_and_note} ;
3- use rustc_hir:: { Block , Expr } ;
1+ use crate :: utils:: ast_utils:: IdentIter ;
2+ use crate :: utils:: { both, count_eq, eq_expr_value, in_macro, search_same, SpanlessEq , SpanlessHash } ;
3+ use crate :: utils:: {
4+ first_line_of_span, get_parent_expr, higher, if_sequence, indent_of, multispan_sugg_with_applicability,
5+ reindent_multiline, snippet, span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then,
6+ } ;
7+ use rustc_data_structures:: fx:: FxHashSet ;
8+ use rustc_errors:: Applicability ;
9+ use rustc_hir:: intravisit:: { self , NestedVisitorMap , Visitor } ;
10+ use rustc_hir:: StmtKind ;
11+ use rustc_hir:: { Block , Expr , ExprKind , HirId } ;
412use rustc_lint:: { LateContext , LateLintPass } ;
13+ use rustc_middle:: hir:: map:: Map ;
514use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
15+ use rustc_span:: source_map:: Span ;
16+ use rustc_span:: symbol:: Ident ;
17+ use std:: borrow:: Cow ;
618
719declare_clippy_lint ! {
820 /// **What it does:** Checks for consecutive `if`s with the same condition.
@@ -103,7 +115,45 @@ declare_clippy_lint! {
103115 "`if` with the same `then` and `else` blocks"
104116}
105117
106- declare_lint_pass ! ( CopyAndPaste => [ IFS_SAME_COND , SAME_FUNCTIONS_IN_IF_CONDITION , IF_SAME_THEN_ELSE ] ) ;
118+ declare_clippy_lint ! {
119+ /// **What it does:** Checks if the `if` and `else` block contain shared code that can be
120+ /// moved out of the blocks.
121+ ///
122+ /// **Why is this bad?** Duplicate code is less maintainable.
123+ ///
124+ /// **Known problems:** Hopefully none.
125+ ///
126+ /// **Example:**
127+ /// ```ignore
128+ /// let foo = if … {
129+ /// println!("Hello World");
130+ /// 13
131+ /// } else {
132+ /// println!("Hello World");
133+ /// 42
134+ /// };
135+ /// ```
136+ ///
137+ /// Could be written as:
138+ /// ```ignore
139+ /// println!("Hello World");
140+ /// let foo = if … {
141+ /// 13
142+ /// } else {
143+ /// 42
144+ /// };
145+ /// ```
146+ pub SHARED_CODE_IN_IF_BLOCKS ,
147+ pedantic,
148+ "`if` statement with shared code in all blocks"
149+ }
150+
151+ declare_lint_pass ! ( CopyAndPaste => [
152+ IFS_SAME_COND ,
153+ SAME_FUNCTIONS_IN_IF_CONDITION ,
154+ IF_SAME_THEN_ELSE ,
155+ SHARED_CODE_IN_IF_BLOCKS
156+ ] ) ;
107157
108158impl < ' tcx > LateLintPass < ' tcx > for CopyAndPaste {
109159 fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) {
@@ -118,30 +168,256 @@ impl<'tcx> LateLintPass<'tcx> for CopyAndPaste {
118168 }
119169
120170 let ( conds, blocks) = if_sequence ( expr) ;
121- lint_same_then_else ( cx , & blocks ) ;
171+ // Conditions
122172 lint_same_cond ( cx, & conds) ;
123173 lint_same_fns_in_if_cond ( cx, & conds) ;
174+ // Block duplication
175+ lint_same_then_else ( cx, & blocks, conds. len ( ) != blocks. len ( ) , expr) ;
124176 }
125177 }
126178}
127179
128- /// Implementation of `IF_SAME_THEN_ELSE`.
129- fn lint_same_then_else ( cx : & LateContext < ' _ > , blocks : & [ & Block < ' _ > ] ) {
130- let eq: & dyn Fn ( & & Block < ' _ > , & & Block < ' _ > ) -> bool =
131- & |& lhs, & rhs| -> bool { SpanlessEq :: new ( cx) . eq_block ( lhs, rhs) } ;
180+ /// Implementation of `SHARED_CODE_IN_IF_BLOCKS` and `IF_SAME_THEN_ELSE` if the blocks are equal.
181+ fn lint_same_then_else < ' tcx > (
182+ cx : & LateContext < ' tcx > ,
183+ blocks : & [ & Block < ' tcx > ] ,
184+ has_unconditional_else : bool ,
185+ expr : & ' tcx Expr < ' _ > ,
186+ ) {
187+ // We only lint ifs with multiple blocks
188+ // TODO xFrednet 2021-01-01: Check if it's an else if block
189+ if blocks. len ( ) < 2 {
190+ return ;
191+ }
132192
133- if let Some ( ( i, j) ) = search_same_sequenced ( blocks, eq) {
134- span_lint_and_note (
193+ let has_expr = blocks[ 0 ] . expr . is_some ( ) ;
194+
195+ // Check if each block has shared code
196+ let mut start_eq = usize:: MAX ;
197+ let mut end_eq = usize:: MAX ;
198+ let mut expr_eq = true ;
199+ for ( index, win) in blocks. windows ( 2 ) . enumerate ( ) {
200+ let l_stmts = win[ 0 ] . stmts ;
201+ let r_stmts = win[ 1 ] . stmts ;
202+
203+ let mut evaluator = SpanlessEq :: new ( cx) ;
204+ let current_start_eq = count_eq ( & mut l_stmts. iter ( ) , & mut r_stmts. iter ( ) , |l, r| evaluator. eq_stmt ( l, r) ) ;
205+ let current_end_eq = count_eq ( & mut l_stmts. iter ( ) . rev ( ) , & mut r_stmts. iter ( ) . rev ( ) , |l, r| {
206+ evaluator. eq_stmt ( l, r)
207+ } ) ;
208+ let block_expr_eq = both ( & win[ 0 ] . expr , & win[ 1 ] . expr , |l, r| evaluator. eq_expr ( l, r) ) ;
209+
210+ // IF_SAME_THEN_ELSE
211+ // We only lint the first two blocks (index == 0). Further blocks will be linted when that if
212+ // statement is checked
213+ if index == 0 && block_expr_eq && l_stmts. len ( ) == r_stmts. len ( ) && l_stmts. len ( ) == current_start_eq {
214+ span_lint_and_note (
215+ cx,
216+ IF_SAME_THEN_ELSE ,
217+ win[ 0 ] . span ,
218+ "this `if` has identical blocks" ,
219+ Some ( win[ 1 ] . span ) ,
220+ "same as this" ,
221+ ) ;
222+
223+ return ;
224+ }
225+
226+ start_eq = start_eq. min ( current_start_eq) ;
227+ end_eq = end_eq. min ( current_end_eq) ;
228+ expr_eq &= block_expr_eq;
229+
230+ // We can return if the eq count is 0 from both sides or if it has no unconditional else case
231+ if !has_unconditional_else || ( start_eq == 0 && end_eq == 0 && ( has_expr && !expr_eq) ) {
232+ return ;
233+ }
234+ }
235+
236+ if has_expr && !expr_eq {
237+ end_eq = 0 ;
238+ }
239+
240+ // Check if the regions are overlapping. Set `end_eq` to prevent the overlap
241+ let min_block_size = blocks. iter ( ) . map ( |x| x. stmts . len ( ) ) . min ( ) . unwrap ( ) ;
242+ if ( start_eq + end_eq) > min_block_size {
243+ end_eq = min_block_size - start_eq;
244+ }
245+
246+ // Only the start is the same
247+ if start_eq != 0 && end_eq == 0 && ( !has_expr || !expr_eq) {
248+ emit_shared_code_in_if_blocks_lint ( cx, start_eq, 0 , false , blocks, expr) ;
249+ } else if end_eq != 0 && ( !has_expr || !expr_eq) {
250+ let block = blocks[ blocks. len ( ) - 1 ] ;
251+ let stmts = block. stmts . split_at ( start_eq) . 1 ;
252+ let ( block_stmts, moved_stmts) = stmts. split_at ( stmts. len ( ) - end_eq) ;
253+
254+ // Scan block
255+ let mut walker = SymbolFinderVisitor :: new ( cx) ;
256+ for stmt in block_stmts {
257+ intravisit:: walk_stmt ( & mut walker, stmt) ;
258+ }
259+ let mut block_defs = walker. defs ;
260+
261+ // Scan moved stmts
262+ let mut moved_start: Option < usize > = None ;
263+ let mut walker = SymbolFinderVisitor :: new ( cx) ;
264+ for ( index, stmt) in moved_stmts. iter ( ) . enumerate ( ) {
265+ intravisit:: walk_stmt ( & mut walker, stmt) ;
266+
267+ for value in & walker. uses {
268+ // Well we can't move this and all prev statements. So reset
269+ if block_defs. contains ( & value) {
270+ moved_start = Some ( index + 1 ) ;
271+ walker. defs . drain ( ) . for_each ( |x| {
272+ block_defs. insert ( x) ;
273+ } ) ;
274+ }
275+ }
276+
277+ walker. uses . clear ( ) ;
278+ }
279+
280+ if let Some ( moved_start) = moved_start {
281+ end_eq -= moved_start;
282+ }
283+
284+ let mut end_linable = true ;
285+ if let Some ( expr) = block. expr {
286+ intravisit:: walk_expr ( & mut walker, expr) ;
287+ end_linable = walker. uses . iter ( ) . any ( |x| !block_defs. contains ( x) ) ;
288+ }
289+
290+ emit_shared_code_in_if_blocks_lint ( cx, start_eq, end_eq, end_linable, blocks, expr) ;
291+ }
292+ }
293+
294+ fn emit_shared_code_in_if_blocks_lint (
295+ cx : & LateContext < ' tcx > ,
296+ start_stmts : usize ,
297+ end_stmts : usize ,
298+ lint_end : bool ,
299+ blocks : & [ & Block < ' tcx > ] ,
300+ if_expr : & ' tcx Expr < ' _ > ,
301+ ) {
302+ if start_stmts == 0 && !lint_end {
303+ return ;
304+ }
305+
306+ // (help, span, suggestion)
307+ let mut suggestions: Vec < ( & str , Span , String ) > = vec ! [ ] ;
308+
309+ if start_stmts > 0 {
310+ let block = blocks[ 0 ] ;
311+ let span_start = first_line_of_span ( cx, if_expr. span ) . shrink_to_lo ( ) ;
312+ let span_end = block. stmts [ start_stmts - 1 ] . span . source_callsite ( ) ;
313+
314+ let cond_span = first_line_of_span ( cx, if_expr. span ) . until ( block. span ) ;
315+ let cond_snippet = reindent_multiline ( snippet ( cx, cond_span, "_" ) , false , None ) ;
316+ let cond_indent = indent_of ( cx, cond_span) ;
317+ let moved_span = block. stmts [ 0 ] . span . source_callsite ( ) . to ( span_end) ;
318+ let moved_snippet = reindent_multiline ( snippet ( cx, moved_span, "_" ) , true , None ) ;
319+ let suggestion = moved_snippet. to_string ( ) + "\n " + & cond_snippet + "{" ;
320+ let suggestion = reindent_multiline ( Cow :: Borrowed ( & suggestion) , true , cond_indent) ;
321+
322+ let span = span_start. to ( span_end) ;
323+ suggestions. push ( ( "START HELP" , span, suggestion. to_string ( ) ) ) ;
324+ }
325+
326+ if lint_end {
327+ let block = blocks[ blocks. len ( ) - 1 ] ;
328+ let span_end = block. span . shrink_to_hi ( ) ;
329+
330+ let moved_start = if end_stmts == 0 && block. expr . is_some ( ) {
331+ block. expr . unwrap ( ) . span
332+ } else {
333+ block. stmts [ block. stmts . len ( ) - end_stmts] . span
334+ }
335+ . source_callsite ( ) ;
336+ let moved_end = if let Some ( expr) = block. expr {
337+ expr. span
338+ } else {
339+ block. stmts [ block. stmts . len ( ) - 1 ] . span
340+ }
341+ . source_callsite ( ) ;
342+
343+ let moved_span = moved_start. to ( moved_end) ;
344+ let moved_snipped = reindent_multiline ( snippet ( cx, moved_span, "_" ) , true , None ) ;
345+ let indent = indent_of ( cx, if_expr. span . shrink_to_hi ( ) ) ;
346+ let suggestion = "}\n " . to_string ( ) + & moved_snipped;
347+ let suggestion = reindent_multiline ( Cow :: Borrowed ( & suggestion) , true , indent) ;
348+
349+ let span = moved_start. to ( span_end) ;
350+ suggestions. push ( ( "END_RANGE" , span, suggestion. to_string ( ) ) ) ;
351+ }
352+
353+ if suggestions. len ( ) == 1 {
354+ let ( _, span, sugg) = & suggestions[ 0 ] ;
355+ span_lint_and_sugg (
135356 cx,
136- IF_SAME_THEN_ELSE ,
137- j. span ,
138- "this `if` has identical blocks" ,
139- Some ( i. span ) ,
140- "same as this" ,
357+ SHARED_CODE_IN_IF_BLOCKS ,
358+ * span,
359+ "All code blocks contain the same code" ,
360+ "Consider moving the code out like this" ,
361+ sugg. clone ( ) ,
362+ Applicability :: Unspecified ,
363+ ) ;
364+ } else {
365+ span_lint_and_then (
366+ cx,
367+ SHARED_CODE_IN_IF_BLOCKS ,
368+ if_expr. span ,
369+ "All if blocks contain the same code" ,
370+ move |diag| {
371+ for ( help, span, sugg) in suggestions {
372+ diag. span_suggestion ( span, help, sugg, Applicability :: Unspecified ) ;
373+ }
374+ } ,
141375 ) ;
142376 }
143377}
144378
379+ pub struct SymbolFinderVisitor < ' a , ' tcx > {
380+ cx : & ' a LateContext < ' tcx > ,
381+ defs : FxHashSet < HirId > ,
382+ uses : FxHashSet < HirId > ,
383+ }
384+
385+ impl < ' a , ' tcx > SymbolFinderVisitor < ' a , ' tcx > {
386+ fn new ( cx : & ' a LateContext < ' tcx > ) -> Self {
387+ SymbolFinderVisitor {
388+ cx,
389+ defs : FxHashSet :: default ( ) ,
390+ uses : FxHashSet :: default ( ) ,
391+ }
392+ }
393+ }
394+
395+ impl < ' a , ' tcx > Visitor < ' tcx > for SymbolFinderVisitor < ' a , ' tcx > {
396+ type Map = Map < ' tcx > ;
397+
398+ fn nested_visit_map ( & mut self ) -> NestedVisitorMap < Self :: Map > {
399+ NestedVisitorMap :: All ( self . cx . tcx . hir ( ) )
400+ }
401+
402+ fn visit_local ( & mut self , l : & ' tcx rustc_hir:: Local < ' tcx > ) {
403+ let local_id = l. pat . hir_id ;
404+ self . defs . insert ( local_id) ;
405+ if let Some ( expr) = l. init {
406+ intravisit:: walk_expr ( self , expr) ;
407+ }
408+ }
409+
410+ fn visit_qpath ( & mut self , qpath : & ' tcx rustc_hir:: QPath < ' tcx > , id : HirId , _span : rustc_span:: Span ) {
411+ if let rustc_hir:: QPath :: Resolved ( _, ref path) = * qpath {
412+ if path. segments . len ( ) == 1 {
413+ if let rustc_hir:: def:: Res :: Local ( var) = self . cx . qpath_res ( qpath, id) {
414+ self . uses . insert ( var) ;
415+ }
416+ }
417+ }
418+ }
419+ }
420+
145421/// Implementation of `IFS_SAME_COND`.
146422fn lint_same_cond ( cx : & LateContext < ' _ > , conds : & [ & Expr < ' _ > ] ) {
147423 let hash: & dyn Fn ( & & Expr < ' _ > ) -> u64 = & |expr| -> u64 {
@@ -195,15 +471,3 @@ fn lint_same_fns_in_if_cond(cx: &LateContext<'_>, conds: &[&Expr<'_>]) {
195471 ) ;
196472 }
197473}
198-
199- fn search_same_sequenced < T , Eq > ( exprs : & [ T ] , eq : Eq ) -> Option < ( & T , & T ) >
200- where
201- Eq : Fn ( & T , & T ) -> bool ,
202- {
203- for win in exprs. windows ( 2 ) {
204- if eq ( & win[ 0 ] , & win[ 1 ] ) {
205- return Some ( ( & win[ 0 ] , & win[ 1 ] ) ) ;
206- }
207- }
208- None
209- }
0 commit comments