11use rustc:: lint:: * ;
22use rustc:: hir:: * ;
3- use utils:: { is_integer_literal, paths, snippet, span_lint} ;
3+ use syntax:: ast:: RangeLimits ;
4+ use syntax:: codemap:: Spanned ;
5+ use utils:: { is_integer_literal, paths, snippet, span_lint, span_lint_and_then} ;
46use utils:: { get_trait_def_id, higher, implements_trait} ;
7+ use utils:: sugg:: Sugg ;
58
69/// **What it does:** Checks for calling `.step_by(0)` on iterators,
710/// which never terminates.
@@ -38,16 +41,57 @@ declare_lint! {
3841 "zipping iterator with a range when `enumerate()` would do"
3942}
4043
44+ /// **What it does:** Checks for exclusive ranges where 1 is added to the
45+ /// upper bound, e.g. `x..(y+1)`.
46+ ///
47+ /// **Why is this bad?** The code is more readable with an inclusive range
48+ /// like `x..=y`.
49+ ///
50+ /// **Known problems:** None.
51+ ///
52+ /// **Example:**
53+ /// ```rust
54+ /// for x..(y+1) { .. }
55+ /// ```
56+ declare_lint ! {
57+ pub RANGE_PLUS_ONE ,
58+ Allow ,
59+ "`x..(y+1)` reads better as `x..=y`"
60+ }
61+
62+ /// **What it does:** Checks for inclusive ranges where 1 is subtracted from
63+ /// the upper bound, e.g. `x..=(y-1)`.
64+ ///
65+ /// **Why is this bad?** The code is more readable with an exclusive range
66+ /// like `x..y`.
67+ ///
68+ /// **Known problems:** None.
69+ ///
70+ /// **Example:**
71+ /// ```rust
72+ /// for x..=(y-1) { .. }
73+ /// ```
74+ declare_lint ! {
75+ pub RANGE_MINUS_ONE ,
76+ Warn ,
77+ "`x..=(y-1)` reads better as `x..y`"
78+ }
79+
4180#[ derive( Copy , Clone ) ]
42- pub struct StepByZero ;
81+ pub struct Pass ;
4382
44- impl LintPass for StepByZero {
83+ impl LintPass for Pass {
4584 fn get_lints ( & self ) -> LintArray {
46- lint_array ! ( ITERATOR_STEP_BY_ZERO , RANGE_ZIP_WITH_LEN )
85+ lint_array ! (
86+ ITERATOR_STEP_BY_ZERO ,
87+ RANGE_ZIP_WITH_LEN ,
88+ RANGE_PLUS_ONE ,
89+ RANGE_MINUS_ONE
90+ )
4791 }
4892}
4993
50- impl < ' a , ' tcx > LateLintPass < ' a , ' tcx > for StepByZero {
94+ impl < ' a , ' tcx > LateLintPass < ' a , ' tcx > for Pass {
5195 fn check_expr ( & mut self , cx : & LateContext < ' a , ' tcx > , expr : & ' tcx Expr ) {
5296 if let ExprMethodCall ( ref path, _, ref args) = expr. node {
5397 let name = path. name . as_str ( ) ;
@@ -92,6 +136,46 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for StepByZero {
92136 } }
93137 }
94138 }
139+
140+ // exclusive range plus one: x..(y+1)
141+ if_let_chain ! { [
142+ let Some ( higher:: Range { start, end: Some ( end) , limits: RangeLimits :: HalfOpen } ) = higher:: range( expr) ,
143+ let Some ( y) = y_plus_one( end) ,
144+ ] , {
145+ span_lint_and_then(
146+ cx,
147+ RANGE_PLUS_ONE ,
148+ expr. span,
149+ "an inclusive range would be more readable" ,
150+ |db| {
151+ let start = start. map_or( "" . to_owned( ) , |x| Sugg :: hir( cx, x, "x" ) . to_string( ) ) ;
152+ let end = Sugg :: hir( cx, y, "y" ) ;
153+ db. span_suggestion( expr. span,
154+ "use" ,
155+ format!( "{}..={}" , start, end) ) ;
156+ } ,
157+ ) ;
158+ } }
159+
160+ // inclusive range minus one: x..=(y-1)
161+ if_let_chain ! { [
162+ let Some ( higher:: Range { start, end: Some ( end) , limits: RangeLimits :: Closed } ) = higher:: range( expr) ,
163+ let Some ( y) = y_minus_one( end) ,
164+ ] , {
165+ span_lint_and_then(
166+ cx,
167+ RANGE_MINUS_ONE ,
168+ expr. span,
169+ "an exclusive range would be more readable" ,
170+ |db| {
171+ let start = start. map_or( "" . to_owned( ) , |x| Sugg :: hir( cx, x, "x" ) . to_string( ) ) ;
172+ let end = Sugg :: hir( cx, y, "y" ) ;
173+ db. span_suggestion( expr. span,
174+ "use" ,
175+ format!( "{}..{}" , start, end) ) ;
176+ } ,
177+ ) ;
178+ } }
95179 }
96180}
97181
@@ -102,3 +186,25 @@ fn has_step_by(cx: &LateContext, expr: &Expr) -> bool {
102186
103187 get_trait_def_id ( cx, & paths:: ITERATOR ) . map_or ( false , |iterator_trait| implements_trait ( cx, ty, iterator_trait, & [ ] ) )
104188}
189+
190+ fn y_plus_one ( expr : & Expr ) -> Option < & Expr > {
191+ match expr. node {
192+ ExprBinary ( Spanned { node : BiAdd , .. } , ref lhs, ref rhs) => {
193+ if is_integer_literal ( lhs, 1 ) {
194+ Some ( rhs)
195+ } else if is_integer_literal ( rhs, 1 ) {
196+ Some ( lhs)
197+ } else {
198+ None
199+ }
200+ } ,
201+ _ => None ,
202+ }
203+ }
204+
205+ fn y_minus_one ( expr : & Expr ) -> Option < & Expr > {
206+ match expr. node {
207+ ExprBinary ( Spanned { node : BiSub , .. } , ref lhs, ref rhs) if is_integer_literal ( rhs, 1 ) => Some ( lhs) ,
208+ _ => None ,
209+ }
210+ }
0 commit comments