Auto merge of #11005 - Centri3:never_loop, r=giraffate

Check if `if` conditions always evaluate to true in `never_loop`

This fixes the example provided in #11004, but it shouldn't be closed as this is still an issue on like
```rust
let x = true;
if x { /* etc */ }`
```
This also makes `clippy_utils::consts::constant` handle `ConstBlock` and `DropTemps`.

changelog: [`never_loop`]: Check if `if` conditions always evaluate to true
This commit is contained in:
bors 2023-06-26 00:42:38 +00:00
commit 407bfd483a
6 changed files with 127 additions and 54 deletions

View File

@ -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<HirId>, main_loop_id: HirId) -> NeverLoopResult {
fn never_loop_block<'tcx>(
cx: &LateContext<'tcx>,
block: &Block<'tcx>,
ignore_ids: &mut Vec<HirId>,
main_loop_id: HirId,
) -> NeverLoopResult {
let iter = block
.stmts
.iter()
@ -103,10 +109,10 @@ fn never_loop_block(block: &Block<'_>, ignore_ids: &mut Vec<HirId>, 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<HirId>, main_loop_id: HirId) -> NeverLoopResult {
fn never_loop_expr<'tcx>(
cx: &LateContext<'tcx>,
expr: &Expr<'tcx>,
ignore_ids: &mut Vec<HirId>,
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<HirId>, 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<HirId>, 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<HirId>, 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<HirId>, 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<HirId>, main_loop_id: H
}
}
fn never_loop_expr_all<'a, T: Iterator<Item = &'a Expr<'a>>>(
fn never_loop_expr_all<'tcx, T: Iterator<Item = &'tcx Expr<'tcx>>>(
cx: &LateContext<'tcx>,
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))
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<Item = &'a Expr<'a>>>(
fn never_loop_expr_branch<'tcx, T: Iterator<Item = &'tcx Expr<'tcx>>>(
cx: &LateContext<'tcx>,
e: &mut T,
ignore_ids: &mut Vec<HirId>,
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)
})
}

View File

@ -6,7 +6,7 @@
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 @@ fn new(lcx: &'a LateContext<'tcx>, typeck_results: &'a ty::TypeckResults<'tcx>)
/// Simple constant folding: Insert an expression, get a constant or none.
pub fn expr(&mut self, e: &Expr<'_>) -> Option<Constant<'tcx>> {
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) => {

View File

@ -1,5 +1,6 @@
#![feature(generators)]
#![warn(clippy::large_futures)]
#![allow(clippy::never_loop)]
#![allow(clippy::future_not_send)]
#![allow(clippy::manual_async_fn)]

View File

@ -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];

View File

@ -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();

View File

@ -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