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