Skip to content

Commit 407bfd4

Browse files
committed
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
2 parents 78e36d9 + 6a1084c commit 407bfd4

File tree

6 files changed

+127
-54
lines changed

6 files changed

+127
-54
lines changed

clippy_lints/src/loops/never_loop.rs

Lines changed: 51 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
11
use super::utils::make_iterator_snippet;
22
use super::NEVER_LOOP;
3-
use clippy_utils::diagnostics::span_lint_and_then;
3+
use clippy_utils::consts::constant;
44
use clippy_utils::higher::ForLoop;
55
use clippy_utils::source::snippet;
6+
use clippy_utils::{consts::Constant, diagnostics::span_lint_and_then};
67
use rustc_errors::Applicability;
78
use rustc_hir::{Block, Destination, Expr, ExprKind, HirId, InlineAsmOperand, Pat, Stmt, StmtKind};
89
use rustc_lint::LateContext;
910
use rustc_span::Span;
1011
use std::iter::{once, Iterator};
1112

12-
pub(super) fn check(
13-
cx: &LateContext<'_>,
14-
block: &Block<'_>,
13+
pub(super) fn check<'tcx>(
14+
cx: &LateContext<'tcx>,
15+
block: &Block<'tcx>,
1516
loop_id: HirId,
1617
span: Span,
1718
for_loop: Option<&ForLoop<'_>>,
1819
) {
19-
match never_loop_block(block, &mut Vec::new(), loop_id) {
20+
match never_loop_block(cx, block, &mut Vec::new(), loop_id) {
2021
NeverLoopResult::AlwaysBreak => {
2122
span_lint_and_then(cx, NEVER_LOOP, span, "this loop never actually loops", |diag| {
2223
if let Some(ForLoop {
@@ -95,18 +96,23 @@ fn combine_branches(b1: NeverLoopResult, b2: NeverLoopResult, ignore_ids: &[HirI
9596
}
9697
}
9798

98-
fn never_loop_block(block: &Block<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id: HirId) -> NeverLoopResult {
99+
fn never_loop_block<'tcx>(
100+
cx: &LateContext<'tcx>,
101+
block: &Block<'tcx>,
102+
ignore_ids: &mut Vec<HirId>,
103+
main_loop_id: HirId,
104+
) -> NeverLoopResult {
99105
let iter = block
100106
.stmts
101107
.iter()
102108
.filter_map(stmt_to_expr)
103109
.chain(block.expr.map(|expr| (expr, None)));
104110

105111
iter.map(|(e, els)| {
106-
let e = never_loop_expr(e, ignore_ids, main_loop_id);
112+
let e = never_loop_expr(cx, e, ignore_ids, main_loop_id);
107113
// els is an else block in a let...else binding
108114
els.map_or(e, |els| {
109-
combine_branches(e, never_loop_block(els, ignore_ids, main_loop_id), ignore_ids)
115+
combine_branches(e, never_loop_block(cx, els, ignore_ids, main_loop_id), ignore_ids)
110116
})
111117
})
112118
.fold(NeverLoopResult::Otherwise, combine_seq)
@@ -122,61 +128,72 @@ fn stmt_to_expr<'tcx>(stmt: &Stmt<'tcx>) -> Option<(&'tcx Expr<'tcx>, Option<&'t
122128
}
123129

124130
#[allow(clippy::too_many_lines)]
125-
fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id: HirId) -> NeverLoopResult {
131+
fn never_loop_expr<'tcx>(
132+
cx: &LateContext<'tcx>,
133+
expr: &Expr<'tcx>,
134+
ignore_ids: &mut Vec<HirId>,
135+
main_loop_id: HirId,
136+
) -> NeverLoopResult {
126137
match expr.kind {
127138
ExprKind::Unary(_, e)
128139
| ExprKind::Cast(e, _)
129140
| ExprKind::Type(e, _)
130141
| ExprKind::Field(e, _)
131142
| ExprKind::AddrOf(_, _, e)
132143
| ExprKind::Repeat(e, _)
133-
| ExprKind::DropTemps(e) => never_loop_expr(e, ignore_ids, main_loop_id),
134-
ExprKind::Let(let_expr) => never_loop_expr(let_expr.init, ignore_ids, main_loop_id),
135-
ExprKind::Array(es) | ExprKind::Tup(es) => never_loop_expr_all(&mut es.iter(), ignore_ids, main_loop_id),
144+
| ExprKind::DropTemps(e) => never_loop_expr(cx, e, ignore_ids, main_loop_id),
145+
ExprKind::Let(let_expr) => never_loop_expr(cx, let_expr.init, ignore_ids, main_loop_id),
146+
ExprKind::Array(es) | ExprKind::Tup(es) => never_loop_expr_all(cx, &mut es.iter(), ignore_ids, main_loop_id),
136147
ExprKind::MethodCall(_, receiver, es, _) => never_loop_expr_all(
148+
cx,
137149
&mut std::iter::once(receiver).chain(es.iter()),
138150
ignore_ids,
139151
main_loop_id,
140152
),
141153
ExprKind::Struct(_, fields, base) => {
142-
let fields = never_loop_expr_all(&mut fields.iter().map(|f| f.expr), ignore_ids, main_loop_id);
154+
let fields = never_loop_expr_all(cx, &mut fields.iter().map(|f| f.expr), ignore_ids, main_loop_id);
143155
if let Some(base) = base {
144-
combine_seq(fields, never_loop_expr(base, ignore_ids, main_loop_id))
156+
combine_seq(fields, never_loop_expr(cx, base, ignore_ids, main_loop_id))
145157
} else {
146158
fields
147159
}
148160
},
149-
ExprKind::Call(e, es) => never_loop_expr_all(&mut once(e).chain(es.iter()), ignore_ids, main_loop_id),
161+
ExprKind::Call(e, es) => never_loop_expr_all(cx, &mut once(e).chain(es.iter()), ignore_ids, main_loop_id),
150162
ExprKind::Binary(_, e1, e2)
151163
| ExprKind::Assign(e1, e2, _)
152164
| ExprKind::AssignOp(_, e1, e2)
153-
| ExprKind::Index(e1, e2) => never_loop_expr_all(&mut [e1, e2].iter().copied(), ignore_ids, main_loop_id),
165+
| ExprKind::Index(e1, e2) => never_loop_expr_all(cx, &mut [e1, e2].iter().copied(), ignore_ids, main_loop_id),
154166
ExprKind::Loop(b, _, _, _) => {
155167
// Break can come from the inner loop so remove them.
156-
absorb_break(never_loop_block(b, ignore_ids, main_loop_id))
168+
absorb_break(never_loop_block(cx, b, ignore_ids, main_loop_id))
157169
},
158170
ExprKind::If(e, e2, e3) => {
159-
let e1 = never_loop_expr(e, ignore_ids, main_loop_id);
160-
let e2 = never_loop_expr(e2, ignore_ids, main_loop_id);
171+
let e1 = never_loop_expr(cx, e, ignore_ids, main_loop_id);
172+
let e2 = never_loop_expr(cx, e2, ignore_ids, main_loop_id);
173+
// If we know the `if` condition evaluates to `true`, don't check everything past it; it
174+
// should just return whatever's evaluated for `e1` and `e2` since `e3` is unreachable
175+
if let Some(Constant::Bool(true)) = constant(cx, cx.typeck_results(), e) {
176+
return combine_seq(e1, e2);
177+
}
161178
let e3 = e3.as_ref().map_or(NeverLoopResult::Otherwise, |e| {
162-
never_loop_expr(e, ignore_ids, main_loop_id)
179+
never_loop_expr(cx, e, ignore_ids, main_loop_id)
163180
});
164181
combine_seq(e1, combine_branches(e2, e3, ignore_ids))
165182
},
166183
ExprKind::Match(e, arms, _) => {
167-
let e = never_loop_expr(e, ignore_ids, main_loop_id);
184+
let e = never_loop_expr(cx, e, ignore_ids, main_loop_id);
168185
if arms.is_empty() {
169186
e
170187
} else {
171-
let arms = never_loop_expr_branch(&mut arms.iter().map(|a| a.body), ignore_ids, main_loop_id);
188+
let arms = never_loop_expr_branch(cx, &mut arms.iter().map(|a| a.body), ignore_ids, main_loop_id);
172189
combine_seq(e, arms)
173190
}
174191
},
175192
ExprKind::Block(b, l) => {
176193
if l.is_some() {
177194
ignore_ids.push(b.hir_id);
178195
}
179-
let ret = never_loop_block(b, ignore_ids, main_loop_id);
196+
let ret = never_loop_block(cx, b, ignore_ids, main_loop_id);
180197
if l.is_some() {
181198
ignore_ids.pop();
182199
}
@@ -198,11 +215,11 @@ fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id: H
198215
// checks if break targets a block instead of a loop
199216
ExprKind::Break(Destination { target_id: Ok(t), .. }, e) if ignore_ids.contains(&t) => e
200217
.map_or(NeverLoopResult::IgnoreUntilEnd(t), |e| {
201-
never_loop_expr(e, ignore_ids, main_loop_id)
218+
never_loop_expr(cx, e, ignore_ids, main_loop_id)
202219
}),
203220
ExprKind::Break(_, e) | ExprKind::Ret(e) => e.as_ref().map_or(NeverLoopResult::AlwaysBreak, |e| {
204221
combine_seq(
205-
never_loop_expr(e, ignore_ids, main_loop_id),
222+
never_loop_expr(cx, e, ignore_ids, main_loop_id),
206223
NeverLoopResult::AlwaysBreak,
207224
)
208225
}),
@@ -211,12 +228,13 @@ fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id: H
211228
.iter()
212229
.map(|(o, _)| match o {
213230
InlineAsmOperand::In { expr, .. } | InlineAsmOperand::InOut { expr, .. } => {
214-
never_loop_expr(expr, ignore_ids, main_loop_id)
231+
never_loop_expr(cx, expr, ignore_ids, main_loop_id)
215232
},
216233
InlineAsmOperand::Out { expr, .. } => {
217-
never_loop_expr_all(&mut expr.iter().copied(), ignore_ids, main_loop_id)
234+
never_loop_expr_all(cx, &mut expr.iter().copied(), ignore_ids, main_loop_id)
218235
},
219236
InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => never_loop_expr_all(
237+
cx,
220238
&mut once(*in_expr).chain(out_expr.iter().copied()),
221239
ignore_ids,
222240
main_loop_id,
@@ -236,22 +254,24 @@ fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id: H
236254
}
237255
}
238256

239-
fn never_loop_expr_all<'a, T: Iterator<Item = &'a Expr<'a>>>(
257+
fn never_loop_expr_all<'tcx, T: Iterator<Item = &'tcx Expr<'tcx>>>(
258+
cx: &LateContext<'tcx>,
240259
es: &mut T,
241260
ignore_ids: &mut Vec<HirId>,
242261
main_loop_id: HirId,
243262
) -> NeverLoopResult {
244-
es.map(|e| never_loop_expr(e, ignore_ids, main_loop_id))
263+
es.map(|e| never_loop_expr(cx, e, ignore_ids, main_loop_id))
245264
.fold(NeverLoopResult::Otherwise, combine_seq)
246265
}
247266

248-
fn never_loop_expr_branch<'a, T: Iterator<Item = &'a Expr<'a>>>(
267+
fn never_loop_expr_branch<'tcx, T: Iterator<Item = &'tcx Expr<'tcx>>>(
268+
cx: &LateContext<'tcx>,
249269
e: &mut T,
250270
ignore_ids: &mut Vec<HirId>,
251271
main_loop_id: HirId,
252272
) -> NeverLoopResult {
253273
e.fold(NeverLoopResult::AlwaysBreak, |a, b| {
254-
combine_branches(a, never_loop_expr(b, ignore_ids, main_loop_id), ignore_ids)
274+
combine_branches(a, never_loop_expr(cx, b, ignore_ids, main_loop_id), ignore_ids)
255275
})
256276
}
257277

clippy_utils/src/consts.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use if_chain::if_chain;
66
use rustc_ast::ast::{self, LitFloatType, LitKind};
77
use rustc_data_structures::sync::Lrc;
88
use rustc_hir::def::{DefKind, Res};
9-
use rustc_hir::{BinOp, BinOpKind, Block, Expr, ExprKind, HirId, Item, ItemKind, Node, QPath, UnOp};
9+
use rustc_hir::{AnonConst, BinOp, BinOpKind, Block, Expr, ExprKind, HirId, Item, ItemKind, Node, QPath, UnOp};
1010
use rustc_lexer::tokenize;
1111
use rustc_lint::LateContext;
1212
use rustc_middle::mir;
@@ -344,6 +344,8 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> {
344344
/// Simple constant folding: Insert an expression, get a constant or none.
345345
pub fn expr(&mut self, e: &Expr<'_>) -> Option<Constant<'tcx>> {
346346
match e.kind {
347+
ExprKind::ConstBlock(AnonConst { body, .. }) => self.expr(self.lcx.tcx.hir().body(body).value),
348+
ExprKind::DropTemps(e) => self.expr(e),
347349
ExprKind::Path(ref qpath) => self.fetch_path(qpath, e.hir_id, self.typeck_results.expr_ty(e)),
348350
ExprKind::Block(block, _) => self.block(block),
349351
ExprKind::Lit(lit) => {

tests/ui/large_futures.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#![feature(generators)]
22
#![warn(clippy::large_futures)]
3+
#![allow(clippy::never_loop)]
34
#![allow(clippy::future_not_send)]
45
#![allow(clippy::manual_async_fn)]
56

tests/ui/large_futures.stderr

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,43 @@
11
error: large future with a size of 16385 bytes
2-
--> $DIR/large_futures.rs:10:9
2+
--> $DIR/large_futures.rs:11:9
33
|
44
LL | big_fut([0u8; 1024 * 16]).await;
55
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider `Box::pin` on it: `Box::pin(big_fut([0u8; 1024 * 16]))`
66
|
77
= note: `-D clippy::large-futures` implied by `-D warnings`
88

99
error: large future with a size of 16386 bytes
10-
--> $DIR/large_futures.rs:12:5
10+
--> $DIR/large_futures.rs:13:5
1111
|
1212
LL | f.await
1313
| ^ help: consider `Box::pin` on it: `Box::pin(f)`
1414

1515
error: large future with a size of 16387 bytes
16-
--> $DIR/large_futures.rs:16:9
16+
--> $DIR/large_futures.rs:17:9
1717
|
1818
LL | wait().await;
1919
| ^^^^^^ help: consider `Box::pin` on it: `Box::pin(wait())`
2020

2121
error: large future with a size of 16387 bytes
22-
--> $DIR/large_futures.rs:20:13
22+
--> $DIR/large_futures.rs:21:13
2323
|
2424
LL | wait().await;
2525
| ^^^^^^ help: consider `Box::pin` on it: `Box::pin(wait())`
2626

2727
error: large future with a size of 65540 bytes
28-
--> $DIR/large_futures.rs:27:5
28+
--> $DIR/large_futures.rs:28:5
2929
|
3030
LL | foo().await;
3131
| ^^^^^ help: consider `Box::pin` on it: `Box::pin(foo())`
3232

3333
error: large future with a size of 49159 bytes
34-
--> $DIR/large_futures.rs:28:5
34+
--> $DIR/large_futures.rs:29:5
3535
|
3636
LL | calls_fut(fut).await;
3737
| ^^^^^^^^^^^^^^ help: consider `Box::pin` on it: `Box::pin(calls_fut(fut))`
3838

3939
error: large future with a size of 65540 bytes
40-
--> $DIR/large_futures.rs:40:5
40+
--> $DIR/large_futures.rs:41:5
4141
|
4242
LL | / async {
4343
LL | | let x = [0i32; 1024 * 16];
@@ -56,7 +56,7 @@ LL + })
5656
|
5757

5858
error: large future with a size of 65540 bytes
59-
--> $DIR/large_futures.rs:51:13
59+
--> $DIR/large_futures.rs:52:13
6060
|
6161
LL | / async {
6262
LL | | let x = [0i32; 1024 * 16];

tests/ui/never_loop.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
#![feature(inline_const)]
12
#![allow(
3+
clippy::eq_op,
24
clippy::single_match,
35
unused_assignments,
46
unused_variables,
@@ -295,6 +297,42 @@ pub fn test24() {
295297
}
296298
}
297299

300+
// Do not lint, we can evaluate `true` to always succeed thus can short-circuit before the `return`
301+
pub fn test25() {
302+
loop {
303+
'label: {
304+
if const { true } {
305+
break 'label;
306+
}
307+
return;
308+
}
309+
}
310+
}
311+
312+
pub fn test26() {
313+
loop {
314+
'label: {
315+
if 1 == 1 {
316+
break 'label;
317+
}
318+
return;
319+
}
320+
}
321+
}
322+
323+
pub fn test27() {
324+
loop {
325+
'label: {
326+
let x = true;
327+
// Lints because we cannot prove it's always `true`
328+
if x {
329+
break 'label;
330+
}
331+
return;
332+
}
333+
}
334+
}
335+
298336
fn main() {
299337
test1();
300338
test2();

0 commit comments

Comments
 (0)