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} ;
33use rustc_hir:: { Block , Expr } ;
44use rustc_lint:: { LateContext , LateLintPass } ;
55use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
6+ use rustc_span:: source_map:: Span ;
67
78declare_clippy_lint ! {
89 /// **What it does:** Checks for consecutive `if`s with the same condition.
@@ -103,7 +104,39 @@ declare_clippy_lint! {
103104 "`if` with the same `then` and `else` blocks"
104105}
105106
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 ] ) ;
107140
108141impl < ' tcx > LateLintPass < ' tcx > for CopyAndPaste {
109142 fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) {
@@ -118,28 +151,112 @@ impl<'tcx> LateLintPass<'tcx> for CopyAndPaste {
118151 }
119152
120153 let ( conds, blocks) = if_sequence ( expr) ;
121- lint_same_then_else ( cx , & blocks ) ;
154+ // Conditions
122155 lint_same_cond ( cx, & conds) ;
123156 lint_same_fns_in_if_cond ( cx, & conds) ;
157+ // Block duplication
158+ lint_same_then_else ( cx, & blocks) ;
124159 }
125160 }
126161}
127162
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 .
129164fn 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+ }
132173
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 (
135184 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 " ,
141190 ) ;
142191 }
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+ }
143260}
144261
145262/// Implementation of `IFS_SAME_COND`.
@@ -195,15 +312,3 @@ fn lint_same_fns_in_if_cond(cx: &LateContext<'_>, conds: &[&Expr<'_>]) {
195312 ) ;
196313 }
197314}
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