1
1
use clippy_utils:: diagnostics:: span_lint_and_help;
2
2
use clippy_utils:: source:: { indent_of, snippet, snippet_block} ;
3
- use rustc_ast:: ast;
3
+ use rustc_ast:: { Block , Label , ast} ;
4
4
use rustc_lint:: { EarlyContext , EarlyLintPass , LintContext } ;
5
5
use rustc_session:: declare_lint_pass;
6
6
use rustc_span:: Span ;
@@ -11,6 +11,7 @@ declare_clippy_lint! {
11
11
/// that contain a `continue` statement in either their main blocks or their
12
12
/// `else`-blocks, when omitting the `else`-block possibly with some
13
13
/// 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`
14
15
///
15
16
/// ### Why is this bad?
16
17
/// Having explicit `else` blocks for `if` statements
@@ -75,6 +76,49 @@ declare_clippy_lint! {
75
76
/// # break;
76
77
/// }
77
78
/// ```
79
+ ///
80
+ /// ```rust
81
+ /// # use std::io::ErrorKind;
82
+ ///
83
+ /// fn foo() -> ErrorKind { ErrorKind::NotFound }
84
+ /// for _ in 0..10 {
85
+ /// match foo() {
86
+ /// ErrorKind::NotFound => {
87
+ /// eprintln!("not found");
88
+ /// continue
89
+ /// }
90
+ /// ErrorKind::TimedOut => {
91
+ /// eprintln!("timeout");
92
+ /// continue
93
+ /// }
94
+ /// _ => {
95
+ /// eprintln!("other error");
96
+ /// continue
97
+ /// }
98
+ /// }
99
+ /// }
100
+ /// ```
101
+ /// Could be rewritten as
102
+ ///
103
+ ///
104
+ /// ```rust
105
+ /// # use std::io::ErrorKind;
106
+ ///
107
+ /// fn foo() -> ErrorKind { ErrorKind::NotFound }
108
+ /// for _ in 0..10 {
109
+ /// match foo() {
110
+ /// ErrorKind::NotFound => {
111
+ /// eprintln!("not found");
112
+ /// }
113
+ /// ErrorKind::TimedOut => {
114
+ /// eprintln!("timeout");
115
+ /// }
116
+ /// _ => {
117
+ /// eprintln!("other error");
118
+ /// }
119
+ /// }
120
+ /// }
121
+ /// ```
78
122
#[ clippy:: version = "pre 1.29.0" ]
79
123
pub NEEDLESS_CONTINUE ,
80
124
pedantic,
@@ -144,15 +188,15 @@ impl EarlyLintPass for NeedlessContinue {
144
188
///
145
189
/// - The expression is a `continue` node.
146
190
/// - 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 {
191
+ fn needless_continue_in_else ( else_expr : & ast:: Expr , label : Option < & Label > ) -> bool {
148
192
match else_expr. kind {
149
193
ast:: ExprKind :: Block ( ref else_block, _) => is_first_block_stmt_continue ( else_block, label) ,
150
194
ast:: ExprKind :: Continue ( l) => compare_labels ( label, l. as_ref ( ) ) ,
151
195
_ => false ,
152
196
}
153
197
}
154
198
155
- fn is_first_block_stmt_continue ( block : & ast :: Block , label : Option < & ast :: Label > ) -> bool {
199
+ fn is_first_block_stmt_continue ( block : & Block , label : Option < & Label > ) -> bool {
156
200
block. stmts . first ( ) . is_some_and ( |stmt| match stmt. kind {
157
201
ast:: StmtKind :: Semi ( ref e) | ast:: StmtKind :: Expr ( ref e) => {
158
202
if let ast:: ExprKind :: Continue ( ref l) = e. kind {
@@ -166,7 +210,7 @@ fn is_first_block_stmt_continue(block: &ast::Block, label: Option<&ast::Label>)
166
210
}
167
211
168
212
/// 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 {
213
+ fn compare_labels ( loop_label : Option < & Label > , continue_label : Option < & Label > ) -> bool {
170
214
match ( loop_label, continue_label) {
171
215
// `loop { continue; }` or `'a loop { continue; }`
172
216
( _, None ) => true ,
@@ -181,7 +225,7 @@ fn compare_labels(loop_label: Option<&ast::Label>, continue_label: Option<&ast::
181
225
/// the AST object representing the loop block of `expr`.
182
226
fn with_loop_block < F > ( expr : & ast:: Expr , mut func : F )
183
227
where
184
- F : FnMut ( & ast :: Block , Option < & ast :: Label > ) ,
228
+ F : FnMut ( & Block , Option < & Label > ) ,
185
229
{
186
230
if let ast:: ExprKind :: While ( _, loop_block, label)
187
231
| ast:: ExprKind :: ForLoop {
@@ -205,7 +249,7 @@ where
205
249
/// - The `else` expression.
206
250
fn with_if_expr < F > ( stmt : & ast:: Stmt , mut func : F )
207
251
where
208
- F : FnMut ( & ast:: Expr , & ast:: Expr , & ast :: Block , & ast:: Expr ) ,
252
+ F : FnMut ( & ast:: Expr , & ast:: Expr , & Block , & ast:: Expr ) ,
209
253
{
210
254
match stmt. kind {
211
255
ast:: StmtKind :: Semi ( ref e) | ast:: StmtKind :: Expr ( ref e) => {
@@ -231,14 +275,14 @@ struct LintData<'a> {
231
275
/// The condition expression for the above `if`.
232
276
if_cond : & ' a ast:: Expr ,
233
277
/// The `then` block of the `if` statement.
234
- if_block : & ' a ast :: Block ,
278
+ if_block : & ' a Block ,
235
279
/// The `else` block of the `if` statement.
236
280
/// Note that we only work with `if` exprs that have an `else` branch.
237
281
else_expr : & ' a ast:: Expr ,
238
282
/// The 0-based index of the `if` statement in the containing loop block.
239
283
stmt_idx : usize ,
240
284
/// The statements of the loop block.
241
- loop_block : & ' a ast :: Block ,
285
+ loop_block : & ' a Block ,
242
286
}
243
287
244
288
const MSG_REDUNDANT_CONTINUE_EXPRESSION : & str = "this `continue` expression is redundant" ;
@@ -329,24 +373,63 @@ fn suggestion_snippet_for_continue_inside_else(cx: &EarlyContext<'_>, data: &Lin
329
373
)
330
374
}
331
375
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 ( )
376
+ fn check_last_stmt_in_expr < F > ( inner_expr : & ast:: Expr , func : & F )
377
+ where
378
+ F : Fn ( Option < & Label > , Span ) ,
379
+ {
380
+ match & inner_expr. kind {
381
+ ast:: ExprKind :: Continue ( continue_label) => {
382
+ func ( continue_label. as_ref ( ) , inner_expr. span ) ;
383
+ } ,
384
+ ast:: ExprKind :: If ( _, then_block, else_block) => {
385
+ check_last_stmt_in_block ( then_block, func) ;
386
+ if let Some ( else_block) = else_block {
387
+ check_last_stmt_in_expr ( else_block, func) ;
388
+ }
389
+ } ,
390
+ ast:: ExprKind :: Match ( _, arms, _) => {
391
+ for arm in arms {
392
+ if let Some ( expr) = & arm. body {
393
+ check_last_stmt_in_expr ( expr, func) ;
394
+ }
395
+ }
396
+ } ,
397
+ ast:: ExprKind :: Block ( b, _) => {
398
+ check_last_stmt_in_block ( b, func) ;
399
+ } ,
400
+ _ => { } ,
401
+ }
402
+ }
403
+
404
+ fn check_last_stmt_in_block < F > ( b : & Block , func : & F )
405
+ where
406
+ F : Fn ( Option < & Label > , Span ) ,
407
+ {
408
+ if let Some ( last_stmt) = b. stmts . last ( )
335
409
&& 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 ( ) )
338
410
{
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
- ) ;
411
+ check_last_stmt_in_expr ( inner_expr, func) ;
347
412
}
413
+ }
414
+
415
+ fn check_and_warn ( cx : & EarlyContext < ' _ > , expr : & ast:: Expr ) {
348
416
with_loop_block ( expr, |loop_block, label| {
349
- for ( i, stmt) in loop_block. stmts . iter ( ) . enumerate ( ) {
417
+ let p = |continue_label : Option < & Label > , span : Span | {
418
+ if compare_labels ( label, continue_label) {
419
+ span_lint_and_help (
420
+ cx,
421
+ NEEDLESS_CONTINUE ,
422
+ span,
423
+ MSG_REDUNDANT_CONTINUE_EXPRESSION ,
424
+ None ,
425
+ DROP_CONTINUE_EXPRESSION_MSG ,
426
+ ) ;
427
+ }
428
+ } ;
429
+
430
+ let stmts = & loop_block. stmts ;
431
+ for ( i, stmt) in stmts. iter ( ) . enumerate ( ) {
432
+ let mut maybe_emitted_in_if = false ;
350
433
with_if_expr ( stmt, |if_expr, cond, then_block, else_expr| {
351
434
let data = & LintData {
352
435
if_expr,
@@ -356,6 +439,8 @@ fn check_and_warn(cx: &EarlyContext<'_>, expr: &ast::Expr) {
356
439
stmt_idx : i,
357
440
loop_block,
358
441
} ;
442
+
443
+ maybe_emitted_in_if = true ;
359
444
if needless_continue_in_else ( else_expr, label) {
360
445
emit_warning (
361
446
cx,
@@ -365,8 +450,14 @@ fn check_and_warn(cx: &EarlyContext<'_>, expr: &ast::Expr) {
365
450
) ;
366
451
} else if is_first_block_stmt_continue ( then_block, label) {
367
452
emit_warning ( cx, data, DROP_ELSE_BLOCK_MSG , LintType :: ContinueInsideThenBlock ) ;
453
+ } else {
454
+ maybe_emitted_in_if = false ;
368
455
}
369
456
} ) ;
457
+
458
+ if i == stmts. len ( ) - 1 && !maybe_emitted_in_if {
459
+ check_last_stmt_in_block ( loop_block, & p) ;
460
+ }
370
461
}
371
462
} ) ;
372
463
}
@@ -400,7 +491,7 @@ fn erode_from_back(s: &str) -> String {
400
491
if ret. is_empty ( ) { s. to_string ( ) } else { ret }
401
492
}
402
493
403
- fn span_of_first_expr_in_block ( block : & ast :: Block ) -> Option < Span > {
494
+ fn span_of_first_expr_in_block ( block : & Block ) -> Option < Span > {
404
495
block. stmts . first ( ) . map ( |stmt| stmt. span )
405
496
}
406
497
0 commit comments