1 use super::utils::make_iterator_snippet;
3 use clippy_utils::diagnostics::span_lint_and_then;
4 use clippy_utils::higher::ForLoop;
5 use clippy_utils::source::snippet;
6 use rustc_errors::Applicability;
7 use rustc_hir::{Block, Expr, ExprKind, HirId, InlineAsmOperand, Pat, Stmt, StmtKind};
8 use rustc_lint::LateContext;
10 use std::iter::{once, Iterator};
17 for_loop: Option<&ForLoop<'_>>,
19 match never_loop_block(block, loop_id) {
20 NeverLoopResult::AlwaysBreak => {
21 span_lint_and_then(cx, NEVER_LOOP, span, "this loop never actually loops", |diag| {
29 // Suggests using an `if let` instead. This is `Unspecified` because the
30 // loop may (probably) contain `break` statements which would be invalid
32 diag.span_suggestion_verbose(
33 for_span.with_hi(iterator.span.hi()),
34 "if you need the first element of the iterator, try writing",
35 for_to_if_let_sugg(cx, iterator, pat),
36 Applicability::Unspecified,
41 NeverLoopResult::MayContinueMainLoop | NeverLoopResult::Otherwise => (),
45 enum NeverLoopResult {
46 // A break/return always get triggered but not necessarily for the main loop.
48 // A continue may occur for the main loop.
54 fn absorb_break(arg: &NeverLoopResult) -> NeverLoopResult {
56 NeverLoopResult::AlwaysBreak | NeverLoopResult::Otherwise => NeverLoopResult::Otherwise,
57 NeverLoopResult::MayContinueMainLoop => NeverLoopResult::MayContinueMainLoop,
61 // Combine two results for parts that are called in order.
63 fn combine_seq(first: NeverLoopResult, second: NeverLoopResult) -> NeverLoopResult {
65 NeverLoopResult::AlwaysBreak | NeverLoopResult::MayContinueMainLoop => first,
66 NeverLoopResult::Otherwise => second,
70 // Combine two results where both parts are called but not necessarily in order.
72 fn combine_both(left: NeverLoopResult, right: NeverLoopResult) -> NeverLoopResult {
74 (NeverLoopResult::MayContinueMainLoop, _) | (_, NeverLoopResult::MayContinueMainLoop) => {
75 NeverLoopResult::MayContinueMainLoop
77 (NeverLoopResult::AlwaysBreak, _) | (_, NeverLoopResult::AlwaysBreak) => NeverLoopResult::AlwaysBreak,
78 (NeverLoopResult::Otherwise, NeverLoopResult::Otherwise) => NeverLoopResult::Otherwise,
82 // Combine two results where only one of the part may have been executed.
84 fn combine_branches(b1: NeverLoopResult, b2: NeverLoopResult) -> NeverLoopResult {
86 (NeverLoopResult::AlwaysBreak, NeverLoopResult::AlwaysBreak) => NeverLoopResult::AlwaysBreak,
87 (NeverLoopResult::MayContinueMainLoop, _) | (_, NeverLoopResult::MayContinueMainLoop) => {
88 NeverLoopResult::MayContinueMainLoop
90 (NeverLoopResult::Otherwise, _) | (_, NeverLoopResult::Otherwise) => NeverLoopResult::Otherwise,
94 fn never_loop_block(block: &Block<'_>, main_loop_id: HirId) -> NeverLoopResult {
95 let mut iter = block.stmts.iter().filter_map(stmt_to_expr).chain(block.expr);
96 never_loop_expr_seq(&mut iter, main_loop_id)
99 fn never_loop_expr_seq<'a, T: Iterator<Item = &'a Expr<'a>>>(es: &mut T, main_loop_id: HirId) -> NeverLoopResult {
100 es.map(|e| never_loop_expr(e, main_loop_id))
101 .fold(NeverLoopResult::Otherwise, combine_seq)
104 fn stmt_to_expr<'tcx>(stmt: &Stmt<'tcx>) -> Option<&'tcx Expr<'tcx>> {
106 StmtKind::Semi(e, ..) | StmtKind::Expr(e, ..) => Some(e),
107 StmtKind::Local(local) => local.init,
108 StmtKind::Item(..) => None,
112 fn never_loop_expr(expr: &Expr<'_>, main_loop_id: HirId) -> NeverLoopResult {
115 | ExprKind::Unary(_, e)
116 | ExprKind::Cast(e, _)
117 | ExprKind::Type(e, _)
118 | ExprKind::Field(e, _)
119 | ExprKind::AddrOf(_, _, e)
120 | ExprKind::Struct(_, _, Some(e))
121 | ExprKind::Repeat(e, _)
122 | ExprKind::DropTemps(e) => never_loop_expr(e, main_loop_id),
123 ExprKind::Let(let_expr) => never_loop_expr(let_expr.init, main_loop_id),
124 ExprKind::Array(es) | ExprKind::MethodCall(_, _, es, _) | ExprKind::Tup(es) => {
125 never_loop_expr_all(&mut es.iter(), main_loop_id)
127 ExprKind::Call(e, es) => never_loop_expr_all(&mut once(e).chain(es.iter()), main_loop_id),
128 ExprKind::Binary(_, e1, e2)
129 | ExprKind::Assign(e1, e2, _)
130 | ExprKind::AssignOp(_, e1, e2)
131 | ExprKind::Index(e1, e2) => never_loop_expr_all(&mut [e1, e2].iter().copied(), main_loop_id),
132 ExprKind::Loop(b, _, _, _) => {
133 // Break can come from the inner loop so remove them.
134 absorb_break(&never_loop_block(b, main_loop_id))
136 ExprKind::If(e, e2, e3) => {
137 let e1 = never_loop_expr(e, main_loop_id);
138 let e2 = never_loop_expr(e2, main_loop_id);
141 .map_or(NeverLoopResult::Otherwise, |e| never_loop_expr(e, main_loop_id));
142 combine_seq(e1, combine_branches(e2, e3))
144 ExprKind::Match(e, arms, _) => {
145 let e = never_loop_expr(e, main_loop_id);
149 let arms = never_loop_expr_branch(&mut arms.iter().map(|a| &*a.body), main_loop_id);
153 ExprKind::Block(b, _) => never_loop_block(b, main_loop_id),
154 ExprKind::Continue(d) => {
157 .expect("target ID can only be missing in the presence of compilation errors");
158 if id == main_loop_id {
159 NeverLoopResult::MayContinueMainLoop
161 NeverLoopResult::AlwaysBreak
164 ExprKind::Break(_, e) | ExprKind::Ret(e) => e.as_ref().map_or(NeverLoopResult::AlwaysBreak, |e| {
165 combine_seq(never_loop_expr(e, main_loop_id), NeverLoopResult::AlwaysBreak)
167 ExprKind::InlineAsm(asm) => asm
170 .map(|(o, _)| match o {
171 InlineAsmOperand::In { expr, .. }
172 | InlineAsmOperand::InOut { expr, .. }
173 | InlineAsmOperand::Sym { expr } => never_loop_expr(expr, main_loop_id),
174 InlineAsmOperand::Out { expr, .. } => never_loop_expr_all(&mut expr.iter(), main_loop_id),
175 InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => {
176 never_loop_expr_all(&mut once(in_expr).chain(out_expr.iter()), main_loop_id)
178 InlineAsmOperand::Const { .. } => NeverLoopResult::Otherwise,
180 .fold(NeverLoopResult::Otherwise, combine_both),
181 ExprKind::Struct(_, _, None)
182 | ExprKind::Yield(_, _)
183 | ExprKind::Closure(_, _, _, _, _)
184 | ExprKind::LlvmInlineAsm(_)
186 | ExprKind::ConstBlock(_)
188 | ExprKind::Err => NeverLoopResult::Otherwise,
192 fn never_loop_expr_all<'a, T: Iterator<Item = &'a Expr<'a>>>(es: &mut T, main_loop_id: HirId) -> NeverLoopResult {
193 es.map(|e| never_loop_expr(e, main_loop_id))
194 .fold(NeverLoopResult::Otherwise, combine_both)
197 fn never_loop_expr_branch<'a, T: Iterator<Item = &'a Expr<'a>>>(e: &mut T, main_loop_id: HirId) -> NeverLoopResult {
198 e.map(|e| never_loop_expr(e, main_loop_id))
199 .fold(NeverLoopResult::AlwaysBreak, combine_branches)
202 fn for_to_if_let_sugg(cx: &LateContext<'_>, iterator: &Expr<'_>, pat: &Pat<'_>) -> String {
203 let pat_snippet = snippet(cx, pat.span, "_");
204 let iter_snippet = make_iterator_snippet(cx, iterator, &mut Applicability::Unspecified);
207 "if let Some({pat}) = {iter}.next()",