11use clippy_utils:: diagnostics:: span_lint_and_help;
22use clippy_utils:: source:: { indent_of, snippet, snippet_block} ;
3- use rustc_ast:: ast;
3+ use rustc_ast:: { Block , Label , ast} ;
44use rustc_lint:: { EarlyContext , EarlyLintPass , LintContext } ;
55use rustc_session:: declare_lint_pass;
66use rustc_span:: Span ;
@@ -11,6 +11,7 @@ declare_clippy_lint! {
1111 /// that contain a `continue` statement in either their main blocks or their
1212 /// `else`-blocks, when omitting the `else`-block possibly with some
1313 /// rearrangement of code can make the code easier to understand.
14+ /// The lint also checks if the last statement in the loop is a `continue`
1415 ///
1516 /// ### Why is this bad?
1617 /// Having explicit `else` blocks for `if` statements
@@ -75,6 +76,45 @@ declare_clippy_lint! {
7576 /// # break;
7677 /// }
7778 /// ```
79+ ///
80+ /// ```rust
81+ /// fn foo() -> std::io::ErrorKind { std::io::ErrorKind::NotFound }
82+ /// for _ in 0..10 {
83+ /// match foo() {
84+ /// std::io::ErrorKind::NotFound => {
85+ /// eprintln!("not found");
86+ /// continue
87+ /// }
88+ /// std::io::ErrorKind::TimedOut => {
89+ /// eprintln!("timeout");
90+ /// continue
91+ /// }
92+ /// _ => {
93+ /// eprintln!("other error");
94+ /// continue
95+ /// }
96+ /// }
97+ /// }
98+ /// ```
99+ /// Could be rewritten as
100+ ///
101+ ///
102+ /// ```rust
103+ /// fn foo() -> std::io::ErrorKind { std::io::ErrorKind::NotFound }
104+ /// for _ in 0..10 {
105+ /// match foo() {
106+ /// std::io::ErrorKind::NotFound => {
107+ /// eprintln!("not found");
108+ /// }
109+ /// std::io::ErrorKind::TimedOut => {
110+ /// eprintln!("timeout");
111+ /// }
112+ /// _ => {
113+ /// eprintln!("other error");
114+ /// }
115+ /// }
116+ /// }
117+ /// ```
78118 #[ clippy:: version = "pre 1.29.0" ]
79119 pub NEEDLESS_CONTINUE ,
80120 pedantic,
@@ -144,15 +184,15 @@ impl EarlyLintPass for NeedlessContinue {
144184///
145185/// - The expression is a `continue` node.
146186/// - The expression node is a block with the first statement being a `continue`.
147- fn needless_continue_in_else ( else_expr : & ast:: Expr , label : Option < & ast :: Label > ) -> bool {
187+ fn needless_continue_in_else ( else_expr : & ast:: Expr , label : Option < & Label > ) -> bool {
148188 match else_expr. kind {
149189 ast:: ExprKind :: Block ( ref else_block, _) => is_first_block_stmt_continue ( else_block, label) ,
150190 ast:: ExprKind :: Continue ( l) => compare_labels ( label, l. as_ref ( ) ) ,
151191 _ => false ,
152192 }
153193}
154194
155- fn is_first_block_stmt_continue ( block : & ast :: Block , label : Option < & ast :: Label > ) -> bool {
195+ fn is_first_block_stmt_continue ( block : & Block , label : Option < & Label > ) -> bool {
156196 block. stmts . first ( ) . is_some_and ( |stmt| match stmt. kind {
157197 ast:: StmtKind :: Semi ( ref e) | ast:: StmtKind :: Expr ( ref e) => {
158198 if let ast:: ExprKind :: Continue ( ref l) = e. kind {
@@ -166,7 +206,7 @@ fn is_first_block_stmt_continue(block: &ast::Block, label: Option<&ast::Label>)
166206}
167207
168208/// If the `continue` has a label, check it matches the label of the loop.
169- fn compare_labels ( loop_label : Option < & ast :: Label > , continue_label : Option < & ast :: Label > ) -> bool {
209+ fn compare_labels ( loop_label : Option < & Label > , continue_label : Option < & Label > ) -> bool {
170210 match ( loop_label, continue_label) {
171211 // `loop { continue; }` or `'a loop { continue; }`
172212 ( _, None ) => true ,
@@ -181,7 +221,7 @@ fn compare_labels(loop_label: Option<&ast::Label>, continue_label: Option<&ast::
181221/// the AST object representing the loop block of `expr`.
182222fn with_loop_block < F > ( expr : & ast:: Expr , mut func : F )
183223where
184- F : FnMut ( & ast :: Block , Option < & ast :: Label > ) ,
224+ F : FnMut ( & Block , Option < & Label > ) ,
185225{
186226 if let ast:: ExprKind :: While ( _, loop_block, label)
187227 | ast:: ExprKind :: ForLoop {
@@ -205,7 +245,7 @@ where
205245/// - The `else` expression.
206246fn with_if_expr < F > ( stmt : & ast:: Stmt , mut func : F )
207247where
208- F : FnMut ( & ast:: Expr , & ast:: Expr , & ast :: Block , & ast:: Expr ) ,
248+ F : FnMut ( & ast:: Expr , & ast:: Expr , & Block , & ast:: Expr ) ,
209249{
210250 match stmt. kind {
211251 ast:: StmtKind :: Semi ( ref e) | ast:: StmtKind :: Expr ( ref e) => {
@@ -231,14 +271,14 @@ struct LintData<'a> {
231271 /// The condition expression for the above `if`.
232272 if_cond : & ' a ast:: Expr ,
233273 /// The `then` block of the `if` statement.
234- if_block : & ' a ast :: Block ,
274+ if_block : & ' a Block ,
235275 /// The `else` block of the `if` statement.
236276 /// Note that we only work with `if` exprs that have an `else` branch.
237277 else_expr : & ' a ast:: Expr ,
238278 /// The 0-based index of the `if` statement in the containing loop block.
239279 stmt_idx : usize ,
240280 /// The statements of the loop block.
241- loop_block : & ' a ast :: Block ,
281+ loop_block : & ' a Block ,
242282}
243283
244284const MSG_REDUNDANT_CONTINUE_EXPRESSION : & str = "this `continue` expression is redundant" ;
@@ -329,23 +369,61 @@ fn suggestion_snippet_for_continue_inside_else(cx: &EarlyContext<'_>, data: &Lin
329369 )
330370}
331371
332- fn check_and_warn ( cx : & EarlyContext < ' _ > , expr : & ast:: Expr ) {
333- if let ast:: ExprKind :: Loop ( loop_block, loop_label, ..) = & expr. kind
334- && let Some ( last_stmt) = loop_block. stmts . last ( )
372+ fn check_last_stmt_in_expr < F > ( inner_expr : & ast:: Expr , func : & F )
373+ where
374+ F : Fn ( Option < & Label > , Span ) ,
375+ {
376+ match & inner_expr. kind {
377+ ast:: ExprKind :: Continue ( continue_label) => {
378+ func ( continue_label. as_ref ( ) , inner_expr. span ) ;
379+ } ,
380+ ast:: ExprKind :: If ( _, then_block, else_block) => {
381+ check_last_stmt_in_block ( then_block, func) ;
382+ if let Some ( else_block) = else_block {
383+ check_last_stmt_in_expr ( else_block, func) ;
384+ }
385+ } ,
386+ ast:: ExprKind :: Match ( _, arms, _) => {
387+ for arm in arms {
388+ if let Some ( expr) = & arm. body {
389+ check_last_stmt_in_expr ( expr, func) ;
390+ }
391+ }
392+ } ,
393+ ast:: ExprKind :: Block ( b, _) => {
394+ check_last_stmt_in_block ( b, func) ;
395+ } ,
396+ _ => { } ,
397+ }
398+ }
399+
400+ fn check_last_stmt_in_block < F > ( b : & Block , func : & F )
401+ where
402+ F : Fn ( Option < & Label > , Span ) ,
403+ {
404+ if let Some ( last_stmt) = b. stmts . last ( )
335405 && let ast:: StmtKind :: Expr ( inner_expr) | ast:: StmtKind :: Semi ( inner_expr) = & last_stmt. kind
336- && let ast:: ExprKind :: Continue ( continue_label) = inner_expr. kind
337- && compare_labels ( loop_label. as_ref ( ) , continue_label. as_ref ( ) )
338406 {
339- span_lint_and_help (
340- cx,
341- NEEDLESS_CONTINUE ,
342- last_stmt. span ,
343- MSG_REDUNDANT_CONTINUE_EXPRESSION ,
344- None ,
345- DROP_CONTINUE_EXPRESSION_MSG ,
346- ) ;
407+ check_last_stmt_in_expr ( inner_expr, func) ;
347408 }
409+ }
410+
411+ fn check_and_warn ( cx : & EarlyContext < ' _ > , expr : & ast:: Expr ) {
348412 with_loop_block ( expr, |loop_block, label| {
413+ let p = |continue_label : Option < & Label > , span : Span | {
414+ if compare_labels ( label, continue_label) {
415+ span_lint_and_help (
416+ cx,
417+ NEEDLESS_CONTINUE ,
418+ span,
419+ MSG_REDUNDANT_CONTINUE_EXPRESSION ,
420+ None ,
421+ DROP_CONTINUE_EXPRESSION_MSG ,
422+ ) ;
423+ }
424+ } ;
425+ check_last_stmt_in_block ( loop_block, & p) ;
426+
349427 for ( i, stmt) in loop_block. stmts . iter ( ) . enumerate ( ) {
350428 with_if_expr ( stmt, |if_expr, cond, then_block, else_expr| {
351429 let data = & LintData {
@@ -400,7 +478,7 @@ fn erode_from_back(s: &str) -> String {
400478 if ret. is_empty ( ) { s. to_string ( ) } else { ret }
401479}
402480
403- fn span_of_first_expr_in_block ( block : & ast :: Block ) -> Option < Span > {
481+ fn span_of_first_expr_in_block ( block : & Block ) -> Option < Span > {
404482 block. stmts . first ( ) . map ( |stmt| stmt. span )
405483}
406484
0 commit comments