Skip to content

Commit

Permalink
[needless_continue]: lint if the last stmt in for/while/loop is `co…
Browse files Browse the repository at this point in the history
…ntinue`, recursively

fixes: rust-lang#4077
  • Loading branch information
lengyijun authored and profetia committed Dec 28, 2024
1 parent 998c780 commit e582fcd
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 36 deletions.
2 changes: 1 addition & 1 deletion clippy_lints/src/methods/unnecessary_to_owned.rs
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,7 @@ fn can_change_type<'a>(cx: &LateContext<'a>, mut expr: &'a Expr<'a>, mut ty: Ty<
for (_, node) in cx.tcx.hir().parent_iter(expr.hir_id) {
match node {
Node::Stmt(_) => return true,
Node::Block(..) => continue,
Node::Block(..) => {},
Node::Item(item) => {
if let ItemKind::Fn(_, _, body_id) = &item.kind
&& let output_ty = return_ty(cx, item.owner_id)
Expand Down
122 changes: 100 additions & 22 deletions clippy_lints/src/needless_continue.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::source::{indent_of, snippet, snippet_block};
use rustc_ast::ast;
use rustc_ast::{Block, Label, ast};
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
use rustc_session::declare_lint_pass;
use rustc_span::Span;
Expand All @@ -11,6 +11,7 @@ declare_clippy_lint! {
/// that contain a `continue` statement in either their main blocks or their
/// `else`-blocks, when omitting the `else`-block possibly with some
/// rearrangement of code can make the code easier to understand.
/// The lint also checks if the last statement in the loop is a `continue`
///
/// ### Why is this bad?
/// Having explicit `else` blocks for `if` statements
Expand Down Expand Up @@ -75,6 +76,45 @@ declare_clippy_lint! {
/// # break;
/// }
/// ```
///
/// ```rust
/// fn foo() -> std::io::ErrorKind { std::io::ErrorKind::NotFound }
/// for _ in 0..10 {
/// match foo() {
/// std::io::ErrorKind::NotFound => {
/// eprintln!("not found");
/// continue
/// }
/// std::io::ErrorKind::TimedOut => {
/// eprintln!("timeout");
/// continue
/// }
/// _ => {
/// eprintln!("other error");
/// continue
/// }
/// }
/// }
/// ```
/// Could be rewritten as
///
///
/// ```rust
/// fn foo() -> std::io::ErrorKind { std::io::ErrorKind::NotFound }
/// for _ in 0..10 {
/// match foo() {
/// std::io::ErrorKind::NotFound => {
/// eprintln!("not found");
/// }
/// std::io::ErrorKind::TimedOut => {
/// eprintln!("timeout");
/// }
/// _ => {
/// eprintln!("other error");
/// }
/// }
/// }
/// ```
#[clippy::version = "pre 1.29.0"]
pub NEEDLESS_CONTINUE,
pedantic,
Expand Down Expand Up @@ -144,15 +184,15 @@ impl EarlyLintPass for NeedlessContinue {
///
/// - The expression is a `continue` node.
/// - The expression node is a block with the first statement being a `continue`.
fn needless_continue_in_else(else_expr: &ast::Expr, label: Option<&ast::Label>) -> bool {
fn needless_continue_in_else(else_expr: &ast::Expr, label: Option<&Label>) -> bool {
match else_expr.kind {
ast::ExprKind::Block(ref else_block, _) => is_first_block_stmt_continue(else_block, label),
ast::ExprKind::Continue(l) => compare_labels(label, l.as_ref()),
_ => false,
}
}

fn is_first_block_stmt_continue(block: &ast::Block, label: Option<&ast::Label>) -> bool {
fn is_first_block_stmt_continue(block: &Block, label: Option<&Label>) -> bool {
block.stmts.first().is_some_and(|stmt| match stmt.kind {
ast::StmtKind::Semi(ref e) | ast::StmtKind::Expr(ref e) => {
if let ast::ExprKind::Continue(ref l) = e.kind {
Expand All @@ -166,7 +206,7 @@ fn is_first_block_stmt_continue(block: &ast::Block, label: Option<&ast::Label>)
}

/// If the `continue` has a label, check it matches the label of the loop.
fn compare_labels(loop_label: Option<&ast::Label>, continue_label: Option<&ast::Label>) -> bool {
fn compare_labels(loop_label: Option<&Label>, continue_label: Option<&Label>) -> bool {
match (loop_label, continue_label) {
// `loop { continue; }` or `'a loop { continue; }`
(_, None) => true,
Expand All @@ -181,7 +221,7 @@ fn compare_labels(loop_label: Option<&ast::Label>, continue_label: Option<&ast::
/// the AST object representing the loop block of `expr`.
fn with_loop_block<F>(expr: &ast::Expr, mut func: F)
where
F: FnMut(&ast::Block, Option<&ast::Label>),
F: FnMut(&Block, Option<&Label>),
{
if let ast::ExprKind::While(_, loop_block, label)
| ast::ExprKind::ForLoop {
Expand All @@ -205,7 +245,7 @@ where
/// - The `else` expression.
fn with_if_expr<F>(stmt: &ast::Stmt, mut func: F)
where
F: FnMut(&ast::Expr, &ast::Expr, &ast::Block, &ast::Expr),
F: FnMut(&ast::Expr, &ast::Expr, &Block, &ast::Expr),
{
match stmt.kind {
ast::StmtKind::Semi(ref e) | ast::StmtKind::Expr(ref e) => {
Expand All @@ -231,14 +271,14 @@ struct LintData<'a> {
/// The condition expression for the above `if`.
if_cond: &'a ast::Expr,
/// The `then` block of the `if` statement.
if_block: &'a ast::Block,
if_block: &'a Block,
/// The `else` block of the `if` statement.
/// Note that we only work with `if` exprs that have an `else` branch.
else_expr: &'a ast::Expr,
/// The 0-based index of the `if` statement in the containing loop block.
stmt_idx: usize,
/// The statements of the loop block.
loop_block: &'a ast::Block,
loop_block: &'a Block,
}

const MSG_REDUNDANT_CONTINUE_EXPRESSION: &str = "this `continue` expression is redundant";
Expand Down Expand Up @@ -329,23 +369,61 @@ fn suggestion_snippet_for_continue_inside_else(cx: &EarlyContext<'_>, data: &Lin
)
}

fn check_and_warn(cx: &EarlyContext<'_>, expr: &ast::Expr) {
if let ast::ExprKind::Loop(loop_block, loop_label, ..) = &expr.kind
&& let Some(last_stmt) = loop_block.stmts.last()
fn check_last_stmt_in_expr<F>(inner_expr: &ast::Expr, func: &F)
where
F: Fn(Option<&Label>, Span),
{
match &inner_expr.kind {
ast::ExprKind::Continue(continue_label) => {
func(continue_label.as_ref(), inner_expr.span);
},
ast::ExprKind::If(_, then_block, else_block) => {
check_last_stmt_in_block(then_block, func);
if let Some(else_block) = else_block {
check_last_stmt_in_expr(else_block, func);
}
},
ast::ExprKind::Match(_, arms, _) => {
for arm in arms {
if let Some(expr) = &arm.body {
check_last_stmt_in_expr(expr, func);
}
}
},
ast::ExprKind::Block(b, _) => {
check_last_stmt_in_block(b, func);
},
_ => {},
}
}

fn check_last_stmt_in_block<F>(b: &Block, func: &F)
where
F: Fn(Option<&Label>, Span),
{
if let Some(last_stmt) = b.stmts.last()
&& let ast::StmtKind::Expr(inner_expr) | ast::StmtKind::Semi(inner_expr) = &last_stmt.kind
&& let ast::ExprKind::Continue(continue_label) = inner_expr.kind
&& compare_labels(loop_label.as_ref(), continue_label.as_ref())
{
span_lint_and_help(
cx,
NEEDLESS_CONTINUE,
last_stmt.span,
MSG_REDUNDANT_CONTINUE_EXPRESSION,
None,
DROP_CONTINUE_EXPRESSION_MSG,
);
check_last_stmt_in_expr(inner_expr, func);
}
}

fn check_and_warn(cx: &EarlyContext<'_>, expr: &ast::Expr) {
with_loop_block(expr, |loop_block, label| {
let p = |continue_label: Option<&Label>, span: Span| {
if compare_labels(label, continue_label) {
span_lint_and_help(
cx,
NEEDLESS_CONTINUE,
span,
MSG_REDUNDANT_CONTINUE_EXPRESSION,
None,
DROP_CONTINUE_EXPRESSION_MSG,
);
}
};
check_last_stmt_in_block(loop_block, &p);

for (i, stmt) in loop_block.stmts.iter().enumerate() {
with_if_expr(stmt, |if_expr, cond, then_block, else_expr| {
let data = &LintData {
Expand Down Expand Up @@ -400,7 +478,7 @@ fn erode_from_back(s: &str) -> String {
if ret.is_empty() { s.to_string() } else { ret }
}

fn span_of_first_expr_in_block(block: &ast::Block) -> Option<Span> {
fn span_of_first_expr_in_block(block: &Block) -> Option<Span> {
block.stmts.first().map(|stmt| stmt.span)
}

Expand Down
1 change: 0 additions & 1 deletion clippy_lints/src/redundant_else.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ impl EarlyLintPass for RedundantElse {
ExprKind::If(_, next_then, Some(next_els)) => {
then = next_then;
els = next_els;
continue;
},
// else if without else
ExprKind::If(..) => return,
Expand Down
4 changes: 0 additions & 4 deletions clippy_lints/src/transmute/transmute_undefined_repr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,14 @@ pub(super) fn check<'tcx>(
| (ReducedTy::UnorderedFields(from_sub_ty), ReducedTy::OrderedFields(Some(to_sub_ty))) => {
from_ty = from_sub_ty;
to_ty = to_sub_ty;
continue;
},
(ReducedTy::OrderedFields(Some(from_sub_ty)), ReducedTy::Other(to_sub_ty)) if reduced_tys.to_fat_ptr => {
from_ty = from_sub_ty;
to_ty = to_sub_ty;
continue;
},
(ReducedTy::Other(from_sub_ty), ReducedTy::OrderedFields(Some(to_sub_ty))) if reduced_tys.from_fat_ptr => {
from_ty = from_sub_ty;
to_ty = to_sub_ty;
continue;
},

// ptr <-> ptr
Expand All @@ -50,7 +47,6 @@ pub(super) fn check<'tcx>(
{
from_ty = from_sub_ty;
to_ty = to_sub_ty;
continue;
},

// fat ptr <-> (*size, *size)
Expand Down
2 changes: 1 addition & 1 deletion tests/missing-test-files.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ fn explore_directory(dir: &Path) -> Vec<String> {
missing_files.push(path.to_str().unwrap().to_string());
}
},
_ => continue,
_ => {},
};
}
}
Expand Down
39 changes: 39 additions & 0 deletions tests/ui/needless_continue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,14 @@ fn simple_loop4() {
}
}

fn simple_loop5() {
loop {
println!("bleh");
{ continue }
//~^ ERROR: this `continue` expression is redundant
}
}

mod issue_2329 {
fn condition() -> bool {
unimplemented!()
Expand Down Expand Up @@ -168,3 +176,34 @@ fn issue_13641() {
}
}
}

mod issue_4077 {
fn main() {
'outer: loop {
'inner: loop {
do_something();
if some_expr() {
println!("bar-7");
continue 'outer;
} else if !some_expr() {
println!("bar-8");
continue 'inner;
} else {
println!("bar-9");
continue 'inner;
}
}
}
}

// The contents of these functions are irrelevant, the purpose of this file is
// shown in main.

fn do_something() {
std::process::exit(0);
}

fn some_expr() -> bool {
true
}
}
38 changes: 31 additions & 7 deletions tests/ui/needless_continue.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,15 @@ error: this `continue` expression is redundant
--> tests/ui/needless_continue.rs:60:9
|
LL | continue;
| ^^^^^^^^^
| ^^^^^^^^
|
= help: consider dropping the `continue` expression

error: this `continue` expression is redundant
--> tests/ui/needless_continue.rs:68:9
|
LL | continue;
| ^^^^^^^^^
| ^^^^^^^^
|
= help: consider dropping the `continue` expression

Expand All @@ -91,8 +91,16 @@ LL | continue
|
= help: consider dropping the `continue` expression

error: this `continue` expression is redundant
--> tests/ui/needless_continue.rs:93:11
|
LL | { continue }
| ^^^^^^^^
|
= help: consider dropping the `continue` expression

error: this `else` block is redundant
--> tests/ui/needless_continue.rs:136:24
--> tests/ui/needless_continue.rs:144:24
|
LL | } else {
| ________________________^
Expand All @@ -117,7 +125,7 @@ LL | | }
}

error: there is no need for an explicit `else` block for this `if` expression
--> tests/ui/needless_continue.rs:143:17
--> tests/ui/needless_continue.rs:151:17
|
LL | / if condition() {
LL | |
Expand All @@ -137,12 +145,28 @@ LL | | }
}

error: this `continue` expression is redundant
--> tests/ui/needless_continue.rs:166:13
--> tests/ui/needless_continue.rs:174:13
|
LL | continue 'b;
| ^^^^^^^^^^^^
| ^^^^^^^^^^^
|
= help: consider dropping the `continue` expression

error: this `continue` expression is redundant
--> tests/ui/needless_continue.rs:190:21
|
LL | continue 'inner;
| ^^^^^^^^^^^^^^^
|
= help: consider dropping the `continue` expression

error: this `continue` expression is redundant
--> tests/ui/needless_continue.rs:193:21
|
LL | continue 'inner;
| ^^^^^^^^^^^^^^^
|
= help: consider dropping the `continue` expression

error: aborting due to 9 previous errors
error: aborting due to 12 previous errors

0 comments on commit e582fcd

Please sign in to comment.