@@ -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_sugg, 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,148 @@ 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
+ let mut applicability = Applicability :: MachineApplicable ;
221
+ let name = snippet_with_applicability ( cx, name_span, "_" , & mut applicability) ;
222
+ let lo = snippet_with_applicability ( cx, l_span, "_" , & mut applicability) ;
223
+ let hi = snippet_with_applicability ( cx, u_span, "_" , & mut applicability) ;
224
+ span_lint_and_sugg (
225
+ cx,
226
+ MANUAL_RANGE_CONTAINS ,
227
+ span,
228
+ & format ! ( "manual `{}::contains` implementation" , range_type) ,
229
+ "use" ,
230
+ format ! ( "({}{}{}).contains(&{})" , lo, range_op, hi, name) ,
231
+ applicability,
232
+ ) ;
233
+ } else if !combine_and && ord == Some ( lord) {
234
+ // `!_.contains(_)`
235
+ // order lower bound and upper bound
236
+ let ( l_span, u_span, l_inc, u_inc) = if lord == Ordering :: Less {
237
+ ( lval_span, rval_span, linc, rinc)
238
+ } else {
239
+ ( rval_span, lval_span, rinc, linc)
240
+ } ;
241
+ if l_inc {
242
+ return ;
243
+ }
244
+ let ( range_type, range_op) = if u_inc {
245
+ ( "Range" , ".." )
246
+ } else {
247
+ ( "RangeInclusive" , "..=" )
248
+ } ;
249
+ let mut applicability = Applicability :: MachineApplicable ;
250
+ let name = snippet_with_applicability ( cx, name_span, "_" , & mut applicability) ;
251
+ let lo = snippet_with_applicability ( cx, l_span, "_" , & mut applicability) ;
252
+ let hi = snippet_with_applicability ( cx, u_span, "_" , & mut applicability) ;
253
+ span_lint_and_sugg (
254
+ cx,
255
+ MANUAL_RANGE_CONTAINS ,
256
+ span,
257
+ & format ! ( "manual `!{}::contains` implementation" , range_type) ,
258
+ "use" ,
259
+ format ! ( "!({}{}{}).contains(&{})" , lo, range_op, hi, name) ,
260
+ applicability,
261
+ ) ;
262
+ }
263
+ }
264
+ }
265
+
266
+ fn check_range_bounds ( cx : & LateContext < ' _ > , ex : & Expr < ' _ > ) -> Option < ( Constant , Ident , Span , Span , Ordering , bool ) > {
267
+ if let ExprKind :: Binary ( ref op, ref l, ref r) = ex. kind {
268
+ let ( inclusive, ordering) = match op. node {
269
+ BinOpKind :: Gt => ( false , Ordering :: Greater ) ,
270
+ BinOpKind :: Ge => ( true , Ordering :: Greater ) ,
271
+ BinOpKind :: Lt => ( false , Ordering :: Less ) ,
272
+ BinOpKind :: Le => ( true , Ordering :: Less ) ,
273
+ _ => return None ,
274
+ } ;
275
+ if let Some ( id) = match_ident ( l) {
276
+ if let Some ( ( c, _) ) = constant ( cx, cx. typeck_results ( ) , r) {
277
+ return Some ( ( c, id, l. span , r. span , ordering, inclusive) ) ;
278
+ }
279
+ } else if let Some ( id) = match_ident ( r) {
280
+ if let Some ( ( c, _) ) = constant ( cx, cx. typeck_results ( ) , l) {
281
+ return Some ( ( c, id, r. span , l. span , ordering. reverse ( ) , inclusive) ) ;
282
+ }
283
+ }
284
+ }
285
+ None
286
+ }
287
+
288
+ fn match_ident ( e : & Expr < ' _ > ) -> Option < Ident > {
289
+ if let ExprKind :: Path ( ref qpath) = e. kind {
290
+ if let Some ( seg) = single_segment_path ( qpath) {
291
+ if seg. args . is_none ( ) {
292
+ return Some ( seg. ident ) ;
293
+ }
294
+ }
295
+ }
296
+ None
297
+ }
298
+
299
+ fn check_range_zip_with_len ( cx : & LateContext < ' _ > , path : & PathSegment < ' _ > , args : & [ Expr < ' _ > ] , span : Span ) {
300
+ let name = path. ident . as_str ( ) ;
301
+ if name == "zip" && args. len ( ) == 2 {
302
+ let iter = & args[ 0 ] . kind ;
303
+ let zip_arg = & args[ 1 ] ;
304
+ if_chain ! {
305
+ // `.iter()` call
306
+ if let ExprKind :: MethodCall ( ref iter_path, _, ref iter_args, _) = * iter;
307
+ if iter_path. ident. name == sym!( iter) ;
308
+ // range expression in `.zip()` call: `0..x.len()`
309
+ if let Some ( higher:: Range { start: Some ( start) , end: Some ( end) , .. } ) = higher:: range( zip_arg) ;
310
+ if is_integer_const( cx, start, 0 ) ;
311
+ // `.len()` call
312
+ if let ExprKind :: MethodCall ( ref len_path, _, ref len_args, _) = end. kind;
313
+ if len_path. ident. name == sym!( len) && len_args. len( ) == 1 ;
314
+ // `.iter()` and `.len()` called on same `Path`
315
+ if let ExprKind :: Path ( QPath :: Resolved ( _, ref iter_path) ) = iter_args[ 0 ] . kind;
316
+ if let ExprKind :: Path ( QPath :: Resolved ( _, ref len_path) ) = len_args[ 0 ] . kind;
317
+ if SpanlessEq :: new( cx) . eq_path_segments( & iter_path. segments, & len_path. segments) ;
318
+ then {
319
+ span_lint( cx,
320
+ RANGE_ZIP_WITH_LEN ,
321
+ span,
322
+ & format!( "it is more idiomatic to use `{}.iter().enumerate()`" ,
323
+ snippet( cx, iter_args[ 0 ] . span, "_" ) )
324
+ ) ;
325
+ }
326
+ }
327
+ }
328
+ }
329
+
176
330
// exclusive range plus one: `x..(y+1)`
177
331
fn check_exclusive_range_plus_one ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > ) {
178
332
if_chain ! {
0 commit comments