@@ -2,15 +2,19 @@ use crate::consts::{constant, Constant};
2
2
use if_chain:: if_chain;
3
3
use rustc_ast:: ast:: RangeLimits ;
4
4
use rustc_errors:: Applicability ;
5
- use rustc_hir:: { BinOpKind , Expr , ExprKind , QPath } ;
5
+ use rustc_hir:: { BinOpKind , Expr , ExprKind , PathSegment , QPath } ;
6
6
use rustc_lint:: { LateContext , LateLintPass } ;
7
7
use rustc_middle:: ty;
8
8
use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
9
- use rustc_span:: source_map:: Spanned ;
9
+ use rustc_span:: source_map:: { Span , Spanned } ;
10
+ use rustc_span:: symbol:: Ident ;
10
11
use std:: cmp:: Ordering ;
11
12
12
13
use crate :: utils:: sugg:: Sugg ;
13
- use crate :: utils:: { get_parent_expr, is_integer_const, snippet, snippet_opt, span_lint, span_lint_and_then} ;
14
+ use crate :: utils:: {
15
+ get_parent_expr, is_integer_const, single_segment_path, snippet, snippet_opt, snippet_with_applicability,
16
+ span_lint, span_lint_and_then,
17
+ } ;
14
18
use crate :: utils:: { higher, SpanlessEq } ;
15
19
16
20
declare_clippy_lint ! {
@@ -128,43 +132,51 @@ declare_clippy_lint! {
128
132
"reversing the limits of range expressions, resulting in empty ranges"
129
133
}
130
134
135
+ declare_clippy_lint ! {
136
+ /// **What it does:** Checks for expressions like `x >= 3 && x < 8` that could
137
+ /// be more readably expressed as `(3..8).contains(x)`.
138
+ ///
139
+ /// **Why is this bad?** `contains` expresses the intent better and has less
140
+ /// failure modes (such as fencepost errors or using `||` instead of `&&`).
141
+ ///
142
+ /// **Known problems:** None.
143
+ ///
144
+ /// **Example:**
145
+ ///
146
+ /// ```rust
147
+ /// // given
148
+ /// let x = 6;
149
+ ///
150
+ /// assert!(x >= 3 && x < 8);
151
+ /// ```
152
+ /// Use instead:
153
+ /// ```rust
154
+ ///# let x = 6;
155
+ /// assert!((3..8).contains(x));
156
+ /// ```
157
+ pub MANUAL_RANGE_CONTAINS ,
158
+ style,
159
+ "manually reimplementing {`Range`, `RangeInclusive`}`::contains`"
160
+ }
161
+
131
162
declare_lint_pass ! ( Ranges => [
132
163
RANGE_ZIP_WITH_LEN ,
133
164
RANGE_PLUS_ONE ,
134
165
RANGE_MINUS_ONE ,
135
166
REVERSED_EMPTY_RANGES ,
167
+ MANUAL_RANGE_CONTAINS ,
136
168
] ) ;
137
169
138
170
impl < ' tcx > LateLintPass < ' tcx > for Ranges {
139
171
fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) {
140
- if let ExprKind :: MethodCall ( ref path, _, ref args, _) = expr. kind {
141
- let name = path. ident . as_str ( ) ;
142
- if name == "zip" && args. len ( ) == 2 {
143
- let iter = & args[ 0 ] . kind ;
144
- let zip_arg = & args[ 1 ] ;
145
- if_chain ! {
146
- // `.iter()` call
147
- if let ExprKind :: MethodCall ( ref iter_path, _, ref iter_args , _) = * iter;
148
- if iter_path. ident. name == sym!( iter) ;
149
- // range expression in `.zip()` call: `0..x.len()`
150
- if let Some ( higher:: Range { start: Some ( start) , end: Some ( end) , .. } ) = higher:: range( zip_arg) ;
151
- if is_integer_const( cx, start, 0 ) ;
152
- // `.len()` call
153
- if let ExprKind :: MethodCall ( ref len_path, _, ref len_args, _) = end. kind;
154
- if len_path. ident. name == sym!( len) && len_args. len( ) == 1 ;
155
- // `.iter()` and `.len()` called on same `Path`
156
- if let ExprKind :: Path ( QPath :: Resolved ( _, ref iter_path) ) = iter_args[ 0 ] . kind;
157
- if let ExprKind :: Path ( QPath :: Resolved ( _, ref len_path) ) = len_args[ 0 ] . kind;
158
- if SpanlessEq :: new( cx) . eq_path_segments( & iter_path. segments, & len_path. segments) ;
159
- then {
160
- span_lint( cx,
161
- RANGE_ZIP_WITH_LEN ,
162
- expr. span,
163
- & format!( "it is more idiomatic to use `{}.iter().enumerate()`" ,
164
- snippet( cx, iter_args[ 0 ] . span, "_" ) ) ) ;
165
- }
166
- }
167
- }
172
+ match expr. kind {
173
+ ExprKind :: MethodCall ( ref path, _, ref args, _) => {
174
+ check_range_zip_with_len ( cx, path, args, expr. span ) ;
175
+ } ,
176
+ ExprKind :: Binary ( ref op, ref l, ref r) => {
177
+ check_possible_range_contains ( cx, op. node , l, r, expr. span ) ;
178
+ } ,
179
+ _ => { } ,
168
180
}
169
181
170
182
check_exclusive_range_plus_one ( cx, expr) ;
@@ -173,6 +185,124 @@ impl<'tcx> LateLintPass<'tcx> for Ranges {
173
185
}
174
186
}
175
187
188
+ fn check_possible_range_contains ( cx : & LateContext < ' _ > , op : BinOpKind , l : & Expr < ' _ > , r : & Expr < ' _ > , span : Span ) {
189
+ let combine_and = match op {
190
+ BinOpKind :: And | BinOpKind :: BitAnd => true ,
191
+ BinOpKind :: Or | BinOpKind :: BitOr => false ,
192
+ _ => return ,
193
+ } ;
194
+ // value, name, order (higher/lower), inclusiveness
195
+ if let ( Some ( ( lval, lname, name_span, lval_span, lord, linc) ) , Some ( ( rval, rname, _, rval_span, rord, rinc) ) ) =
196
+ ( check_range_bounds ( cx, l) , check_range_bounds ( cx, r) )
197
+ {
198
+ // we only lint comparisons on the same name and with different
199
+ // direction
200
+ if lname != rname || lord == rord {
201
+ return ;
202
+ }
203
+ let ord = Constant :: partial_cmp ( cx. tcx , cx. typeck_results ( ) . expr_ty ( l) , & lval, & rval) ;
204
+ if combine_and && ord == Some ( rord) {
205
+ // order lower bound and upper bound
206
+ let ( l_span, u_span, l_inc, u_inc) = if rord == Ordering :: Less {
207
+ ( lval_span, rval_span, linc, rinc)
208
+ } else {
209
+ ( rval_span, lval_span, rinc, linc)
210
+ } ;
211
+ // we only lint inclusive lower bounds
212
+ if !l_inc {
213
+ return ;
214
+ }
215
+ let ( range_type, range_op) = if u_inc {
216
+ ( "RangeInclusive" , "..=" )
217
+ } else {
218
+ ( "Range" , ".." )
219
+ } ;
220
+ span_lint_and_then (
221
+ cx,
222
+ MANUAL_RANGE_CONTAINS ,
223
+ span,
224
+ & format ! ( "manual `{}::contains` implementation" , range_type) ,
225
+ |diag| {
226
+ let mut applicability = Applicability :: MachineApplicable ;
227
+ let name = snippet_with_applicability ( cx, name_span, "_" , & mut applicability) ;
228
+ let lo = snippet_with_applicability ( cx, l_span, "_" , & mut applicability) ;
229
+ let hi = snippet_with_applicability ( cx, u_span, "_" , & mut applicability) ;
230
+ diag. span_suggestion (
231
+ span,
232
+ "use" ,
233
+ format ! ( "({}{}{}).contains({})" , lo, range_op, hi, name) ,
234
+ applicability,
235
+ ) ;
236
+ } ,
237
+ ) ;
238
+ }
239
+ }
240
+ //TODO: lint !(..).contains
241
+ }
242
+
243
+ fn check_range_bounds ( cx : & LateContext < ' _ > , ex : & Expr < ' _ > ) -> Option < ( Constant , Ident , Span , Span , Ordering , bool ) > {
244
+ if let ExprKind :: Binary ( ref op, ref l, ref r) = ex. kind {
245
+ let ( inclusive, ordering) = match op. node {
246
+ BinOpKind :: Gt => ( false , Ordering :: Greater ) ,
247
+ BinOpKind :: Ge => ( true , Ordering :: Greater ) ,
248
+ BinOpKind :: Lt => ( false , Ordering :: Less ) ,
249
+ BinOpKind :: Le => ( true , Ordering :: Less ) ,
250
+ _ => return None ,
251
+ } ;
252
+ if let Some ( id) = match_ident ( l) {
253
+ if let Some ( ( c, _) ) = constant ( cx, cx. typeck_results ( ) , r) {
254
+ return Some ( ( c, id, l. span , r. span , ordering, inclusive) ) ;
255
+ }
256
+ } else if let Some ( id) = match_ident ( r) {
257
+ if let Some ( ( c, _) ) = constant ( cx, cx. typeck_results ( ) , l) {
258
+ return Some ( ( c, id, r. span , l. span , ordering. reverse ( ) , inclusive) ) ;
259
+ }
260
+ }
261
+ }
262
+ None
263
+ }
264
+
265
+ fn match_ident ( e : & Expr < ' _ > ) -> Option < Ident > {
266
+ if let ExprKind :: Path ( ref qpath) = e. kind {
267
+ if let Some ( seg) = single_segment_path ( qpath) {
268
+ if seg. args . is_none ( ) {
269
+ return Some ( seg. ident ) ;
270
+ }
271
+ }
272
+ }
273
+ None
274
+ }
275
+
276
+ fn check_range_zip_with_len ( cx : & LateContext < ' _ > , path : & PathSegment < ' _ > , args : & [ Expr < ' _ > ] , span : Span ) {
277
+ let name = path. ident . as_str ( ) ;
278
+ if name == "zip" && args. len ( ) == 2 {
279
+ let iter = & args[ 0 ] . kind ;
280
+ let zip_arg = & args[ 1 ] ;
281
+ if_chain ! {
282
+ // `.iter()` call
283
+ if let ExprKind :: MethodCall ( ref iter_path, _, ref iter_args, _) = * iter;
284
+ if iter_path. ident. name == sym!( iter) ;
285
+ // range expression in `.zip()` call: `0..x.len()`
286
+ if let Some ( higher:: Range { start: Some ( start) , end: Some ( end) , .. } ) = higher:: range( zip_arg) ;
287
+ if is_integer_const( cx, start, 0 ) ;
288
+ // `.len()` call
289
+ if let ExprKind :: MethodCall ( ref len_path, _, ref len_args, _) = end. kind;
290
+ if len_path. ident. name == sym!( len) && len_args. len( ) == 1 ;
291
+ // `.iter()` and `.len()` called on same `Path`
292
+ if let ExprKind :: Path ( QPath :: Resolved ( _, ref iter_path) ) = iter_args[ 0 ] . kind;
293
+ if let ExprKind :: Path ( QPath :: Resolved ( _, ref len_path) ) = len_args[ 0 ] . kind;
294
+ if SpanlessEq :: new( cx) . eq_path_segments( & iter_path. segments, & len_path. segments) ;
295
+ then {
296
+ span_lint( cx,
297
+ RANGE_ZIP_WITH_LEN ,
298
+ span,
299
+ & format!( "it is more idiomatic to use `{}.iter().enumerate()`" ,
300
+ snippet( cx, iter_args[ 0 ] . span, "_" ) ) ) ;
301
+ }
302
+ }
303
+ }
304
+ }
305
+
176
306
// exclusive range plus one: `x..(y+1)`
177
307
fn check_exclusive_range_plus_one ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > ) {
178
308
if_chain ! {
0 commit comments