diff --git a/clippy_lints/src/loops/never_loop.rs b/clippy_lints/src/loops/never_loop.rs index 8a253ae5810f..0ac66fa98650 100644 --- a/clippy_lints/src/loops/never_loop.rs +++ b/clippy_lints/src/loops/never_loop.rs @@ -22,7 +22,10 @@ pub(super) fn check<'tcx>( for_loop: Option<&ForLoop<'_>>, ) { match never_loop_block(cx, block, &mut Vec::new(), loop_id) { - NeverLoopResult::Diverging { ref break_spans } => { + NeverLoopResult::Diverging { + ref break_spans, + ref never_spans, + } => { span_lint_and_then(cx, NEVER_LOOP, span, "this loop never actually loops", |diag| { if let Some(ForLoop { arg: iterator, @@ -34,12 +37,16 @@ pub(super) fn check<'tcx>( { // If the block contains a break or continue, or if the loop has a label, `MachineApplicable` is not // appropriate. - let app = if !contains_any_break_or_continue(block) && label.is_none() { + let mut app = if !contains_any_break_or_continue(block) && label.is_none() { Applicability::MachineApplicable } else { Applicability::Unspecified }; + if !never_spans.is_empty() { + app = Applicability::HasPlaceholders; + } + let mut suggestions = vec![( for_span.with_hi(iterator.span.hi()), for_to_if_let_sugg(cx, iterator, pat), @@ -51,6 +58,13 @@ pub(super) fn check<'tcx>( suggestions, app, ); + + for span in never_spans { + diag.span_help( + *span, + "this code is unreachable. Consider moving the reachable parts out", + ); + } } }); }, @@ -77,13 +91,16 @@ fn contains_any_break_or_continue(block: &Block<'_>) -> bool { /// The first two bits of information are in this enum, and the last part is in the /// `local_labels` variable, which contains a list of `(block_id, reachable)` pairs ordered by /// scope. -#[derive(Clone)] +#[derive(Clone, Debug)] enum NeverLoopResult { /// A continue may occur for the main loop. MayContinueMainLoop, /// We have not encountered any main loop continue, /// but we are diverging (subsequent control flow is not reachable) - Diverging { break_spans: Vec }, + Diverging { + break_spans: Vec, + never_spans: Vec, + }, /// We have not encountered any main loop continue, /// and subsequent control flow is (possibly) reachable Normal, @@ -128,14 +145,18 @@ fn combine_branches(b1: NeverLoopResult, b2: NeverLoopResult) -> NeverLoopResult ( NeverLoopResult::Diverging { break_spans: mut break_spans1, + never_spans: mut never_spans1, }, NeverLoopResult::Diverging { break_spans: mut break_spans2, + never_spans: mut never_spans2, }, ) => { break_spans1.append(&mut break_spans2); + never_spans1.append(&mut never_spans2); NeverLoopResult::Diverging { break_spans: break_spans1, + never_spans: never_spans1, } }, } @@ -207,6 +228,8 @@ fn all_spans_after_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> Vec { } return vec![stmt.span]; + } else if let Node::Block(_) = cx.tcx.parent_hir_node(expr.hir_id) { + return vec![expr.span]; } vec![] @@ -270,10 +293,13 @@ fn never_loop_expr<'tcx>( ExprKind::Match(e, arms, _) => { let e = never_loop_expr(cx, e, local_labels, main_loop_id); combine_seq(e, || { - arms.iter() - .fold(NeverLoopResult::Diverging { break_spans: vec![] }, |a, b| { - combine_branches(a, never_loop_expr(cx, b.body, local_labels, main_loop_id)) - }) + arms.iter().fold( + NeverLoopResult::Diverging { + break_spans: vec![], + never_spans: vec![], + }, + |a, b| combine_branches(a, never_loop_expr(cx, b.body, local_labels, main_loop_id)), + ) }) }, ExprKind::Block(b, _) => { @@ -296,6 +322,7 @@ fn never_loop_expr<'tcx>( } else { NeverLoopResult::Diverging { break_spans: all_spans_after_expr(cx, expr), + never_spans: vec![], } } }, @@ -306,7 +333,10 @@ fn never_loop_expr<'tcx>( combine_seq(first, || { // checks if break targets a block instead of a loop mark_block_as_reachable(expr, local_labels); - NeverLoopResult::Diverging { break_spans: vec![] } + NeverLoopResult::Diverging { + break_spans: vec![], + never_spans: vec![], + } }) }, ExprKind::Break(dest, e) => { @@ -322,11 +352,15 @@ fn never_loop_expr<'tcx>( } else { all_spans_after_expr(cx, expr) }, + never_spans: vec![], } }) }, ExprKind::Become(e) => combine_seq(never_loop_expr(cx, e, local_labels, main_loop_id), || { - NeverLoopResult::Diverging { break_spans: vec![] } + NeverLoopResult::Diverging { + break_spans: vec![], + never_spans: vec![], + } }), ExprKind::InlineAsm(asm) => combine_seq_many(asm.operands.iter().map(|(o, _)| match o { InlineAsmOperand::In { expr, .. } | InlineAsmOperand::InOut { expr, .. } => { @@ -356,7 +390,10 @@ fn never_loop_expr<'tcx>( }; let result = combine_seq(result, || { if cx.typeck_results().expr_ty(expr).is_never() { - NeverLoopResult::Diverging { break_spans: vec![] } + NeverLoopResult::Diverging { + break_spans: vec![], + never_spans: all_spans_after_expr(cx, expr), + } } else { NeverLoopResult::Normal } diff --git a/tests/ui/never_loop.rs b/tests/ui/never_loop.rs index 48d4b8ad1519..01db64a446c0 100644 --- a/tests/ui/never_loop.rs +++ b/tests/ui/never_loop.rs @@ -498,3 +498,27 @@ fn issue15059() { () } } + +fn issue15350() { + 'bar: for _ in 0..100 { + //~^ never_loop + loop { + //~^ never_loop + println!("This will still run"); + break 'bar; + } + } + + 'foo: for _ in 0..100 { + //~^ never_loop + loop { + //~^ never_loop + println!("This will still run"); + loop { + //~^ never_loop + println!("This will still run"); + break 'foo; + } + } + } +} diff --git a/tests/ui/never_loop.stderr b/tests/ui/never_loop.stderr index 54b463266a3a..4fda06cff4ab 100644 --- a/tests/ui/never_loop.stderr +++ b/tests/ui/never_loop.stderr @@ -193,6 +193,19 @@ LL | | return; LL | | } | |_____^ | +help: this code is unreachable. Consider moving the reachable parts out + --> tests/ui/never_loop.rs:436:9 + | +LL | / loop { +LL | | +LL | | break 'outer; +LL | | } + | |_________^ +help: this code is unreachable. Consider moving the reachable parts out + --> tests/ui/never_loop.rs:440:9 + | +LL | return; + | ^^^^^^^ help: if you need the first element of the iterator, try writing | LL - 'outer: for v in 0..10 { @@ -297,5 +310,87 @@ LL ~ LL ~ | -error: aborting due to 24 previous errors +error: this loop never actually loops + --> tests/ui/never_loop.rs:503:5 + | +LL | / 'bar: for _ in 0..100 { +LL | | +LL | | loop { +... | +LL | | } + | |_____^ + | +help: this code is unreachable. Consider moving the reachable parts out + --> tests/ui/never_loop.rs:505:9 + | +LL | / loop { +LL | | +LL | | println!("This will still run"); +LL | | break 'bar; +LL | | } + | |_________^ +help: if you need the first element of the iterator, try writing + | +LL - 'bar: for _ in 0..100 { +LL + if let Some(_) = (0..100).next() { + | + +error: this loop never actually loops + --> tests/ui/never_loop.rs:505:9 + | +LL | / loop { +LL | | +LL | | println!("This will still run"); +LL | | break 'bar; +LL | | } + | |_________^ + +error: this loop never actually loops + --> tests/ui/never_loop.rs:512:5 + | +LL | / 'foo: for _ in 0..100 { +LL | | +LL | | loop { +... | +LL | | } + | |_____^ + | +help: this code is unreachable. Consider moving the reachable parts out + --> tests/ui/never_loop.rs:514:9 + | +LL | / loop { +LL | | +LL | | println!("This will still run"); +LL | | loop { +... | +LL | | } + | |_________^ +help: if you need the first element of the iterator, try writing + | +LL - 'foo: for _ in 0..100 { +LL + if let Some(_) = (0..100).next() { + | + +error: this loop never actually loops + --> tests/ui/never_loop.rs:514:9 + | +LL | / loop { +LL | | +LL | | println!("This will still run"); +LL | | loop { +... | +LL | | } + | |_________^ + +error: this loop never actually loops + --> tests/ui/never_loop.rs:517:13 + | +LL | / loop { +LL | | +LL | | println!("This will still run"); +LL | | break 'foo; +LL | | } + | |_____________^ + +error: aborting due to 29 previous errors diff --git a/tests/ui/never_loop_fixable.fixed b/tests/ui/never_loop_fixable.fixed index 5bc9ff1bb4df..00c2af93a28f 100644 --- a/tests/ui/never_loop_fixable.fixed +++ b/tests/ui/never_loop_fixable.fixed @@ -1,4 +1,4 @@ -#![allow(clippy::iter_next_slice, clippy::needless_return)] +#![allow(clippy::iter_next_slice, clippy::needless_return, clippy::redundant_pattern_matching)] fn no_break_or_continue_loop() { if let Some(i) = [1, 2, 3].iter().next() { diff --git a/tests/ui/never_loop_fixable.rs b/tests/ui/never_loop_fixable.rs index 9782bc107e9a..de85599f094d 100644 --- a/tests/ui/never_loop_fixable.rs +++ b/tests/ui/never_loop_fixable.rs @@ -1,4 +1,4 @@ -#![allow(clippy::iter_next_slice, clippy::needless_return)] +#![allow(clippy::iter_next_slice, clippy::needless_return, clippy::redundant_pattern_matching)] fn no_break_or_continue_loop() { for i in [1, 2, 3].iter() {