]>
Commit | Line | Data |
---|---|---|
94222f64 | 1 | use super::utils::make_iterator_snippet; |
f20569fa | 2 | use super::NEVER_LOOP; |
94222f64 XL |
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, LoopSource, Node, Pat, Stmt, StmtKind}; | |
f20569fa XL |
8 | use rustc_lint::LateContext; |
9 | use std::iter::{once, Iterator}; | |
10 | ||
11 | pub(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 | ||
39 | enum 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] | |
48 | fn 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] | |
57 | fn 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] | |
66 | fn 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] | |
78 | fn 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 | ||
88 | fn 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 | ||
95 | fn 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 | ||
100 | fn 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 | ||
108 | fn 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 | ||
188 | fn 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 | ||
193 | fn 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 | |
198 | fn 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 | } |