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