• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use super::utils::make_iterator_snippet;
2 use super::NEVER_LOOP;
3 use clippy_utils::consts::constant;
4 use clippy_utils::higher::ForLoop;
5 use clippy_utils::source::snippet;
6 use clippy_utils::{consts::Constant, diagnostics::span_lint_and_then};
7 use rustc_errors::Applicability;
8 use rustc_hir::{Block, Destination, Expr, ExprKind, HirId, InlineAsmOperand, Pat, Stmt, StmtKind};
9 use rustc_lint::LateContext;
10 use rustc_span::Span;
11 use std::iter::{once, Iterator};
12 
check<'tcx>( cx: &LateContext<'tcx>, block: &Block<'tcx>, loop_id: HirId, span: Span, for_loop: Option<&ForLoop<'_>>, )13 pub(super) fn check<'tcx>(
14     cx: &LateContext<'tcx>,
15     block: &Block<'tcx>,
16     loop_id: HirId,
17     span: Span,
18     for_loop: Option<&ForLoop<'_>>,
19 ) {
20     match never_loop_block(cx, block, &mut Vec::new(), loop_id) {
21         NeverLoopResult::AlwaysBreak => {
22             span_lint_and_then(cx, NEVER_LOOP, span, "this loop never actually loops", |diag| {
23                 if let Some(ForLoop {
24                     arg: iterator,
25                     pat,
26                     span: for_span,
27                     ..
28                 }) = for_loop
29                 {
30                     // Suggests using an `if let` instead. This is `Unspecified` because the
31                     // loop may (probably) contain `break` statements which would be invalid
32                     // in an `if let`.
33                     diag.span_suggestion_verbose(
34                         for_span.with_hi(iterator.span.hi()),
35                         "if you need the first element of the iterator, try writing",
36                         for_to_if_let_sugg(cx, iterator, pat),
37                         Applicability::Unspecified,
38                     );
39                 }
40             });
41         },
42         NeverLoopResult::MayContinueMainLoop | NeverLoopResult::Otherwise => (),
43         NeverLoopResult::IgnoreUntilEnd(_) => unreachable!(),
44     }
45 }
46 
47 #[derive(Copy, Clone)]
48 enum NeverLoopResult {
49     // A break/return always get triggered but not necessarily for the main loop.
50     AlwaysBreak,
51     // A continue may occur for the main loop.
52     MayContinueMainLoop,
53     // Ignore everything until the end of the block with this id
54     IgnoreUntilEnd(HirId),
55     Otherwise,
56 }
57 
58 #[must_use]
absorb_break(arg: NeverLoopResult) -> NeverLoopResult59 fn absorb_break(arg: NeverLoopResult) -> NeverLoopResult {
60     match arg {
61         NeverLoopResult::AlwaysBreak | NeverLoopResult::Otherwise => NeverLoopResult::Otherwise,
62         NeverLoopResult::MayContinueMainLoop => NeverLoopResult::MayContinueMainLoop,
63         NeverLoopResult::IgnoreUntilEnd(id) => NeverLoopResult::IgnoreUntilEnd(id),
64     }
65 }
66 
67 // Combine two results for parts that are called in order.
68 #[must_use]
combine_seq(first: NeverLoopResult, second: NeverLoopResult) -> NeverLoopResult69 fn combine_seq(first: NeverLoopResult, second: NeverLoopResult) -> NeverLoopResult {
70     match first {
71         NeverLoopResult::AlwaysBreak | NeverLoopResult::MayContinueMainLoop | NeverLoopResult::IgnoreUntilEnd(_) => {
72             first
73         },
74         NeverLoopResult::Otherwise => second,
75     }
76 }
77 
78 // Combine two results where only one of the part may have been executed.
79 #[must_use]
combine_branches(b1: NeverLoopResult, b2: NeverLoopResult, ignore_ids: &[HirId]) -> NeverLoopResult80 fn combine_branches(b1: NeverLoopResult, b2: NeverLoopResult, ignore_ids: &[HirId]) -> NeverLoopResult {
81     match (b1, b2) {
82         (NeverLoopResult::IgnoreUntilEnd(a), NeverLoopResult::IgnoreUntilEnd(b)) => {
83             if ignore_ids.iter().find(|&e| e == &a || e == &b).unwrap() == &a {
84                 NeverLoopResult::IgnoreUntilEnd(b)
85             } else {
86                 NeverLoopResult::IgnoreUntilEnd(a)
87             }
88         },
89         (i @ NeverLoopResult::IgnoreUntilEnd(_), NeverLoopResult::AlwaysBreak)
90         | (NeverLoopResult::AlwaysBreak, i @ NeverLoopResult::IgnoreUntilEnd(_)) => i,
91         (NeverLoopResult::AlwaysBreak, NeverLoopResult::AlwaysBreak) => NeverLoopResult::AlwaysBreak,
92         (NeverLoopResult::MayContinueMainLoop, _) | (_, NeverLoopResult::MayContinueMainLoop) => {
93             NeverLoopResult::MayContinueMainLoop
94         },
95         (NeverLoopResult::Otherwise, _) | (_, NeverLoopResult::Otherwise) => NeverLoopResult::Otherwise,
96     }
97 }
98 
never_loop_block<'tcx>( cx: &LateContext<'tcx>, block: &Block<'tcx>, ignore_ids: &mut Vec<HirId>, main_loop_id: HirId, ) -> NeverLoopResult99 fn never_loop_block<'tcx>(
100     cx: &LateContext<'tcx>,
101     block: &Block<'tcx>,
102     ignore_ids: &mut Vec<HirId>,
103     main_loop_id: HirId,
104 ) -> NeverLoopResult {
105     let iter = block
106         .stmts
107         .iter()
108         .filter_map(stmt_to_expr)
109         .chain(block.expr.map(|expr| (expr, None)));
110 
111     iter.map(|(e, els)| {
112         let e = never_loop_expr(cx, e, ignore_ids, main_loop_id);
113         // els is an else block in a let...else binding
114         els.map_or(e, |els| {
115             combine_branches(e, never_loop_block(cx, els, ignore_ids, main_loop_id), ignore_ids)
116         })
117     })
118     .fold(NeverLoopResult::Otherwise, combine_seq)
119 }
120 
stmt_to_expr<'tcx>(stmt: &Stmt<'tcx>) -> Option<(&'tcx Expr<'tcx>, Option<&'tcx Block<'tcx>>)>121 fn stmt_to_expr<'tcx>(stmt: &Stmt<'tcx>) -> Option<(&'tcx Expr<'tcx>, Option<&'tcx Block<'tcx>>)> {
122     match stmt.kind {
123         StmtKind::Semi(e) | StmtKind::Expr(e) => Some((e, None)),
124         // add the let...else expression (if present)
125         StmtKind::Local(local) => local.init.map(|init| (init, local.els)),
126         StmtKind::Item(..) => None,
127     }
128 }
129 
130 #[allow(clippy::too_many_lines)]
never_loop_expr<'tcx>( cx: &LateContext<'tcx>, expr: &Expr<'tcx>, ignore_ids: &mut Vec<HirId>, main_loop_id: HirId, ) -> NeverLoopResult131 fn never_loop_expr<'tcx>(
132     cx: &LateContext<'tcx>,
133     expr: &Expr<'tcx>,
134     ignore_ids: &mut Vec<HirId>,
135     main_loop_id: HirId,
136 ) -> NeverLoopResult {
137     match expr.kind {
138         ExprKind::Unary(_, e)
139         | ExprKind::Cast(e, _)
140         | ExprKind::Type(e, _)
141         | ExprKind::Field(e, _)
142         | ExprKind::AddrOf(_, _, e)
143         | ExprKind::Repeat(e, _)
144         | ExprKind::DropTemps(e) => never_loop_expr(cx, e, ignore_ids, main_loop_id),
145         ExprKind::Let(let_expr) => never_loop_expr(cx, let_expr.init, ignore_ids, main_loop_id),
146         ExprKind::Array(es) | ExprKind::Tup(es) => never_loop_expr_all(cx, &mut es.iter(), ignore_ids, main_loop_id),
147         ExprKind::MethodCall(_, receiver, es, _) => never_loop_expr_all(
148             cx,
149             &mut std::iter::once(receiver).chain(es.iter()),
150             ignore_ids,
151             main_loop_id,
152         ),
153         ExprKind::Struct(_, fields, base) => {
154             let fields = never_loop_expr_all(cx, &mut fields.iter().map(|f| f.expr), ignore_ids, main_loop_id);
155             if let Some(base) = base {
156                 combine_seq(fields, never_loop_expr(cx, base, ignore_ids, main_loop_id))
157             } else {
158                 fields
159             }
160         },
161         ExprKind::Call(e, es) => never_loop_expr_all(cx, &mut once(e).chain(es.iter()), ignore_ids, main_loop_id),
162         ExprKind::Binary(_, e1, e2)
163         | ExprKind::Assign(e1, e2, _)
164         | ExprKind::AssignOp(_, e1, e2)
165         | ExprKind::Index(e1, e2) => never_loop_expr_all(cx, &mut [e1, e2].iter().copied(), ignore_ids, main_loop_id),
166         ExprKind::Loop(b, _, _, _) => {
167             // Break can come from the inner loop so remove them.
168             absorb_break(never_loop_block(cx, b, ignore_ids, main_loop_id))
169         },
170         ExprKind::If(e, e2, e3) => {
171             let e1 = never_loop_expr(cx, e, ignore_ids, main_loop_id);
172             let e2 = never_loop_expr(cx, e2, ignore_ids, main_loop_id);
173             // If we know the `if` condition evaluates to `true`, don't check everything past it; it
174             // should just return whatever's evaluated for `e1` and `e2` since `e3` is unreachable
175             if let Some(Constant::Bool(true)) = constant(cx, cx.typeck_results(), e) {
176                 return combine_seq(e1, e2);
177             }
178             let e3 = e3.as_ref().map_or(NeverLoopResult::Otherwise, |e| {
179                 never_loop_expr(cx, e, ignore_ids, main_loop_id)
180             });
181             combine_seq(e1, combine_branches(e2, e3, ignore_ids))
182         },
183         ExprKind::Match(e, arms, _) => {
184             let e = never_loop_expr(cx, e, ignore_ids, main_loop_id);
185             if arms.is_empty() {
186                 e
187             } else {
188                 let arms = never_loop_expr_branch(cx, &mut arms.iter().map(|a| a.body), ignore_ids, main_loop_id);
189                 combine_seq(e, arms)
190             }
191         },
192         ExprKind::Block(b, l) => {
193             if l.is_some() {
194                 ignore_ids.push(b.hir_id);
195             }
196             let ret = never_loop_block(cx, b, ignore_ids, main_loop_id);
197             if l.is_some() {
198                 ignore_ids.pop();
199             }
200             match ret {
201                 NeverLoopResult::IgnoreUntilEnd(a) if a == b.hir_id => NeverLoopResult::Otherwise,
202                 _ => ret,
203             }
204         },
205         ExprKind::Continue(d) => {
206             let id = d
207                 .target_id
208                 .expect("target ID can only be missing in the presence of compilation errors");
209             if id == main_loop_id {
210                 NeverLoopResult::MayContinueMainLoop
211             } else {
212                 NeverLoopResult::AlwaysBreak
213             }
214         },
215         // checks if break targets a block instead of a loop
216         ExprKind::Break(Destination { target_id: Ok(t), .. }, e) if ignore_ids.contains(&t) => e
217             .map_or(NeverLoopResult::IgnoreUntilEnd(t), |e| {
218                 never_loop_expr(cx, e, ignore_ids, main_loop_id)
219             }),
220         ExprKind::Break(_, e) | ExprKind::Ret(e) => e.as_ref().map_or(NeverLoopResult::AlwaysBreak, |e| {
221             combine_seq(
222                 never_loop_expr(cx, e, ignore_ids, main_loop_id),
223                 NeverLoopResult::AlwaysBreak,
224             )
225         }),
226         ExprKind::Become(e) => combine_seq(
227             never_loop_expr(cx, e, ignore_ids, main_loop_id),
228             NeverLoopResult::AlwaysBreak,
229         ),
230         ExprKind::InlineAsm(asm) => asm
231             .operands
232             .iter()
233             .map(|(o, _)| match o {
234                 InlineAsmOperand::In { expr, .. } | InlineAsmOperand::InOut { expr, .. } => {
235                     never_loop_expr(cx, expr, ignore_ids, main_loop_id)
236                 },
237                 InlineAsmOperand::Out { expr, .. } => {
238                     never_loop_expr_all(cx, &mut expr.iter().copied(), ignore_ids, main_loop_id)
239                 },
240                 InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => never_loop_expr_all(
241                     cx,
242                     &mut once(*in_expr).chain(out_expr.iter().copied()),
243                     ignore_ids,
244                     main_loop_id,
245                 ),
246                 InlineAsmOperand::Const { .. }
247                 | InlineAsmOperand::SymFn { .. }
248                 | InlineAsmOperand::SymStatic { .. } => NeverLoopResult::Otherwise,
249             })
250             .fold(NeverLoopResult::Otherwise, combine_seq),
251         ExprKind::OffsetOf(_, _)
252         | ExprKind::Yield(_, _)
253         | ExprKind::Closure { .. }
254         | ExprKind::Path(_)
255         | ExprKind::ConstBlock(_)
256         | ExprKind::Lit(_)
257         | ExprKind::Err(_) => NeverLoopResult::Otherwise,
258     }
259 }
260 
never_loop_expr_all<'tcx, T: Iterator<Item = &'tcx Expr<'tcx>>>( cx: &LateContext<'tcx>, es: &mut T, ignore_ids: &mut Vec<HirId>, main_loop_id: HirId, ) -> NeverLoopResult261 fn never_loop_expr_all<'tcx, T: Iterator<Item = &'tcx Expr<'tcx>>>(
262     cx: &LateContext<'tcx>,
263     es: &mut T,
264     ignore_ids: &mut Vec<HirId>,
265     main_loop_id: HirId,
266 ) -> NeverLoopResult {
267     es.map(|e| never_loop_expr(cx, e, ignore_ids, main_loop_id))
268         .fold(NeverLoopResult::Otherwise, combine_seq)
269 }
270 
never_loop_expr_branch<'tcx, T: Iterator<Item = &'tcx Expr<'tcx>>>( cx: &LateContext<'tcx>, e: &mut T, ignore_ids: &mut Vec<HirId>, main_loop_id: HirId, ) -> NeverLoopResult271 fn never_loop_expr_branch<'tcx, T: Iterator<Item = &'tcx Expr<'tcx>>>(
272     cx: &LateContext<'tcx>,
273     e: &mut T,
274     ignore_ids: &mut Vec<HirId>,
275     main_loop_id: HirId,
276 ) -> NeverLoopResult {
277     e.fold(NeverLoopResult::AlwaysBreak, |a, b| {
278         combine_branches(a, never_loop_expr(cx, b, ignore_ids, main_loop_id), ignore_ids)
279     })
280 }
281 
for_to_if_let_sugg(cx: &LateContext<'_>, iterator: &Expr<'_>, pat: &Pat<'_>) -> String282 fn for_to_if_let_sugg(cx: &LateContext<'_>, iterator: &Expr<'_>, pat: &Pat<'_>) -> String {
283     let pat_snippet = snippet(cx, pat.span, "_");
284     let iter_snippet = make_iterator_snippet(cx, iterator, &mut Applicability::Unspecified);
285 
286     format!("if let Some({pat_snippet}) = {iter_snippet}.next()")
287 }
288