]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/ranges.rs
Auto merge of #4085 - phansch:empty_loop_tests, r=matthiaskrgr
[rust.git] / clippy_lints / src / ranges.rs
1 use if_chain::if_chain;
2 use rustc::hir::*;
3 use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
4 use rustc::{declare_lint_pass, declare_tool_lint};
5 use rustc_errors::Applicability;
6 use syntax::ast::RangeLimits;
7 use syntax::source_map::Spanned;
8
9 use crate::utils::sugg::Sugg;
10 use crate::utils::{get_trait_def_id, higher, implements_trait, SpanlessEq};
11 use crate::utils::{is_integer_literal, paths, snippet, snippet_opt, span_lint, span_lint_and_then};
12
13 declare_clippy_lint! {
14     /// **What it does:** Checks for calling `.step_by(0)` on iterators,
15     /// which never terminates.
16     ///
17     /// **Why is this bad?** This very much looks like an oversight, since with
18     /// `loop { .. }` there is an obvious better way to endlessly loop.
19     ///
20     /// **Known problems:** None.
21     ///
22     /// **Example:**
23     /// ```ignore
24     /// for x in (5..5).step_by(0) {
25     ///     ..
26     /// }
27     /// ```
28     pub ITERATOR_STEP_BY_ZERO,
29     correctness,
30     "using `Iterator::step_by(0)`, which produces an infinite iterator"
31 }
32
33 declare_clippy_lint! {
34     /// **What it does:** Checks for zipping a collection with the range of
35     /// `0.._.len()`.
36     ///
37     /// **Why is this bad?** The code is better expressed with `.enumerate()`.
38     ///
39     /// **Known problems:** None.
40     ///
41     /// **Example:**
42     /// ```rust
43     /// x.iter().zip(0..x.len())
44     /// ```
45     pub RANGE_ZIP_WITH_LEN,
46     complexity,
47     "zipping iterator with a range when `enumerate()` would do"
48 }
49
50 declare_clippy_lint! {
51     /// **What it does:** Checks for exclusive ranges where 1 is added to the
52     /// upper bound, e.g., `x..(y+1)`.
53     ///
54     /// **Why is this bad?** The code is more readable with an inclusive range
55     /// like `x..=y`.
56     ///
57     /// **Known problems:** Will add unnecessary pair of parentheses when the
58     /// expression is not wrapped in a pair but starts with a opening parenthesis
59     /// and ends with a closing one.
60     /// I.e., `let _ = (f()+1)..(f()+1)` results in `let _ = ((f()+1)..=f())`.
61     ///
62     /// **Example:**
63     /// ```rust
64     /// for x..(y+1) { .. }
65     /// ```
66     pub RANGE_PLUS_ONE,
67     complexity,
68     "`x..(y+1)` reads better as `x..=y`"
69 }
70
71 declare_clippy_lint! {
72     /// **What it does:** Checks for inclusive ranges where 1 is subtracted from
73     /// the upper bound, e.g., `x..=(y-1)`.
74     ///
75     /// **Why is this bad?** The code is more readable with an exclusive range
76     /// like `x..y`.
77     ///
78     /// **Known problems:** None.
79     ///
80     /// **Example:**
81     /// ```rust
82     /// for x..=(y-1) { .. }
83     /// ```
84     pub RANGE_MINUS_ONE,
85     complexity,
86     "`x..=(y-1)` reads better as `x..y`"
87 }
88
89 declare_lint_pass!(Ranges => [
90     ITERATOR_STEP_BY_ZERO,
91     RANGE_ZIP_WITH_LEN,
92     RANGE_PLUS_ONE,
93     RANGE_MINUS_ONE
94 ]);
95
96 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Ranges {
97     fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) {
98         if let ExprKind::MethodCall(ref path, _, ref args) = expr.node {
99             let name = path.ident.as_str();
100
101             // Range with step_by(0).
102             if name == "step_by" && args.len() == 2 && has_step_by(cx, &args[0]) {
103                 use crate::consts::{constant, Constant};
104                 if let Some((Constant::Int(0), _)) = constant(cx, cx.tables, &args[1]) {
105                     span_lint(
106                         cx,
107                         ITERATOR_STEP_BY_ZERO,
108                         expr.span,
109                         "Iterator::step_by(0) will panic at runtime",
110                     );
111                 }
112             } else if name == "zip" && args.len() == 2 {
113                 let iter = &args[0].node;
114                 let zip_arg = &args[1];
115                 if_chain! {
116                     // `.iter()` call
117                     if let ExprKind::MethodCall(ref iter_path, _, ref iter_args ) = *iter;
118                     if iter_path.ident.name == "iter";
119                     // range expression in `.zip()` call: `0..x.len()`
120                     if let Some(higher::Range { start: Some(start), end: Some(end), .. }) = higher::range(cx, zip_arg);
121                     if is_integer_literal(start, 0);
122                     // `.len()` call
123                     if let ExprKind::MethodCall(ref len_path, _, ref len_args) = end.node;
124                     if len_path.ident.name == "len" && len_args.len() == 1;
125                     // `.iter()` and `.len()` called on same `Path`
126                     if let ExprKind::Path(QPath::Resolved(_, ref iter_path)) = iter_args[0].node;
127                     if let ExprKind::Path(QPath::Resolved(_, ref len_path)) = len_args[0].node;
128                     if SpanlessEq::new(cx).eq_path_segments(&iter_path.segments, &len_path.segments);
129                      then {
130                          span_lint(cx,
131                                    RANGE_ZIP_WITH_LEN,
132                                    expr.span,
133                                    &format!("It is more idiomatic to use {}.iter().enumerate()",
134                                             snippet(cx, iter_args[0].span, "_")));
135                     }
136                 }
137             }
138         }
139
140         // exclusive range plus one: `x..(y+1)`
141         if_chain! {
142             if let Some(higher::Range {
143                 start,
144                 end: Some(end),
145                 limits: RangeLimits::HalfOpen
146             }) = higher::range(cx, expr);
147             if let Some(y) = y_plus_one(end);
148             then {
149                 let span = expr.span
150                     .ctxt()
151                     .outer()
152                     .expn_info()
153                     .map_or(expr.span, |info| info.call_site);
154                 span_lint_and_then(
155                     cx,
156                     RANGE_PLUS_ONE,
157                     span,
158                     "an inclusive range would be more readable",
159                     |db| {
160                         let start = start.map_or(String::new(), |x| Sugg::hir(cx, x, "x").to_string());
161                         let end = Sugg::hir(cx, y, "y");
162                         if let Some(is_wrapped) = &snippet_opt(cx, span) {
163                             if is_wrapped.starts_with('(') && is_wrapped.ends_with(')') {
164                                 db.span_suggestion(
165                                     span,
166                                     "use",
167                                     format!("({}..={})", start, end),
168                                     Applicability::MaybeIncorrect,
169                                 );
170                             } else {
171                                 db.span_suggestion(
172                                     span,
173                                     "use",
174                                     format!("{}..={}", start, end),
175                                     Applicability::MachineApplicable, // snippet
176                                 );
177                             }
178                         }
179                     },
180                 );
181             }
182         }
183
184         // inclusive range minus one: `x..=(y-1)`
185         if_chain! {
186             if let Some(higher::Range { start, end: Some(end), limits: RangeLimits::Closed }) = higher::range(cx, expr);
187             if let Some(y) = y_minus_one(end);
188             then {
189                 span_lint_and_then(
190                     cx,
191                     RANGE_MINUS_ONE,
192                     expr.span,
193                     "an exclusive range would be more readable",
194                     |db| {
195                         let start = start.map_or(String::new(), |x| Sugg::hir(cx, x, "x").to_string());
196                         let end = Sugg::hir(cx, y, "y");
197                         db.span_suggestion(
198                             expr.span,
199                             "use",
200                             format!("{}..{}", start, end),
201                             Applicability::MachineApplicable, // snippet
202                         );
203                     },
204                 );
205             }
206         }
207     }
208 }
209
210 fn has_step_by(cx: &LateContext<'_, '_>, expr: &Expr) -> bool {
211     // No need for `walk_ptrs_ty` here because `step_by` moves `self`, so it
212     // can't be called on a borrowed range.
213     let ty = cx.tables.expr_ty_adjusted(expr);
214
215     get_trait_def_id(cx, &paths::ITERATOR).map_or(false, |iterator_trait| implements_trait(cx, ty, iterator_trait, &[]))
216 }
217
218 fn y_plus_one(expr: &Expr) -> Option<&Expr> {
219     match expr.node {
220         ExprKind::Binary(
221             Spanned {
222                 node: BinOpKind::Add, ..
223             },
224             ref lhs,
225             ref rhs,
226         ) => {
227             if is_integer_literal(lhs, 1) {
228                 Some(rhs)
229             } else if is_integer_literal(rhs, 1) {
230                 Some(lhs)
231             } else {
232                 None
233             }
234         },
235         _ => None,
236     }
237 }
238
239 fn y_minus_one(expr: &Expr) -> Option<&Expr> {
240     match expr.node {
241         ExprKind::Binary(
242             Spanned {
243                 node: BinOpKind::Sub, ..
244             },
245             ref lhs,
246             ref rhs,
247         ) if is_integer_literal(rhs, 1) => Some(lhs),
248         _ => None,
249     }
250 }