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