diff --git a/clippy_lints/src/loops/never_loop.rs b/clippy_lints/src/loops/never_loop.rs index 5f1fdf00be8..4f2092492aa 100644 --- a/clippy_lints/src/loops/never_loop.rs +++ b/clippy_lints/src/loops/never_loop.rs @@ -1,22 +1,23 @@ use super::utils::make_iterator_snippet; use super::NEVER_LOOP; -use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::consts::constant; use clippy_utils::higher::ForLoop; use clippy_utils::source::snippet; +use clippy_utils::{consts::Constant, diagnostics::span_lint_and_then}; use rustc_errors::Applicability; use rustc_hir::{Block, Destination, Expr, ExprKind, HirId, InlineAsmOperand, Pat, Stmt, StmtKind}; use rustc_lint::LateContext; use rustc_span::Span; use std::iter::{once, Iterator}; -pub(super) fn check( - cx: &LateContext<'_>, - block: &Block<'_>, +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + block: &Block<'tcx>, loop_id: HirId, span: Span, for_loop: Option<&ForLoop<'_>>, ) { - match never_loop_block(block, &mut Vec::new(), loop_id) { + match never_loop_block(cx, block, &mut Vec::new(), loop_id) { NeverLoopResult::AlwaysBreak => { span_lint_and_then(cx, NEVER_LOOP, span, "this loop never actually loops", |diag| { if let Some(ForLoop { @@ -95,7 +96,12 @@ fn combine_branches(b1: NeverLoopResult, b2: NeverLoopResult, ignore_ids: &[HirI } } -fn never_loop_block(block: &Block<'_>, ignore_ids: &mut Vec, main_loop_id: HirId) -> NeverLoopResult { +fn never_loop_block<'tcx>( + cx: &LateContext<'tcx>, + block: &Block<'tcx>, + ignore_ids: &mut Vec, + main_loop_id: HirId, +) -> NeverLoopResult { let iter = block .stmts .iter() @@ -103,10 +109,10 @@ fn never_loop_block(block: &Block<'_>, ignore_ids: &mut Vec, main_loop_id .chain(block.expr.map(|expr| (expr, None))); iter.map(|(e, els)| { - let e = never_loop_expr(e, ignore_ids, main_loop_id); + let e = never_loop_expr(cx, e, ignore_ids, main_loop_id); // els is an else block in a let...else binding els.map_or(e, |els| { - combine_branches(e, never_loop_block(els, ignore_ids, main_loop_id), ignore_ids) + combine_branches(e, never_loop_block(cx, els, ignore_ids, main_loop_id), ignore_ids) }) }) .fold(NeverLoopResult::Otherwise, combine_seq) @@ -122,7 +128,12 @@ fn stmt_to_expr<'tcx>(stmt: &Stmt<'tcx>) -> Option<(&'tcx Expr<'tcx>, Option<&'t } #[allow(clippy::too_many_lines)] -fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec, main_loop_id: HirId) -> NeverLoopResult { +fn never_loop_expr<'tcx>( + cx: &LateContext<'tcx>, + expr: &Expr<'tcx>, + ignore_ids: &mut Vec, + main_loop_id: HirId, +) -> NeverLoopResult { match expr.kind { ExprKind::Unary(_, e) | ExprKind::Cast(e, _) @@ -130,45 +141,51 @@ fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec, main_loop_id: H | ExprKind::Field(e, _) | ExprKind::AddrOf(_, _, e) | ExprKind::Repeat(e, _) - | ExprKind::DropTemps(e) => never_loop_expr(e, ignore_ids, 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(), ignore_ids, main_loop_id), + | ExprKind::DropTemps(e) => never_loop_expr(cx, e, ignore_ids, main_loop_id), + ExprKind::Let(let_expr) => never_loop_expr(cx, let_expr.init, ignore_ids, main_loop_id), + ExprKind::Array(es) | ExprKind::Tup(es) => never_loop_expr_all(cx, &mut es.iter(), ignore_ids, main_loop_id), ExprKind::MethodCall(_, receiver, es, _) => never_loop_expr_all( + cx, &mut std::iter::once(receiver).chain(es.iter()), ignore_ids, main_loop_id, ), ExprKind::Struct(_, fields, base) => { - let fields = never_loop_expr_all(&mut fields.iter().map(|f| f.expr), ignore_ids, main_loop_id); + let fields = never_loop_expr_all(cx, &mut fields.iter().map(|f| f.expr), ignore_ids, main_loop_id); if let Some(base) = base { - combine_seq(fields, never_loop_expr(base, ignore_ids, main_loop_id)) + combine_seq(fields, never_loop_expr(cx, base, ignore_ids, main_loop_id)) } else { fields } }, - ExprKind::Call(e, es) => never_loop_expr_all(&mut once(e).chain(es.iter()), ignore_ids, main_loop_id), + ExprKind::Call(e, es) => never_loop_expr_all(cx, &mut once(e).chain(es.iter()), ignore_ids, main_loop_id), ExprKind::Binary(_, e1, e2) | ExprKind::Assign(e1, e2, _) | ExprKind::AssignOp(_, e1, e2) - | ExprKind::Index(e1, e2) => never_loop_expr_all(&mut [e1, e2].iter().copied(), ignore_ids, main_loop_id), + | ExprKind::Index(e1, e2) => never_loop_expr_all(cx, &mut [e1, e2].iter().copied(), ignore_ids, main_loop_id), ExprKind::Loop(b, _, _, _) => { // Break can come from the inner loop so remove them. - absorb_break(never_loop_block(b, ignore_ids, main_loop_id)) + absorb_break(never_loop_block(cx, b, ignore_ids, main_loop_id)) }, ExprKind::If(e, e2, e3) => { - let e1 = never_loop_expr(e, ignore_ids, main_loop_id); - let e2 = never_loop_expr(e2, ignore_ids, main_loop_id); + let e1 = never_loop_expr(cx, e, ignore_ids, main_loop_id); + let e2 = never_loop_expr(cx, e2, ignore_ids, main_loop_id); + // If we know the `if` condition evaluates to `true`, don't check everything past it; it + // should just return whatever's evaluated for `e1` and `e2` since `e3` is unreachable + if let Some(Constant::Bool(true)) = constant(cx, cx.typeck_results(), e) { + return combine_seq(e1, e2); + } let e3 = e3.as_ref().map_or(NeverLoopResult::Otherwise, |e| { - never_loop_expr(e, ignore_ids, main_loop_id) + never_loop_expr(cx, e, ignore_ids, main_loop_id) }); combine_seq(e1, combine_branches(e2, e3, ignore_ids)) }, ExprKind::Match(e, arms, _) => { - let e = never_loop_expr(e, ignore_ids, main_loop_id); + let e = never_loop_expr(cx, e, ignore_ids, main_loop_id); if arms.is_empty() { e } else { - let arms = never_loop_expr_branch(&mut arms.iter().map(|a| a.body), ignore_ids, main_loop_id); + let arms = never_loop_expr_branch(cx, &mut arms.iter().map(|a| a.body), ignore_ids, main_loop_id); combine_seq(e, arms) } }, @@ -176,7 +193,7 @@ fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec, main_loop_id: H if l.is_some() { ignore_ids.push(b.hir_id); } - let ret = never_loop_block(b, ignore_ids, main_loop_id); + let ret = never_loop_block(cx, b, ignore_ids, main_loop_id); if l.is_some() { ignore_ids.pop(); } @@ -198,11 +215,11 @@ fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec, main_loop_id: H // 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::IgnoreUntilEnd(t), |e| { - never_loop_expr(e, ignore_ids, main_loop_id) + never_loop_expr(cx, e, ignore_ids, main_loop_id) }), ExprKind::Break(_, e) | ExprKind::Ret(e) => e.as_ref().map_or(NeverLoopResult::AlwaysBreak, |e| { combine_seq( - never_loop_expr(e, ignore_ids, main_loop_id), + never_loop_expr(cx, e, ignore_ids, main_loop_id), NeverLoopResult::AlwaysBreak, ) }), @@ -211,12 +228,13 @@ fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec, main_loop_id: H .iter() .map(|(o, _)| match o { InlineAsmOperand::In { expr, .. } | InlineAsmOperand::InOut { expr, .. } => { - never_loop_expr(expr, ignore_ids, main_loop_id) + never_loop_expr(cx, expr, ignore_ids, main_loop_id) }, InlineAsmOperand::Out { expr, .. } => { - never_loop_expr_all(&mut expr.iter().copied(), ignore_ids, main_loop_id) + never_loop_expr_all(cx, &mut expr.iter().copied(), ignore_ids, main_loop_id) }, InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => never_loop_expr_all( + cx, &mut once(*in_expr).chain(out_expr.iter().copied()), ignore_ids, main_loop_id, @@ -236,22 +254,24 @@ fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec, main_loop_id: H } } -fn never_loop_expr_all<'a, T: Iterator>>( +fn never_loop_expr_all<'tcx, T: Iterator>>( + cx: &LateContext<'tcx>, es: &mut T, ignore_ids: &mut Vec, main_loop_id: HirId, ) -> NeverLoopResult { - es.map(|e| never_loop_expr(e, ignore_ids, main_loop_id)) + es.map(|e| never_loop_expr(cx, e, ignore_ids, main_loop_id)) .fold(NeverLoopResult::Otherwise, combine_seq) } -fn never_loop_expr_branch<'a, T: Iterator>>( +fn never_loop_expr_branch<'tcx, T: Iterator>>( + cx: &LateContext<'tcx>, e: &mut T, ignore_ids: &mut Vec, main_loop_id: HirId, ) -> NeverLoopResult { e.fold(NeverLoopResult::AlwaysBreak, |a, b| { - combine_branches(a, never_loop_expr(b, ignore_ids, main_loop_id), ignore_ids) + combine_branches(a, never_loop_expr(cx, b, ignore_ids, main_loop_id), ignore_ids) }) } diff --git a/clippy_utils/src/consts.rs b/clippy_utils/src/consts.rs index 72582ba7e78..03dc9ca56ff 100644 --- a/clippy_utils/src/consts.rs +++ b/clippy_utils/src/consts.rs @@ -6,7 +6,7 @@ use if_chain::if_chain; use rustc_ast::ast::{self, LitFloatType, LitKind}; use rustc_data_structures::sync::Lrc; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::{BinOp, BinOpKind, Block, Expr, ExprKind, HirId, Item, ItemKind, Node, QPath, UnOp}; +use rustc_hir::{AnonConst, BinOp, BinOpKind, Block, Expr, ExprKind, HirId, Item, ItemKind, Node, QPath, UnOp}; use rustc_lexer::tokenize; use rustc_lint::LateContext; use rustc_middle::mir; @@ -344,6 +344,8 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> { /// Simple constant folding: Insert an expression, get a constant or none. pub fn expr(&mut self, e: &Expr<'_>) -> Option> { match e.kind { + ExprKind::ConstBlock(AnonConst { body, .. }) => self.expr(self.lcx.tcx.hir().body(body).value), + ExprKind::DropTemps(e) => self.expr(e), ExprKind::Path(ref qpath) => self.fetch_path(qpath, e.hir_id, self.typeck_results.expr_ty(e)), ExprKind::Block(block, _) => self.block(block), ExprKind::Lit(lit) => { diff --git a/tests/ui/large_futures.rs b/tests/ui/large_futures.rs index 4a8ba995da5..e0f6b3d9d3b 100644 --- a/tests/ui/large_futures.rs +++ b/tests/ui/large_futures.rs @@ -1,5 +1,6 @@ #![feature(generators)] #![warn(clippy::large_futures)] +#![allow(clippy::never_loop)] #![allow(clippy::future_not_send)] #![allow(clippy::manual_async_fn)] diff --git a/tests/ui/large_futures.stderr b/tests/ui/large_futures.stderr index 67e0fceff6e..5bcf054884e 100644 --- a/tests/ui/large_futures.stderr +++ b/tests/ui/large_futures.stderr @@ -1,5 +1,5 @@ error: large future with a size of 16385 bytes - --> $DIR/large_futures.rs:10:9 + --> $DIR/large_futures.rs:11:9 | LL | big_fut([0u8; 1024 * 16]).await; | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider `Box::pin` on it: `Box::pin(big_fut([0u8; 1024 * 16]))` @@ -7,37 +7,37 @@ LL | big_fut([0u8; 1024 * 16]).await; = note: `-D clippy::large-futures` implied by `-D warnings` error: large future with a size of 16386 bytes - --> $DIR/large_futures.rs:12:5 + --> $DIR/large_futures.rs:13:5 | LL | f.await | ^ help: consider `Box::pin` on it: `Box::pin(f)` error: large future with a size of 16387 bytes - --> $DIR/large_futures.rs:16:9 + --> $DIR/large_futures.rs:17:9 | LL | wait().await; | ^^^^^^ help: consider `Box::pin` on it: `Box::pin(wait())` error: large future with a size of 16387 bytes - --> $DIR/large_futures.rs:20:13 + --> $DIR/large_futures.rs:21:13 | LL | wait().await; | ^^^^^^ help: consider `Box::pin` on it: `Box::pin(wait())` error: large future with a size of 65540 bytes - --> $DIR/large_futures.rs:27:5 + --> $DIR/large_futures.rs:28:5 | LL | foo().await; | ^^^^^ help: consider `Box::pin` on it: `Box::pin(foo())` error: large future with a size of 49159 bytes - --> $DIR/large_futures.rs:28:5 + --> $DIR/large_futures.rs:29:5 | LL | calls_fut(fut).await; | ^^^^^^^^^^^^^^ help: consider `Box::pin` on it: `Box::pin(calls_fut(fut))` error: large future with a size of 65540 bytes - --> $DIR/large_futures.rs:40:5 + --> $DIR/large_futures.rs:41:5 | LL | / async { LL | | let x = [0i32; 1024 * 16]; @@ -56,7 +56,7 @@ LL + }) | error: large future with a size of 65540 bytes - --> $DIR/large_futures.rs:51:13 + --> $DIR/large_futures.rs:52:13 | LL | / async { LL | | let x = [0i32; 1024 * 16]; diff --git a/tests/ui/never_loop.rs b/tests/ui/never_loop.rs index 29821ff96fc..eb179f30e75 100644 --- a/tests/ui/never_loop.rs +++ b/tests/ui/never_loop.rs @@ -1,4 +1,6 @@ +#![feature(inline_const)] #![allow( + clippy::eq_op, clippy::single_match, unused_assignments, unused_variables, @@ -295,6 +297,42 @@ pub fn test24() { } } +// Do not lint, we can evaluate `true` to always succeed thus can short-circuit before the `return` +pub fn test25() { + loop { + 'label: { + if const { true } { + break 'label; + } + return; + } + } +} + +pub fn test26() { + loop { + 'label: { + if 1 == 1 { + break 'label; + } + return; + } + } +} + +pub fn test27() { + loop { + 'label: { + let x = true; + // Lints because we cannot prove it's always `true` + if x { + break 'label; + } + return; + } + } +} + fn main() { test1(); test2(); diff --git a/tests/ui/never_loop.stderr b/tests/ui/never_loop.stderr index b2eafb345e3..0446c09cd5b 100644 --- a/tests/ui/never_loop.stderr +++ b/tests/ui/never_loop.stderr @@ -1,5 +1,5 @@ error: this loop never actually loops - --> $DIR/never_loop.rs:10:5 + --> $DIR/never_loop.rs:12:5 | LL | / loop { LL | | // clippy::never_loop @@ -13,7 +13,7 @@ LL | | } = note: `#[deny(clippy::never_loop)]` on by default error: this loop never actually loops - --> $DIR/never_loop.rs:32:5 + --> $DIR/never_loop.rs:34:5 | LL | / loop { LL | | // never loops @@ -23,7 +23,7 @@ LL | | } | |_____^ error: this loop never actually loops - --> $DIR/never_loop.rs:52:5 + --> $DIR/never_loop.rs:54:5 | LL | / loop { LL | | // never loops @@ -35,7 +35,7 @@ LL | | } | |_____^ error: this loop never actually loops - --> $DIR/never_loop.rs:54:9 + --> $DIR/never_loop.rs:56:9 | LL | / while i == 0 { LL | | // never loops @@ -44,7 +44,7 @@ LL | | } | |_________^ error: this loop never actually loops - --> $DIR/never_loop.rs:66:9 + --> $DIR/never_loop.rs:68:9 | LL | / loop { LL | | // never loops @@ -56,7 +56,7 @@ LL | | } | |_________^ error: this loop never actually loops - --> $DIR/never_loop.rs:102:5 + --> $DIR/never_loop.rs:104:5 | LL | / while let Some(y) = x { LL | | // never loops @@ -65,7 +65,7 @@ LL | | } | |_____^ error: this loop never actually loops - --> $DIR/never_loop.rs:109:5 + --> $DIR/never_loop.rs:111:5 | LL | / for x in 0..10 { LL | | // never loops @@ -82,7 +82,7 @@ LL | if let Some(x) = (0..10).next() { | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: this loop never actually loops - --> $DIR/never_loop.rs:157:5 + --> $DIR/never_loop.rs:159:5 | LL | / 'outer: while a { LL | | // never loops @@ -94,7 +94,7 @@ LL | | } | |_____^ error: this loop never actually loops - --> $DIR/never_loop.rs:172:9 + --> $DIR/never_loop.rs:174:9 | LL | / while false { LL | | break 'label; @@ -102,7 +102,7 @@ LL | | } | |_________^ error: this loop never actually loops - --> $DIR/never_loop.rs:223:13 + --> $DIR/never_loop.rs:225:13 | LL | let _ = loop { | _____________^ @@ -115,7 +115,7 @@ LL | | }; | |_____^ error: this loop never actually loops - --> $DIR/never_loop.rs:244:5 + --> $DIR/never_loop.rs:246:5 | LL | / 'a: loop { LL | | 'b: { @@ -127,7 +127,7 @@ LL | | } | |_____^ error: sub-expression diverges - --> $DIR/never_loop.rs:247:17 + --> $DIR/never_loop.rs:249:17 | LL | break 'a; | ^^^^^^^^ @@ -135,7 +135,7 @@ LL | break 'a; = note: `-D clippy::diverging-sub-expression` implied by `-D warnings` error: this loop never actually loops - --> $DIR/never_loop.rs:278:13 + --> $DIR/never_loop.rs:280:13 | LL | / for _ in 0..20 { LL | | break 'block; @@ -147,5 +147,17 @@ help: if you need the first element of the iterator, try writing LL | if let Some(_) = (0..20).next() { | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -error: aborting due to 13 previous errors +error: this loop never actually loops + --> $DIR/never_loop.rs:324:5 + | +LL | / loop { +LL | | 'label: { +LL | | let x = true; +LL | | // Lints because we cannot prove it's always `true` +... | +LL | | } +LL | | } + | |_____^ + +error: aborting due to 14 previous errors