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