From 6a1084c26fdd9653c0b8940bc5d3894ec9bb7d9d Mon Sep 17 00:00:00 2001
From: Catherine <114838443+Centri3@users.noreply.github.com>
Date: Thu, 22 Jun 2023 04:31:05 -0500
Subject: [PATCH] Check if `if` conditions always evaluate to true in
 `never_loop`

---
 clippy_lints/src/loops/never_loop.rs | 82 +++++++++++++++++-----------
 clippy_utils/src/consts.rs           |  4 +-
 tests/ui/large_futures.rs            |  1 +
 tests/ui/large_futures.stderr        | 16 +++---
 tests/ui/never_loop.rs               | 38 +++++++++++++
 tests/ui/never_loop.stderr           | 40 +++++++++-----
 6 files changed, 127 insertions(+), 54 deletions(-)

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<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)
     })
 }
 
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<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) => {
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