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,40 @@ 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 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 ] ) ;
107141
108142impl < ' tcx > LateLintPass < ' tcx > for CopyAndPaste {
109143 fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) {
@@ -118,28 +152,112 @@ impl<'tcx> LateLintPass<'tcx> for CopyAndPaste {
118152 }
119153
120154 let ( conds, blocks) = if_sequence ( expr) ;
121- lint_same_then_else ( cx , & blocks ) ;
155+ // Conditions
122156 lint_same_cond ( cx, & conds) ;
123157 lint_same_fns_in_if_cond ( cx, & conds) ;
158+ // Block duplication
159+ lint_same_then_else ( cx, & blocks, conds. len ( ) != blocks. len ( ) ) ;
124160 }
125161 }
126162}
127163
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+ }
132174
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 (
135185 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 " ,
141191 ) ;
142192 }
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+ }
143261}
144262
145263/// Implementation of `IFS_SAME_COND`.
@@ -195,15 +313,3 @@ fn lint_same_fns_in_if_cond(cx: &LateContext<'_>, conds: &[&Expr<'_>]) {
195313 ) ;
196314 }
197315}
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