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} ;
1
+ use crate :: utils:: { both , count_eq , eq_expr_value, in_macro, search_same, SpanlessEq , SpanlessHash } ;
2
+ use crate :: utils:: { get_parent_expr, higher, if_sequence, span_lint_and_help , span_lint_and_note} ;
3
3
use rustc_hir:: { Block , Expr } ;
4
4
use rustc_lint:: { LateContext , LateLintPass } ;
5
5
use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
6
+ use rustc_span:: source_map:: Span ;
6
7
7
8
declare_clippy_lint ! {
8
9
/// **What it does:** Checks for consecutive `if`s with the same condition.
@@ -103,7 +104,40 @@ declare_clippy_lint! {
103
104
"`if` with the same `then` and `else` blocks"
104
105
}
105
106
106
- declare_lint_pass ! ( CopyAndPaste => [ IFS_SAME_COND , SAME_FUNCTIONS_IN_IF_CONDITION , IF_SAME_THEN_ELSE ] ) ;
107
+ declare_clippy_lint ! {
108
+ /// **What it does:** Checks if the `if` and `else` block contain shared that can be
109
+ /// moved out of the blocks.
110
+ ///
111
+ /// **Why is this bad?** Duplicate code is less maintainable.
112
+ ///
113
+ /// **Known problems:** Hopefully none.
114
+ ///
115
+ /// **Example:**
116
+ /// ```ignore
117
+ /// let foo = if … {
118
+ /// println!("Hello World");
119
+ /// 13
120
+ /// } else {
121
+ /// println!("Hello World");
122
+ /// 42
123
+ /// };
124
+ /// ```
125
+ ///
126
+ /// Could be written as:
127
+ /// ```ignore
128
+ /// println!("Hello World");
129
+ /// let foo = if … {
130
+ /// 13
131
+ /// } else {
132
+ /// 42
133
+ /// };
134
+ /// ```
135
+ pub SHARED_CODE_IN_IF_BLOCKS ,
136
+ complexity,
137
+ "`if` statement with shared code in all blocks"
138
+ }
139
+
140
+ declare_lint_pass ! ( CopyAndPaste => [ IFS_SAME_COND , SAME_FUNCTIONS_IN_IF_CONDITION , IF_SAME_THEN_ELSE , SHARED_CODE_IN_IF_BLOCKS ] ) ;
107
141
108
142
impl < ' tcx > LateLintPass < ' tcx > for CopyAndPaste {
109
143
fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) {
@@ -118,28 +152,112 @@ impl<'tcx> LateLintPass<'tcx> for CopyAndPaste {
118
152
}
119
153
120
154
let ( conds, blocks) = if_sequence ( expr) ;
121
- lint_same_then_else ( cx , & blocks ) ;
155
+ // Conditions
122
156
lint_same_cond ( cx, & conds) ;
123
157
lint_same_fns_in_if_cond ( cx, & conds) ;
158
+ // Block duplication
159
+ lint_same_then_else ( cx, & blocks, conds. len ( ) != blocks. len ( ) ) ;
124
160
}
125
161
}
126
162
}
127
163
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) } ;
164
+ /// Implementation of `SHARED_CODE_IN_IF_BLOCKS` and `IF_SAME_THEN_ELSE` if the blocks are equal.
165
+ fn lint_same_then_else ( cx : & LateContext < ' _ > , blocks : & [ & Block < ' _ > ] , has_unconditional_else : bool ) {
166
+ /// This retrieves the span of the actual call site.
167
+ fn get_source_span ( span : Span ) -> Span {
168
+ if span. from_expansion ( ) {
169
+ span. source_callee ( ) . unwrap ( ) . call_site
170
+ } else {
171
+ span
172
+ }
173
+ }
132
174
133
- if let Some ( ( i, j) ) = search_same_sequenced ( blocks, eq) {
134
- span_lint_and_note (
175
+ fn min ( a : usize , b : usize ) -> usize {
176
+ if a < b {
177
+ a
178
+ } else {
179
+ b
180
+ }
181
+ }
182
+
183
+ fn lint_duplicate_code ( cx : & LateContext < ' _ > , position : & str , lint_span : Span ) {
184
+ span_lint_and_help (
135
185
cx,
136
- IF_SAME_THEN_ELSE ,
137
- j . span ,
138
- "this `if` has identical blocks" ,
139
- Some ( i . span ) ,
140
- "same as this " ,
186
+ SHARED_CODE_IN_IF_BLOCKS ,
187
+ lint_span ,
188
+ format ! ( "All if blocks contain the same code at the {}" , position ) . as_str ( ) ,
189
+ None ,
190
+ "Consider moving the code out of the if statement to prevent code duplication " ,
141
191
) ;
142
192
}
193
+
194
+ // We only lint ifs with multiple blocks
195
+ if blocks. len ( ) < 2 {
196
+ return ;
197
+ }
198
+
199
+ // Check if each block has shared code
200
+ let mut start_eq = usize:: MAX ;
201
+ let mut end_eq = usize:: MAX ;
202
+ for ( index, win) in blocks. windows ( 2 ) . enumerate ( ) {
203
+ let l_stmts = win[ 0 ] . stmts ;
204
+ let r_stmts = win[ 1 ] . stmts ;
205
+
206
+ let mut evaluator = SpanlessEq :: new ( cx) ;
207
+ let current_start_eq = count_eq ( & mut l_stmts. iter ( ) , & mut r_stmts. iter ( ) , |l, r| evaluator. eq_stmt ( l, r) ) ;
208
+ let current_end_eq = count_eq ( & mut l_stmts. iter ( ) . rev ( ) , & mut r_stmts. iter ( ) . rev ( ) , |l, r| {
209
+ evaluator. eq_stmt ( l, r)
210
+ } ) ;
211
+ let current_block_expr_eq = both ( & win[ 0 ] . expr , & win[ 1 ] . expr , |l, r| evaluator. eq_expr ( l, r) ) ;
212
+
213
+ // IF_SAME_THEN_ELSE
214
+ // We only lint the first two blocks (index == 0). Further blocks will be linted when that if
215
+ // statement is checked
216
+ if index == 0 && current_block_expr_eq && l_stmts. len ( ) == r_stmts. len ( ) && l_stmts. len ( ) == current_start_eq {
217
+ span_lint_and_note (
218
+ cx,
219
+ IF_SAME_THEN_ELSE ,
220
+ win[ 0 ] . span ,
221
+ "this `if` has identical blocks" ,
222
+ Some ( win[ 1 ] . span ) ,
223
+ "same as this" ,
224
+ ) ;
225
+
226
+ return ;
227
+ }
228
+
229
+ start_eq = min ( start_eq, current_start_eq) ;
230
+ end_eq = min ( end_eq, current_end_eq) ;
231
+
232
+ // We can return if the eq count is 0 from both sides or if it has no unconditional else case
233
+ if !has_unconditional_else || start_eq == 0 && end_eq == 0 {
234
+ return ;
235
+ } ;
236
+ }
237
+
238
+ let first_block = blocks[ 0 ] ;
239
+
240
+ // prevent double lint if the `start_eq` and `end_eq` cover the entire block
241
+ if start_eq == first_block. stmts . len ( ) {
242
+ end_eq = 0 ;
243
+ }
244
+
245
+ if start_eq != 0 {
246
+ let start = first_block. span . shrink_to_lo ( ) ;
247
+ let end = get_source_span ( first_block. stmts [ start_eq - 1 ] . span ) ;
248
+ let lint_span = start. to ( end) ;
249
+
250
+ lint_duplicate_code ( cx, "start" , lint_span) ;
251
+ }
252
+
253
+ if end_eq != 0 {
254
+ let index = first_block. stmts . len ( ) - end_eq;
255
+ let start = get_source_span ( first_block. stmts [ index] . span ) ;
256
+ let end = first_block. span . shrink_to_hi ( ) ;
257
+ let lint_span = start. to ( end) ;
258
+
259
+ lint_duplicate_code ( cx, "end" , lint_span) ;
260
+ }
143
261
}
144
262
145
263
/// Implementation of `IFS_SAME_COND`.
@@ -195,15 +313,3 @@ fn lint_same_fns_in_if_cond(cx: &LateContext<'_>, conds: &[&Expr<'_>]) {
195
313
) ;
196
314
}
197
315
}
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