]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/needless_continue.rs
Auto merge of #4085 - phansch:empty_loop_tests, r=matthiaskrgr
[rust.git] / clippy_lints / src / needless_continue.rs
1 //! Checks for continue statements in loops that are redundant.
2 //!
3 //! For example, the lint would catch
4 //!
5 //! ```rust
6 //! let mut a = 1;
7 //! let x = true;
8 //!
9 //! while a < 5 {
10 //!     a = 6;
11 //!     if x {
12 //!         // ...
13 //!     } else {
14 //!         continue;
15 //!     }
16 //!     println!("Hello, world");
17 //! }
18 //! ```
19 //!
20 //! And suggest something like this:
21 //!
22 //! ```rust
23 //! let mut a = 1;
24 //! let x = true;
25 //!
26 //! while a < 5 {
27 //!     a = 6;
28 //!     if x {
29 //!         // ...
30 //!         println!("Hello, world");
31 //!     }
32 //! }
33 //! ```
34 //!
35 //! This lint is **warn** by default.
36 use rustc::lint::{EarlyContext, EarlyLintPass, LintArray, LintPass};
37 use rustc::{declare_lint_pass, declare_tool_lint};
38 use std::borrow::Cow;
39 use syntax::ast;
40 use syntax::source_map::{original_sp, DUMMY_SP};
41
42 use crate::utils::{in_macro_or_desugar, snippet, snippet_block, span_help_and_lint, trim_multiline};
43
44 declare_clippy_lint! {
45     /// **What it does:** The lint checks for `if`-statements appearing in loops
46     /// that contain a `continue` statement in either their main blocks or their
47     /// `else`-blocks, when omitting the `else`-block possibly with some
48     /// rearrangement of code can make the code easier to understand.
49     ///
50     /// **Why is this bad?** Having explicit `else` blocks for `if` statements
51     /// containing `continue` in their THEN branch adds unnecessary branching and
52     /// nesting to the code. Having an else block containing just `continue` can
53     /// also be better written by grouping the statements following the whole `if`
54     /// statement within the THEN block and omitting the else block completely.
55     ///
56     /// **Known problems:** None
57     ///
58     /// **Example:**
59     /// ```rust
60     /// while condition() {
61     ///     update_condition();
62     ///     if x {
63     ///         // ...
64     ///     } else {
65     ///         continue;
66     ///     }
67     ///     println!("Hello, world");
68     /// }
69     /// ```
70     ///
71     /// Could be rewritten as
72     ///
73     /// ```rust
74     /// while condition() {
75     ///     update_condition();
76     ///     if x {
77     ///         // ...
78     ///         println!("Hello, world");
79     ///     }
80     /// }
81     /// ```
82     ///
83     /// As another example, the following code
84     ///
85     /// ```rust
86     /// loop {
87     ///     if waiting() {
88     ///         continue;
89     ///     } else {
90     ///         // Do something useful
91     ///     }
92     /// }
93     /// ```
94     /// Could be rewritten as
95     ///
96     /// ```rust
97     /// loop {
98     ///     if waiting() {
99     ///         continue;
100     ///     }
101     ///     // Do something useful
102     /// }
103     /// ```
104     pub NEEDLESS_CONTINUE,
105     pedantic,
106     "`continue` statements that can be replaced by a rearrangement of code"
107 }
108
109 declare_lint_pass!(NeedlessContinue => [NEEDLESS_CONTINUE]);
110
111 impl EarlyLintPass for NeedlessContinue {
112     fn check_expr(&mut self, ctx: &EarlyContext<'_>, expr: &ast::Expr) {
113         if !in_macro_or_desugar(expr.span) {
114             check_and_warn(ctx, expr);
115         }
116     }
117 }
118
119 /* This lint has to mainly deal with two cases of needless continue
120  * statements. */
121 // Case 1 [Continue inside else block]:
122 //
123 //     loop {
124 //         // region A
125 //         if cond {
126 //             // region B
127 //         } else {
128 //             continue;
129 //         }
130 //         // region C
131 //     }
132 //
133 // This code can better be written as follows:
134 //
135 //     loop {
136 //         // region A
137 //         if cond {
138 //             // region B
139 //             // region C
140 //         }
141 //     }
142 //
143 // Case 2 [Continue inside then block]:
144 //
145 //     loop {
146 //       // region A
147 //       if cond {
148 //           continue;
149 //           // potentially more code here.
150 //       } else {
151 //           // region B
152 //       }
153 //       // region C
154 //     }
155 //
156 //
157 // This snippet can be refactored to:
158 //
159 //     loop {
160 //       // region A
161 //       if !cond {
162 //           // region B
163 //           // region C
164 //       }
165 //     }
166 //
167
168 /// Given an expression, returns true if either of the following is true
169 ///
170 /// - The expression is a `continue` node.
171 /// - The expression node is a block with the first statement being a
172 /// `continue`.
173 fn needless_continue_in_else(else_expr: &ast::Expr, label: Option<&ast::Label>) -> bool {
174     match else_expr.node {
175         ast::ExprKind::Block(ref else_block, _) => is_first_block_stmt_continue(else_block, label),
176         ast::ExprKind::Continue(l) => compare_labels(label, l.as_ref()),
177         _ => false,
178     }
179 }
180
181 fn is_first_block_stmt_continue(block: &ast::Block, label: Option<&ast::Label>) -> bool {
182     block.stmts.get(0).map_or(false, |stmt| match stmt.node {
183         ast::StmtKind::Semi(ref e) | ast::StmtKind::Expr(ref e) => {
184             if let ast::ExprKind::Continue(ref l) = e.node {
185                 compare_labels(label, l.as_ref())
186             } else {
187                 false
188             }
189         },
190         _ => false,
191     })
192 }
193
194 /// If the `continue` has a label, check it matches the label of the loop.
195 fn compare_labels(loop_label: Option<&ast::Label>, continue_label: Option<&ast::Label>) -> bool {
196     match (loop_label, continue_label) {
197         // `loop { continue; }` or `'a loop { continue; }`
198         (_, None) => true,
199         // `loop { continue 'a; }`
200         (None, _) => false,
201         // `'a loop { continue 'a; }` or `'a loop { continue 'b; }`
202         (Some(x), Some(y)) => x.ident == y.ident,
203     }
204 }
205
206 /// If `expr` is a loop expression (while/while let/for/loop), calls `func` with
207 /// the AST object representing the loop block of `expr`.
208 fn with_loop_block<F>(expr: &ast::Expr, mut func: F)
209 where
210     F: FnMut(&ast::Block, Option<&ast::Label>),
211 {
212     match expr.node {
213         ast::ExprKind::While(_, ref loop_block, ref label)
214         | ast::ExprKind::WhileLet(_, _, ref loop_block, ref label)
215         | ast::ExprKind::ForLoop(_, _, ref loop_block, ref label)
216         | ast::ExprKind::Loop(ref loop_block, ref label) => func(loop_block, label.as_ref()),
217         _ => {},
218     }
219 }
220
221 /// If `stmt` is an if expression node with an `else` branch, calls func with
222 /// the
223 /// following:
224 ///
225 /// - The `if` expression itself,
226 /// - The `if` condition expression,
227 /// - The `then` block, and
228 /// - The `else` expression.
229 fn with_if_expr<F>(stmt: &ast::Stmt, mut func: F)
230 where
231     F: FnMut(&ast::Expr, &ast::Expr, &ast::Block, &ast::Expr),
232 {
233     match stmt.node {
234         ast::StmtKind::Semi(ref e) | ast::StmtKind::Expr(ref e) => {
235             if let ast::ExprKind::If(ref cond, ref if_block, Some(ref else_expr)) = e.node {
236                 func(e, cond, if_block, else_expr);
237             }
238         },
239         _ => {},
240     }
241 }
242
243 /// A type to distinguish between the two distinct cases this lint handles.
244 #[derive(Copy, Clone, Debug)]
245 enum LintType {
246     ContinueInsideElseBlock,
247     ContinueInsideThenBlock,
248 }
249
250 /// Data we pass around for construction of help messages.
251 struct LintData<'a> {
252     /// The `if` expression encountered in the above loop.
253     if_expr: &'a ast::Expr,
254     /// The condition expression for the above `if`.
255     if_cond: &'a ast::Expr,
256     /// The `then` block of the `if` statement.
257     if_block: &'a ast::Block,
258     /// The `else` block of the `if` statement.
259     /// Note that we only work with `if` exprs that have an `else` branch.
260     else_expr: &'a ast::Expr,
261     /// The 0-based index of the `if` statement in the containing loop block.
262     stmt_idx: usize,
263     /// The statements of the loop block.
264     block_stmts: &'a [ast::Stmt],
265 }
266
267 const MSG_REDUNDANT_ELSE_BLOCK: &str = "This else block is redundant.\n";
268
269 const MSG_ELSE_BLOCK_NOT_NEEDED: &str = "There is no need for an explicit `else` block for this `if` \
270                                          expression\n";
271
272 const DROP_ELSE_BLOCK_AND_MERGE_MSG: &str = "Consider dropping the else clause and merging the code that \
273                                              follows (in the loop) with the if block, like so:\n";
274
275 const DROP_ELSE_BLOCK_MSG: &str = "Consider dropping the else clause, and moving out the code in the else \
276                                    block, like so:\n";
277
278 fn emit_warning<'a>(ctx: &EarlyContext<'_>, data: &'a LintData<'_>, header: &str, typ: LintType) {
279     // snip    is the whole *help* message that appears after the warning.
280     // message is the warning message.
281     // expr    is the expression which the lint warning message refers to.
282     let (snip, message, expr) = match typ {
283         LintType::ContinueInsideElseBlock => (
284             suggestion_snippet_for_continue_inside_else(ctx, data, header),
285             MSG_REDUNDANT_ELSE_BLOCK,
286             data.else_expr,
287         ),
288         LintType::ContinueInsideThenBlock => (
289             suggestion_snippet_for_continue_inside_if(ctx, data, header),
290             MSG_ELSE_BLOCK_NOT_NEEDED,
291             data.if_expr,
292         ),
293     };
294     span_help_and_lint(ctx, NEEDLESS_CONTINUE, expr.span, message, &snip);
295 }
296
297 fn suggestion_snippet_for_continue_inside_if<'a>(
298     ctx: &EarlyContext<'_>,
299     data: &'a LintData<'_>,
300     header: &str,
301 ) -> String {
302     let cond_code = snippet(ctx, data.if_cond.span, "..");
303
304     let if_code = format!("if {} {{\n    continue;\n}}\n", cond_code);
305     /* ^^^^--- Four spaces of indentation. */
306     // region B
307     let else_code = snippet(ctx, data.else_expr.span, "..").into_owned();
308     let else_code = erode_block(&else_code);
309     let else_code = trim_multiline(Cow::from(else_code), false);
310
311     let mut ret = String::from(header);
312     ret.push_str(&if_code);
313     ret.push_str(&else_code);
314     ret.push_str("\n...");
315     ret
316 }
317
318 fn suggestion_snippet_for_continue_inside_else<'a>(
319     ctx: &EarlyContext<'_>,
320     data: &'a LintData<'_>,
321     header: &str,
322 ) -> String {
323     let cond_code = snippet(ctx, data.if_cond.span, "..");
324     let mut if_code = format!("if {} {{\n", cond_code);
325
326     // Region B
327     let block_code = &snippet(ctx, data.if_block.span, "..").into_owned();
328     let block_code = erode_block(block_code);
329     let block_code = trim_multiline(Cow::from(block_code), false);
330
331     if_code.push_str(&block_code);
332
333     // Region C
334     // These is the code in the loop block that follows the if/else construction
335     // we are complaining about. We want to pull all of this code into the
336     // `then` block of the `if` statement.
337     let to_annex = data.block_stmts[data.stmt_idx + 1..]
338         .iter()
339         .map(|stmt| original_sp(stmt.span, DUMMY_SP))
340         .map(|span| snippet_block(ctx, span, "..").into_owned())
341         .collect::<Vec<_>>()
342         .join("\n");
343
344     let mut ret = String::from(header);
345
346     ret.push_str(&if_code);
347     ret.push_str("\n// Merged code follows...");
348     ret.push_str(&to_annex);
349     ret.push_str("\n}\n");
350     ret
351 }
352
353 fn check_and_warn<'a>(ctx: &EarlyContext<'_>, expr: &'a ast::Expr) {
354     with_loop_block(expr, |loop_block, label| {
355         for (i, stmt) in loop_block.stmts.iter().enumerate() {
356             with_if_expr(stmt, |if_expr, cond, then_block, else_expr| {
357                 let data = &LintData {
358                     stmt_idx: i,
359                     if_expr,
360                     if_cond: cond,
361                     if_block: then_block,
362                     else_expr,
363                     block_stmts: &loop_block.stmts,
364                 };
365                 if needless_continue_in_else(else_expr, label) {
366                     emit_warning(
367                         ctx,
368                         data,
369                         DROP_ELSE_BLOCK_AND_MERGE_MSG,
370                         LintType::ContinueInsideElseBlock,
371                     );
372                 } else if is_first_block_stmt_continue(then_block, label) {
373                     emit_warning(ctx, data, DROP_ELSE_BLOCK_MSG, LintType::ContinueInsideThenBlock);
374                 }
375             });
376         }
377     });
378 }
379
380 /// Eats at `s` from the end till a closing brace `}` is encountered, and then
381 /// continues eating till a non-whitespace character is found.
382 /// e.g., the string
383 ///
384 /// ```rust
385 /// {
386 ///     let x = 5;
387 /// }
388 /// ```
389 ///
390 /// is transformed to
391 ///
392 /// ```ignore
393 ///     {
394 ///         let x = 5;
395 /// ```
396 ///
397 /// NOTE: when there is no closing brace in `s`, `s` is _not_ preserved, i.e.,
398 /// an empty string will be returned in that case.
399 pub fn erode_from_back(s: &str) -> String {
400     let mut ret = String::from(s);
401     while ret.pop().map_or(false, |c| c != '}') {}
402     while let Some(c) = ret.pop() {
403         if !c.is_whitespace() {
404             ret.push(c);
405             break;
406         }
407     }
408     ret
409 }
410
411 /// Eats at `s` from the front by first skipping all leading whitespace. Then,
412 /// any number of opening braces are eaten, followed by any number of newlines.
413 /// e.g.,  the string
414 ///
415 /// ```ignore
416 ///         {
417 ///             something();
418 ///             inside_a_block();
419 ///         }
420 /// ```
421 ///
422 /// is transformed to
423 ///
424 /// ```ignore
425 ///             something();
426 ///             inside_a_block();
427 ///         }
428 /// ```
429 pub fn erode_from_front(s: &str) -> String {
430     s.chars()
431         .skip_while(|c| c.is_whitespace())
432         .skip_while(|c| *c == '{')
433         .skip_while(|c| *c == '\n')
434         .collect::<String>()
435 }
436
437 /// If `s` contains the code for a block, delimited by braces, this function
438 /// tries to get the contents of the block. If there is no closing brace
439 /// present,
440 /// an empty string is returned.
441 pub fn erode_block(s: &str) -> String {
442     erode_from_back(&erode_from_front(s))
443 }