@@ -2,15 +2,19 @@ use crate::consts::{constant, Constant};
22use if_chain:: if_chain;
33use rustc_ast:: ast:: RangeLimits ;
44use rustc_errors:: Applicability ;
5- use rustc_hir:: { BinOpKind , Expr , ExprKind , QPath } ;
5+ use rustc_hir:: { BinOpKind , Expr , ExprKind , PathSegment , QPath } ;
66use rustc_lint:: { LateContext , LateLintPass } ;
77use rustc_middle:: ty;
88use 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 ;
1011use std:: cmp:: Ordering ;
1112
1213use 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+ } ;
1418use crate :: utils:: { higher, SpanlessEq } ;
1519
1620declare_clippy_lint ! {
@@ -128,43 +132,51 @@ declare_clippy_lint! {
128132 "reversing the limits of range expressions, resulting in empty ranges"
129133}
130134
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+
131162declare_lint_pass ! ( Ranges => [
132163 RANGE_ZIP_WITH_LEN ,
133164 RANGE_PLUS_ONE ,
134165 RANGE_MINUS_ONE ,
135166 REVERSED_EMPTY_RANGES ,
167+ MANUAL_RANGE_CONTAINS ,
136168] ) ;
137169
138170impl < ' tcx > LateLintPass < ' tcx > for Ranges {
139171 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+ _ => { } ,
168180 }
169181
170182 check_exclusive_range_plus_one ( cx, expr) ;
@@ -173,6 +185,148 @@ impl<'tcx> LateLintPass<'tcx> for Ranges {
173185 }
174186}
175187
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+
176330// exclusive range plus one: `x..(y+1)`
177331fn check_exclusive_range_plus_one ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > ) {
178332 if_chain ! {
0 commit comments