mirror of
https://github.com/rust-lang/rust-clippy
synced 2024-11-24 13:43:17 +00:00
Auto merge of #9858 - DesmondWillowbrook:never_loop, r=dswij
`never_loop`: don't emit AlwaysBreaks if it targets a block ref: https://github.com/rust-lang/rust-clippy/pull/9837#issuecomment-1312788194 The previous fix (#9837) was too simple and ignored all break commands inside a labelled block, regardless of whether their destination was a labelled block or a loop. This fix tracks all the labelled blocks in scope to ensure that only breaks targeting loops are considered. changelog: [`never_loop`]: prevent false negatives from `breaks` nested in labelled blocks
This commit is contained in:
commit
1c9c34de17
3 changed files with 94 additions and 46 deletions
|
@ -4,7 +4,7 @@ use clippy_utils::diagnostics::span_lint_and_then;
|
||||||
use clippy_utils::higher::ForLoop;
|
use clippy_utils::higher::ForLoop;
|
||||||
use clippy_utils::source::snippet;
|
use clippy_utils::source::snippet;
|
||||||
use rustc_errors::Applicability;
|
use rustc_errors::Applicability;
|
||||||
use rustc_hir::{Block, Expr, ExprKind, HirId, InlineAsmOperand, Pat, Stmt, StmtKind};
|
use rustc_hir::{Block, Destination, Expr, ExprKind, HirId, InlineAsmOperand, Pat, Stmt, StmtKind};
|
||||||
use rustc_lint::LateContext;
|
use rustc_lint::LateContext;
|
||||||
use rustc_span::Span;
|
use rustc_span::Span;
|
||||||
use std::iter::{once, Iterator};
|
use std::iter::{once, Iterator};
|
||||||
|
@ -16,7 +16,7 @@ pub(super) fn check(
|
||||||
span: Span,
|
span: Span,
|
||||||
for_loop: Option<&ForLoop<'_>>,
|
for_loop: Option<&ForLoop<'_>>,
|
||||||
) {
|
) {
|
||||||
match never_loop_block(block, loop_id) {
|
match never_loop_block(block, &mut Vec::new(), loop_id) {
|
||||||
NeverLoopResult::AlwaysBreak => {
|
NeverLoopResult::AlwaysBreak => {
|
||||||
span_lint_and_then(cx, NEVER_LOOP, span, "this loop never actually loops", |diag| {
|
span_lint_and_then(cx, NEVER_LOOP, span, "this loop never actually loops", |diag| {
|
||||||
if let Some(ForLoop {
|
if let Some(ForLoop {
|
||||||
|
@ -92,35 +92,34 @@ fn combine_branches(b1: NeverLoopResult, b2: NeverLoopResult) -> NeverLoopResult
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn never_loop_block(block: &Block<'_>, main_loop_id: HirId) -> NeverLoopResult {
|
fn never_loop_block(block: &Block<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id: HirId) -> NeverLoopResult {
|
||||||
let mut iter = block
|
let iter = block
|
||||||
.stmts
|
.stmts
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(stmt_to_expr)
|
.filter_map(stmt_to_expr)
|
||||||
.chain(block.expr.map(|expr| (expr, None)));
|
.chain(block.expr.map(|expr| (expr, None)));
|
||||||
never_loop_expr_seq(&mut iter, main_loop_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn never_loop_expr_seq<'a, T: Iterator<Item = (&'a Expr<'a>, Option<&'a Block<'a>>)>>(
|
iter.map(|(e, els)| {
|
||||||
es: &mut T,
|
let e = never_loop_expr(e, ignore_ids, main_loop_id);
|
||||||
main_loop_id: HirId,
|
// els is an else block in a let...else binding
|
||||||
) -> NeverLoopResult {
|
els.map_or(e, |els| {
|
||||||
es.map(|(e, els)| {
|
combine_branches(e, never_loop_block(els, ignore_ids, main_loop_id))
|
||||||
let e = never_loop_expr(e, main_loop_id);
|
})
|
||||||
els.map_or(e, |els| combine_branches(e, never_loop_block(els, main_loop_id)))
|
|
||||||
})
|
})
|
||||||
.fold(NeverLoopResult::Otherwise, combine_seq)
|
.fold(NeverLoopResult::Otherwise, combine_seq)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stmt_to_expr<'tcx>(stmt: &Stmt<'tcx>) -> Option<(&'tcx Expr<'tcx>, Option<&'tcx Block<'tcx>>)> {
|
fn stmt_to_expr<'tcx>(stmt: &Stmt<'tcx>) -> Option<(&'tcx Expr<'tcx>, Option<&'tcx Block<'tcx>>)> {
|
||||||
match stmt.kind {
|
match stmt.kind {
|
||||||
StmtKind::Semi(e, ..) | StmtKind::Expr(e, ..) => Some((e, None)),
|
StmtKind::Semi(e) | StmtKind::Expr(e) => Some((e, None)),
|
||||||
|
// add the let...else expression (if present)
|
||||||
StmtKind::Local(local) => local.init.map(|init| (init, local.els)),
|
StmtKind::Local(local) => local.init.map(|init| (init, local.els)),
|
||||||
StmtKind::Item(..) => None,
|
StmtKind::Item(..) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn never_loop_expr(expr: &Expr<'_>, main_loop_id: HirId) -> NeverLoopResult {
|
#[allow(clippy::too_many_lines)]
|
||||||
|
fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id: HirId) -> NeverLoopResult {
|
||||||
match expr.kind {
|
match expr.kind {
|
||||||
ExprKind::Box(e)
|
ExprKind::Box(e)
|
||||||
| ExprKind::Unary(_, e)
|
| ExprKind::Unary(_, e)
|
||||||
|
@ -129,48 +128,56 @@ fn never_loop_expr(expr: &Expr<'_>, main_loop_id: HirId) -> NeverLoopResult {
|
||||||
| ExprKind::Field(e, _)
|
| ExprKind::Field(e, _)
|
||||||
| ExprKind::AddrOf(_, _, e)
|
| ExprKind::AddrOf(_, _, e)
|
||||||
| ExprKind::Repeat(e, _)
|
| ExprKind::Repeat(e, _)
|
||||||
| ExprKind::DropTemps(e) => never_loop_expr(e, main_loop_id),
|
| ExprKind::DropTemps(e) => never_loop_expr(e, ignore_ids, main_loop_id),
|
||||||
ExprKind::Let(let_expr) => never_loop_expr(let_expr.init, main_loop_id),
|
ExprKind::Let(let_expr) => never_loop_expr(let_expr.init, ignore_ids, main_loop_id),
|
||||||
ExprKind::Array(es) | ExprKind::Tup(es) => never_loop_expr_all(&mut es.iter(), main_loop_id),
|
ExprKind::Array(es) | ExprKind::Tup(es) => never_loop_expr_all(&mut es.iter(), ignore_ids, main_loop_id),
|
||||||
ExprKind::MethodCall(_, receiver, es, _) => {
|
ExprKind::MethodCall(_, receiver, es, _) => never_loop_expr_all(
|
||||||
never_loop_expr_all(&mut std::iter::once(receiver).chain(es.iter()), main_loop_id)
|
&mut std::iter::once(receiver).chain(es.iter()),
|
||||||
},
|
ignore_ids,
|
||||||
|
main_loop_id,
|
||||||
|
),
|
||||||
ExprKind::Struct(_, fields, base) => {
|
ExprKind::Struct(_, fields, base) => {
|
||||||
let fields = never_loop_expr_all(&mut fields.iter().map(|f| f.expr), main_loop_id);
|
let fields = never_loop_expr_all(&mut fields.iter().map(|f| f.expr), ignore_ids, main_loop_id);
|
||||||
if let Some(base) = base {
|
if let Some(base) = base {
|
||||||
combine_both(fields, never_loop_expr(base, main_loop_id))
|
combine_both(fields, never_loop_expr(base, ignore_ids, main_loop_id))
|
||||||
} else {
|
} else {
|
||||||
fields
|
fields
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ExprKind::Call(e, es) => never_loop_expr_all(&mut once(e).chain(es.iter()), main_loop_id),
|
ExprKind::Call(e, es) => never_loop_expr_all(&mut once(e).chain(es.iter()), ignore_ids, main_loop_id),
|
||||||
ExprKind::Binary(_, e1, e2)
|
ExprKind::Binary(_, e1, e2)
|
||||||
| ExprKind::Assign(e1, e2, _)
|
| ExprKind::Assign(e1, e2, _)
|
||||||
| ExprKind::AssignOp(_, e1, e2)
|
| ExprKind::AssignOp(_, e1, e2)
|
||||||
| ExprKind::Index(e1, e2) => never_loop_expr_all(&mut [e1, e2].iter().copied(), main_loop_id),
|
| ExprKind::Index(e1, e2) => never_loop_expr_all(&mut [e1, e2].iter().copied(), ignore_ids, main_loop_id),
|
||||||
ExprKind::Loop(b, _, _, _) => {
|
ExprKind::Loop(b, _, _, _) => {
|
||||||
// Break can come from the inner loop so remove them.
|
// Break can come from the inner loop so remove them.
|
||||||
absorb_break(never_loop_block(b, main_loop_id))
|
absorb_break(never_loop_block(b, ignore_ids, main_loop_id))
|
||||||
},
|
},
|
||||||
ExprKind::If(e, e2, e3) => {
|
ExprKind::If(e, e2, e3) => {
|
||||||
let e1 = never_loop_expr(e, main_loop_id);
|
let e1 = never_loop_expr(e, ignore_ids, main_loop_id);
|
||||||
let e2 = never_loop_expr(e2, main_loop_id);
|
let e2 = never_loop_expr(e2, ignore_ids, main_loop_id);
|
||||||
let e3 = e3
|
let e3 = e3.as_ref().map_or(NeverLoopResult::Otherwise, |e| {
|
||||||
.as_ref()
|
never_loop_expr(e, ignore_ids, main_loop_id)
|
||||||
.map_or(NeverLoopResult::Otherwise, |e| never_loop_expr(e, main_loop_id));
|
});
|
||||||
combine_seq(e1, combine_branches(e2, e3))
|
combine_seq(e1, combine_branches(e2, e3))
|
||||||
},
|
},
|
||||||
ExprKind::Match(e, arms, _) => {
|
ExprKind::Match(e, arms, _) => {
|
||||||
let e = never_loop_expr(e, main_loop_id);
|
let e = never_loop_expr(e, ignore_ids, main_loop_id);
|
||||||
if arms.is_empty() {
|
if arms.is_empty() {
|
||||||
e
|
e
|
||||||
} else {
|
} else {
|
||||||
let arms = never_loop_expr_branch(&mut arms.iter().map(|a| a.body), main_loop_id);
|
let arms = never_loop_expr_branch(&mut arms.iter().map(|a| a.body), ignore_ids, main_loop_id);
|
||||||
combine_seq(e, arms)
|
combine_seq(e, arms)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ExprKind::Block(b, None) => never_loop_block(b, main_loop_id),
|
ExprKind::Block(b, l) => {
|
||||||
ExprKind::Block(b, Some(_label)) => absorb_break(never_loop_block(b, main_loop_id)),
|
if l.is_some() {
|
||||||
|
ignore_ids.push(b.hir_id);
|
||||||
|
}
|
||||||
|
let ret = never_loop_block(b, ignore_ids, main_loop_id);
|
||||||
|
ignore_ids.pop();
|
||||||
|
ret
|
||||||
|
},
|
||||||
ExprKind::Continue(d) => {
|
ExprKind::Continue(d) => {
|
||||||
let id = d
|
let id = d
|
||||||
.target_id
|
.target_id
|
||||||
|
@ -181,20 +188,32 @@ fn never_loop_expr(expr: &Expr<'_>, main_loop_id: HirId) -> NeverLoopResult {
|
||||||
NeverLoopResult::AlwaysBreak
|
NeverLoopResult::AlwaysBreak
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// checks if break targets a block instead of a loop
|
||||||
|
ExprKind::Break(Destination { target_id: Ok(t), .. }, e) if ignore_ids.contains(&t) => e
|
||||||
|
.map_or(NeverLoopResult::Otherwise, |e| {
|
||||||
|
combine_seq(never_loop_expr(e, ignore_ids, main_loop_id), NeverLoopResult::Otherwise)
|
||||||
|
}),
|
||||||
ExprKind::Break(_, e) | ExprKind::Ret(e) => e.as_ref().map_or(NeverLoopResult::AlwaysBreak, |e| {
|
ExprKind::Break(_, e) | ExprKind::Ret(e) => e.as_ref().map_or(NeverLoopResult::AlwaysBreak, |e| {
|
||||||
combine_seq(never_loop_expr(e, main_loop_id), NeverLoopResult::AlwaysBreak)
|
combine_seq(
|
||||||
|
never_loop_expr(e, ignore_ids, main_loop_id),
|
||||||
|
NeverLoopResult::AlwaysBreak,
|
||||||
|
)
|
||||||
}),
|
}),
|
||||||
ExprKind::InlineAsm(asm) => asm
|
ExprKind::InlineAsm(asm) => asm
|
||||||
.operands
|
.operands
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(o, _)| match o {
|
.map(|(o, _)| match o {
|
||||||
InlineAsmOperand::In { expr, .. } | InlineAsmOperand::InOut { expr, .. } => {
|
InlineAsmOperand::In { expr, .. } | InlineAsmOperand::InOut { expr, .. } => {
|
||||||
never_loop_expr(expr, main_loop_id)
|
never_loop_expr(expr, ignore_ids, main_loop_id)
|
||||||
},
|
},
|
||||||
InlineAsmOperand::Out { expr, .. } => never_loop_expr_all(&mut expr.iter().copied(), main_loop_id),
|
InlineAsmOperand::Out { expr, .. } => {
|
||||||
InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => {
|
never_loop_expr_all(&mut expr.iter().copied(), ignore_ids, main_loop_id)
|
||||||
never_loop_expr_all(&mut once(*in_expr).chain(out_expr.iter().copied()), main_loop_id)
|
|
||||||
},
|
},
|
||||||
|
InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => never_loop_expr_all(
|
||||||
|
&mut once(*in_expr).chain(out_expr.iter().copied()),
|
||||||
|
ignore_ids,
|
||||||
|
main_loop_id,
|
||||||
|
),
|
||||||
InlineAsmOperand::Const { .. }
|
InlineAsmOperand::Const { .. }
|
||||||
| InlineAsmOperand::SymFn { .. }
|
| InlineAsmOperand::SymFn { .. }
|
||||||
| InlineAsmOperand::SymStatic { .. } => NeverLoopResult::Otherwise,
|
| InlineAsmOperand::SymStatic { .. } => NeverLoopResult::Otherwise,
|
||||||
|
@ -209,13 +228,21 @@ fn never_loop_expr(expr: &Expr<'_>, main_loop_id: HirId) -> NeverLoopResult {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn never_loop_expr_all<'a, T: Iterator<Item = &'a Expr<'a>>>(es: &mut T, main_loop_id: HirId) -> NeverLoopResult {
|
fn never_loop_expr_all<'a, T: Iterator<Item = &'a Expr<'a>>>(
|
||||||
es.map(|e| never_loop_expr(e, main_loop_id))
|
es: &mut T,
|
||||||
|
ignore_ids: &mut Vec<HirId>,
|
||||||
|
main_loop_id: HirId,
|
||||||
|
) -> NeverLoopResult {
|
||||||
|
es.map(|e| never_loop_expr(e, ignore_ids, main_loop_id))
|
||||||
.fold(NeverLoopResult::Otherwise, combine_both)
|
.fold(NeverLoopResult::Otherwise, combine_both)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn never_loop_expr_branch<'a, T: Iterator<Item = &'a Expr<'a>>>(e: &mut T, main_loop_id: HirId) -> NeverLoopResult {
|
fn never_loop_expr_branch<'a, T: Iterator<Item = &'a Expr<'a>>>(
|
||||||
e.map(|e| never_loop_expr(e, main_loop_id))
|
e: &mut T,
|
||||||
|
ignore_ids: &mut Vec<HirId>,
|
||||||
|
main_loop_id: HirId,
|
||||||
|
) -> NeverLoopResult {
|
||||||
|
e.map(|e| never_loop_expr(e, ignore_ids, main_loop_id))
|
||||||
.fold(NeverLoopResult::AlwaysBreak, combine_branches)
|
.fold(NeverLoopResult::AlwaysBreak, combine_branches)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -234,13 +234,22 @@ pub fn test19() {
|
||||||
fn thing(iter: impl Iterator) {
|
fn thing(iter: impl Iterator) {
|
||||||
for _ in iter {
|
for _ in iter {
|
||||||
'b: {
|
'b: {
|
||||||
// error goes away if we just have the block's value be ().
|
|
||||||
break 'b;
|
break 'b;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn test20() {
|
||||||
|
'a: loop {
|
||||||
|
'b: {
|
||||||
|
break 'b 'c: {
|
||||||
|
break 'a;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
test1();
|
test1();
|
||||||
test2();
|
test2();
|
||||||
|
|
|
@ -114,5 +114,17 @@ LL | | break x;
|
||||||
LL | | };
|
LL | | };
|
||||||
| |_____^
|
| |_____^
|
||||||
|
|
||||||
error: aborting due to 10 previous errors
|
error: this loop never actually loops
|
||||||
|
--> $DIR/never_loop.rs:244:5
|
||||||
|
|
|
||||||
|
LL | / 'a: loop {
|
||||||
|
LL | | 'b: {
|
||||||
|
LL | | break 'b 'c: {
|
||||||
|
LL | | break 'a;
|
||||||
|
LL | | };
|
||||||
|
LL | | }
|
||||||
|
LL | | }
|
||||||
|
| |_____^
|
||||||
|
|
||||||
|
error: aborting due to 11 previous errors
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue