]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/loops/never_loop.rs
Auto merge of #107843 - bjorn3:sync_cg_clif-2023-02-09, r=bjorn3
[rust.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, Destination, 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, &mut Vec::new(), 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<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id: HirId) -> NeverLoopResult {
96     let iter = block
97         .stmts
98         .iter()
99         .filter_map(stmt_to_expr)
100         .chain(block.expr.map(|expr| (expr, None)));
101
102     iter.map(|(e, els)| {
103         let e = never_loop_expr(e, ignore_ids, main_loop_id);
104         // els is an else block in a let...else binding
105         els.map_or(e, |els| {
106             combine_branches(e, never_loop_block(els, ignore_ids, main_loop_id))
107         })
108     })
109     .fold(NeverLoopResult::Otherwise, combine_seq)
110 }
111
112 fn stmt_to_expr<'tcx>(stmt: &Stmt<'tcx>) -> Option<(&'tcx Expr<'tcx>, Option<&'tcx Block<'tcx>>)> {
113     match stmt.kind {
114         StmtKind::Semi(e) | StmtKind::Expr(e) => Some((e, None)),
115         // add the let...else expression (if present)
116         StmtKind::Local(local) => local.init.map(|init| (init, local.els)),
117         StmtKind::Item(..) => None,
118     }
119 }
120
121 #[allow(clippy::too_many_lines)]
122 fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id: HirId) -> NeverLoopResult {
123     match expr.kind {
124         ExprKind::Box(e)
125         | ExprKind::Unary(_, e)
126         | ExprKind::Cast(e, _)
127         | ExprKind::Type(e, _)
128         | ExprKind::Field(e, _)
129         | ExprKind::AddrOf(_, _, e)
130         | ExprKind::Repeat(e, _)
131         | ExprKind::DropTemps(e) => never_loop_expr(e, ignore_ids, main_loop_id),
132         ExprKind::Let(let_expr) => never_loop_expr(let_expr.init, ignore_ids, main_loop_id),
133         ExprKind::Array(es) | ExprKind::Tup(es) => never_loop_expr_all(&mut es.iter(), ignore_ids, main_loop_id),
134         ExprKind::MethodCall(_, receiver, es, _) => never_loop_expr_all(
135             &mut std::iter::once(receiver).chain(es.iter()),
136             ignore_ids,
137             main_loop_id,
138         ),
139         ExprKind::Struct(_, fields, base) => {
140             let fields = never_loop_expr_all(&mut fields.iter().map(|f| f.expr), ignore_ids, main_loop_id);
141             if let Some(base) = base {
142                 combine_both(fields, never_loop_expr(base, ignore_ids, main_loop_id))
143             } else {
144                 fields
145             }
146         },
147         ExprKind::Call(e, es) => never_loop_expr_all(&mut once(e).chain(es.iter()), ignore_ids, main_loop_id),
148         ExprKind::Binary(_, e1, e2)
149         | ExprKind::Assign(e1, e2, _)
150         | ExprKind::AssignOp(_, e1, e2)
151         | ExprKind::Index(e1, e2) => never_loop_expr_all(&mut [e1, e2].iter().copied(), ignore_ids, main_loop_id),
152         ExprKind::Loop(b, _, _, _) => {
153             // Break can come from the inner loop so remove them.
154             absorb_break(never_loop_block(b, ignore_ids, main_loop_id))
155         },
156         ExprKind::If(e, e2, e3) => {
157             let e1 = never_loop_expr(e, ignore_ids, main_loop_id);
158             let e2 = never_loop_expr(e2, ignore_ids, main_loop_id);
159             let e3 = e3.as_ref().map_or(NeverLoopResult::Otherwise, |e| {
160                 never_loop_expr(e, ignore_ids, main_loop_id)
161             });
162             combine_seq(e1, combine_branches(e2, e3))
163         },
164         ExprKind::Match(e, arms, _) => {
165             let e = never_loop_expr(e, ignore_ids, main_loop_id);
166             if arms.is_empty() {
167                 e
168             } else {
169                 let arms = never_loop_expr_branch(&mut arms.iter().map(|a| a.body), ignore_ids, main_loop_id);
170                 combine_seq(e, arms)
171             }
172         },
173         ExprKind::Block(b, l) => {
174             if l.is_some() {
175                 ignore_ids.push(b.hir_id);
176             }
177             let ret = never_loop_block(b, ignore_ids, main_loop_id);
178             ignore_ids.pop();
179             ret
180         },
181         ExprKind::Continue(d) => {
182             let id = d
183                 .target_id
184                 .expect("target ID can only be missing in the presence of compilation errors");
185             if id == main_loop_id {
186                 NeverLoopResult::MayContinueMainLoop
187             } else {
188                 NeverLoopResult::AlwaysBreak
189             }
190         },
191         // checks if break targets a block instead of a loop
192         ExprKind::Break(Destination { target_id: Ok(t), .. }, e) if ignore_ids.contains(&t) => e
193             .map_or(NeverLoopResult::Otherwise, |e| {
194                 combine_seq(never_loop_expr(e, ignore_ids, main_loop_id), NeverLoopResult::Otherwise)
195             }),
196         ExprKind::Break(_, e) | ExprKind::Ret(e) => e.as_ref().map_or(NeverLoopResult::AlwaysBreak, |e| {
197             combine_seq(
198                 never_loop_expr(e, ignore_ids, main_loop_id),
199                 NeverLoopResult::AlwaysBreak,
200             )
201         }),
202         ExprKind::InlineAsm(asm) => asm
203             .operands
204             .iter()
205             .map(|(o, _)| match o {
206                 InlineAsmOperand::In { expr, .. } | InlineAsmOperand::InOut { expr, .. } => {
207                     never_loop_expr(expr, ignore_ids, main_loop_id)
208                 },
209                 InlineAsmOperand::Out { expr, .. } => {
210                     never_loop_expr_all(&mut expr.iter().copied(), ignore_ids, main_loop_id)
211                 },
212                 InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => never_loop_expr_all(
213                     &mut once(*in_expr).chain(out_expr.iter().copied()),
214                     ignore_ids,
215                     main_loop_id,
216                 ),
217                 InlineAsmOperand::Const { .. }
218                 | InlineAsmOperand::SymFn { .. }
219                 | InlineAsmOperand::SymStatic { .. } => NeverLoopResult::Otherwise,
220             })
221             .fold(NeverLoopResult::Otherwise, combine_both),
222         ExprKind::Yield(_, _)
223         | ExprKind::Closure { .. }
224         | ExprKind::Path(_)
225         | ExprKind::ConstBlock(_)
226         | ExprKind::Lit(_)
227         | ExprKind::Err => NeverLoopResult::Otherwise,
228     }
229 }
230
231 fn never_loop_expr_all<'a, T: Iterator<Item = &'a Expr<'a>>>(
232     es: &mut T,
233     ignore_ids: &mut Vec<HirId>,
234     main_loop_id: HirId,
235 ) -> NeverLoopResult {
236     es.map(|e| never_loop_expr(e, ignore_ids, main_loop_id))
237         .fold(NeverLoopResult::Otherwise, combine_both)
238 }
239
240 fn never_loop_expr_branch<'a, T: Iterator<Item = &'a Expr<'a>>>(
241     e: &mut T,
242     ignore_ids: &mut Vec<HirId>,
243     main_loop_id: HirId,
244 ) -> NeverLoopResult {
245     e.map(|e| never_loop_expr(e, ignore_ids, main_loop_id))
246         .fold(NeverLoopResult::AlwaysBreak, combine_branches)
247 }
248
249 fn for_to_if_let_sugg(cx: &LateContext<'_>, iterator: &Expr<'_>, pat: &Pat<'_>) -> String {
250     let pat_snippet = snippet(cx, pat.span, "_");
251     let iter_snippet = make_iterator_snippet(cx, iterator, &mut Applicability::Unspecified);
252
253     format!("if let Some({pat_snippet}) = {iter_snippet}.next()")
254 }