]> git.proxmox.com Git - rustc.git/blob - src/tools/clippy/clippy_lints/src/loops/never_loop.rs
New upstream version 1.66.0+dfsg1
[rustc.git] / src / tools / clippy / clippy_lints / src / loops / never_loop.rs
1 use super::utils::make_iterator_snippet;
2 use super::NEVER_LOOP;
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;
9 use rustc_span::Span;
10 use std::iter::{once, Iterator};
11
12 pub(super) fn check(
13 cx: &LateContext<'_>,
14 block: &Block<'_>,
15 loop_id: HirId,
16 span: Span,
17 for_loop: Option<&ForLoop<'_>>,
18 ) {
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| {
22 if let Some(ForLoop {
23 arg: iterator,
24 pat,
25 span: for_span,
26 ..
27 }) = for_loop
28 {
29 // Suggests using an `if let` instead. This is `Unspecified` because the
30 // loop may (probably) contain `break` statements which would be invalid
31 // in an `if let`.
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,
37 );
38 }
39 });
40 },
41 NeverLoopResult::MayContinueMainLoop | NeverLoopResult::Otherwise => (),
42 }
43 }
44
45 #[derive(Copy, Clone)]
46 enum NeverLoopResult {
47 // A break/return always get triggered but not necessarily for the main loop.
48 AlwaysBreak,
49 // A continue may occur for the main loop.
50 MayContinueMainLoop,
51 Otherwise,
52 }
53
54 #[must_use]
55 fn absorb_break(arg: NeverLoopResult) -> NeverLoopResult {
56 match arg {
57 NeverLoopResult::AlwaysBreak | NeverLoopResult::Otherwise => NeverLoopResult::Otherwise,
58 NeverLoopResult::MayContinueMainLoop => NeverLoopResult::MayContinueMainLoop,
59 }
60 }
61
62 // Combine two results for parts that are called in order.
63 #[must_use]
64 fn combine_seq(first: NeverLoopResult, second: NeverLoopResult) -> NeverLoopResult {
65 match first {
66 NeverLoopResult::AlwaysBreak | NeverLoopResult::MayContinueMainLoop => first,
67 NeverLoopResult::Otherwise => second,
68 }
69 }
70
71 // Combine two results where both parts are called but not necessarily in order.
72 #[must_use]
73 fn combine_both(left: NeverLoopResult, right: NeverLoopResult) -> NeverLoopResult {
74 match (left, right) {
75 (NeverLoopResult::MayContinueMainLoop, _) | (_, NeverLoopResult::MayContinueMainLoop) => {
76 NeverLoopResult::MayContinueMainLoop
77 },
78 (NeverLoopResult::AlwaysBreak, _) | (_, NeverLoopResult::AlwaysBreak) => NeverLoopResult::AlwaysBreak,
79 (NeverLoopResult::Otherwise, NeverLoopResult::Otherwise) => NeverLoopResult::Otherwise,
80 }
81 }
82
83 // Combine two results where only one of the part may have been executed.
84 #[must_use]
85 fn combine_branches(b1: NeverLoopResult, b2: NeverLoopResult) -> NeverLoopResult {
86 match (b1, b2) {
87 (NeverLoopResult::AlwaysBreak, NeverLoopResult::AlwaysBreak) => NeverLoopResult::AlwaysBreak,
88 (NeverLoopResult::MayContinueMainLoop, _) | (_, NeverLoopResult::MayContinueMainLoop) => {
89 NeverLoopResult::MayContinueMainLoop
90 },
91 (NeverLoopResult::Otherwise, _) | (_, NeverLoopResult::Otherwise) => NeverLoopResult::Otherwise,
92 }
93 }
94
95 fn never_loop_block(block: &Block<'_>, main_loop_id: HirId) -> NeverLoopResult {
96 let mut iter = block
97 .stmts
98 .iter()
99 .filter_map(stmt_to_expr)
100 .chain(block.expr.map(|expr| (expr, None)));
101 never_loop_expr_seq(&mut iter, main_loop_id)
102 }
103
104 fn never_loop_expr_seq<'a, T: Iterator<Item = (&'a Expr<'a>, Option<&'a Block<'a>>)>>(
105 es: &mut T,
106 main_loop_id: HirId,
107 ) -> NeverLoopResult {
108 es.map(|(e, els)| {
109 let e = never_loop_expr(e, main_loop_id);
110 els.map_or(e, |els| combine_branches(e, never_loop_block(els, main_loop_id)))
111 })
112 .fold(NeverLoopResult::Otherwise, combine_seq)
113 }
114
115 fn stmt_to_expr<'tcx>(stmt: &Stmt<'tcx>) -> Option<(&'tcx Expr<'tcx>, Option<&'tcx Block<'tcx>>)> {
116 match stmt.kind {
117 StmtKind::Semi(e, ..) | StmtKind::Expr(e, ..) => Some((e, None)),
118 StmtKind::Local(local) => local.init.map(|init| (init, local.els)),
119 StmtKind::Item(..) => None,
120 }
121 }
122
123 fn never_loop_expr(expr: &Expr<'_>, main_loop_id: HirId) -> NeverLoopResult {
124 match expr.kind {
125 ExprKind::Box(e)
126 | ExprKind::Unary(_, e)
127 | ExprKind::Cast(e, _)
128 | ExprKind::Type(e, _)
129 | ExprKind::Field(e, _)
130 | ExprKind::AddrOf(_, _, e)
131 | ExprKind::Repeat(e, _)
132 | ExprKind::DropTemps(e) => never_loop_expr(e, main_loop_id),
133 ExprKind::Let(let_expr) => never_loop_expr(let_expr.init, main_loop_id),
134 ExprKind::Array(es) | ExprKind::Tup(es) => never_loop_expr_all(&mut es.iter(), main_loop_id),
135 ExprKind::MethodCall(_, receiver, es, _) => {
136 never_loop_expr_all(&mut std::iter::once(receiver).chain(es.iter()), main_loop_id)
137 },
138 ExprKind::Struct(_, fields, base) => {
139 let fields = never_loop_expr_all(&mut fields.iter().map(|f| f.expr), main_loop_id);
140 if let Some(base) = base {
141 combine_both(fields, never_loop_expr(base, main_loop_id))
142 } else {
143 fields
144 }
145 },
146 ExprKind::Call(e, es) => never_loop_expr_all(&mut once(e).chain(es.iter()), main_loop_id),
147 ExprKind::Binary(_, e1, e2)
148 | ExprKind::Assign(e1, e2, _)
149 | ExprKind::AssignOp(_, e1, e2)
150 | ExprKind::Index(e1, e2) => never_loop_expr_all(&mut [e1, e2].iter().copied(), main_loop_id),
151 ExprKind::Loop(b, _, _, _) => {
152 // Break can come from the inner loop so remove them.
153 absorb_break(never_loop_block(b, main_loop_id))
154 },
155 ExprKind::If(e, e2, e3) => {
156 let e1 = never_loop_expr(e, main_loop_id);
157 let e2 = never_loop_expr(e2, main_loop_id);
158 let e3 = e3
159 .as_ref()
160 .map_or(NeverLoopResult::Otherwise, |e| never_loop_expr(e, main_loop_id));
161 combine_seq(e1, combine_branches(e2, e3))
162 },
163 ExprKind::Match(e, arms, _) => {
164 let e = never_loop_expr(e, main_loop_id);
165 if arms.is_empty() {
166 e
167 } else {
168 let arms = never_loop_expr_branch(&mut arms.iter().map(|a| a.body), main_loop_id);
169 combine_seq(e, arms)
170 }
171 },
172 ExprKind::Block(b, _) => never_loop_block(b, main_loop_id),
173 ExprKind::Continue(d) => {
174 let id = d
175 .target_id
176 .expect("target ID can only be missing in the presence of compilation errors");
177 if id == main_loop_id {
178 NeverLoopResult::MayContinueMainLoop
179 } else {
180 NeverLoopResult::AlwaysBreak
181 }
182 },
183 ExprKind::Break(_, e) | ExprKind::Ret(e) => e.as_ref().map_or(NeverLoopResult::AlwaysBreak, |e| {
184 combine_seq(never_loop_expr(e, main_loop_id), NeverLoopResult::AlwaysBreak)
185 }),
186 ExprKind::InlineAsm(asm) => asm
187 .operands
188 .iter()
189 .map(|(o, _)| match o {
190 InlineAsmOperand::In { expr, .. } | InlineAsmOperand::InOut { expr, .. } => {
191 never_loop_expr(expr, main_loop_id)
192 },
193 InlineAsmOperand::Out { expr, .. } => never_loop_expr_all(&mut expr.iter().copied(), main_loop_id),
194 InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => {
195 never_loop_expr_all(&mut once(*in_expr).chain(out_expr.iter().copied()), main_loop_id)
196 },
197 InlineAsmOperand::Const { .. }
198 | InlineAsmOperand::SymFn { .. }
199 | InlineAsmOperand::SymStatic { .. } => NeverLoopResult::Otherwise,
200 })
201 .fold(NeverLoopResult::Otherwise, combine_both),
202 ExprKind::Yield(_, _)
203 | ExprKind::Closure { .. }
204 | ExprKind::Path(_)
205 | ExprKind::ConstBlock(_)
206 | ExprKind::Lit(_)
207 | ExprKind::Err => NeverLoopResult::Otherwise,
208 }
209 }
210
211 fn never_loop_expr_all<'a, T: Iterator<Item = &'a Expr<'a>>>(es: &mut T, main_loop_id: HirId) -> NeverLoopResult {
212 es.map(|e| never_loop_expr(e, main_loop_id))
213 .fold(NeverLoopResult::Otherwise, combine_both)
214 }
215
216 fn never_loop_expr_branch<'a, T: Iterator<Item = &'a Expr<'a>>>(e: &mut T, main_loop_id: HirId) -> NeverLoopResult {
217 e.map(|e| never_loop_expr(e, main_loop_id))
218 .fold(NeverLoopResult::AlwaysBreak, combine_branches)
219 }
220
221 fn for_to_if_let_sugg(cx: &LateContext<'_>, iterator: &Expr<'_>, pat: &Pat<'_>) -> String {
222 let pat_snippet = snippet(cx, pat.span, "_");
223 let iter_snippet = make_iterator_snippet(cx, iterator, &mut Applicability::Unspecified);
224
225 format!("if let Some({pat_snippet}) = {iter_snippet}.next()")
226 }