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