@@ -2,6 +2,7 @@ use std::mem;
22use std:: ops:: ControlFlow ;
33
44use clippy_utils:: comparisons:: { Rel , normalize_comparison} ;
5+ use clippy_utils:: consts:: { ConstEvalCtxt , Constant } ;
56use clippy_utils:: diagnostics:: span_lint_and_then;
67use clippy_utils:: higher:: { If , Range } ;
78use clippy_utils:: macros:: { find_assert_eq_args, first_node_macro_backtrace, root_macro_call} ;
@@ -91,35 +92,133 @@ enum LengthComparison {
9192 /// `v.len() == 5`
9293 LengthEqualInt ,
9394}
95+ #[ derive( Copy , Clone , Debug ) ]
96+ struct EvaluatedIntExpr < ' hir > {
97+ expr : & ' hir Expr < ' hir > ,
98+ value : usize ,
99+ }
100+ #[ derive( Copy , Clone , Debug ) ]
101+ enum AssertionSide < ' hir > {
102+ /// `v.len()` in `v.len() > 5`
103+ SliceLen {
104+ /// `v` in `v.len()`
105+ slice : & ' hir Expr < ' hir > ,
106+ } ,
107+ /// `5` in `v.len() > 5`
108+ AssertedLen ( EvaluatedIntExpr < ' hir > ) ,
109+ }
110+ impl < ' hir > AssertionSide < ' hir > {
111+ pub fn from_expr ( cx : & LateContext < ' _ > , expr : & ' hir Expr < ' hir > ) -> Option < Self > {
112+ Self :: asserted_len_from_int_lit_expr ( expr)
113+ . or_else ( || Self :: slice_len_from_expr ( cx, expr) )
114+ . or_else ( || Self :: asserted_len_from_possibly_const_expr ( cx, expr) )
115+ }
116+ pub fn slice_len_from_expr ( cx : & LateContext < ' _ > , expr : & ' hir Expr < ' hir > ) -> Option < Self > {
117+ if let ExprKind :: MethodCall ( method, recv, [ ] , _) = expr. kind
118+ // checking method name first rather than receiver's type could improve performance
119+ && method. ident . name == sym:: len
120+ && cx. typeck_results ( ) . expr_ty_adjusted ( recv) . peel_refs ( ) . is_slice ( )
121+ {
122+ Some ( Self :: SliceLen { slice : recv } )
123+ } else {
124+ None
125+ }
126+ }
127+ pub fn asserted_len_from_expr ( cx : & LateContext < ' _ > , expr : & ' hir Expr < ' hir > ) -> Option < Self > {
128+ Self :: asserted_len_from_int_lit_expr ( expr) . or_else ( || Self :: asserted_len_from_possibly_const_expr ( cx, expr) )
129+ }
130+ pub fn asserted_len_from_int_lit_expr ( expr : & ' hir Expr < ' hir > ) -> Option < Self > {
131+ if let ExprKind :: Lit ( Spanned {
132+ node : LitKind :: Int ( Pu128 ( x) , _) ,
133+ ..
134+ } ) = expr. kind
135+ {
136+ Some ( Self :: AssertedLen ( EvaluatedIntExpr {
137+ expr,
138+ value : x as usize ,
139+ } ) )
140+ } else {
141+ None
142+ }
143+ }
144+ pub fn asserted_len_from_possibly_const_expr ( cx : & LateContext < ' _ > , expr : & ' hir Expr < ' hir > ) -> Option < Self > {
145+ if let Some ( Constant :: Int ( x) ) = ConstEvalCtxt :: new ( cx) . eval ( expr) {
146+ Some ( Self :: AssertedLen ( EvaluatedIntExpr {
147+ expr,
148+ value : x as usize ,
149+ } ) )
150+ } else {
151+ None
152+ }
153+ }
154+ }
94155
95156/// Extracts parts out of a length comparison expression.
96157///
97158/// E.g. for `v.len() > 5` this returns `Some((LengthComparison::IntLessThanLength, 5, v.len()))`
98159fn len_comparison < ' hir > (
160+ cx : & LateContext < ' _ > ,
99161 bin_op : BinOpKind ,
100- left : & ' hir Expr < ' hir > ,
101- right : & ' hir Expr < ' hir > ,
102- ) -> Option < ( LengthComparison , usize , & ' hir Expr < ' hir > ) > {
103- macro_rules! int_lit_pat {
104- ( $id: ident) => {
105- ExprKind :: Lit ( Spanned {
106- node: LitKind :: Int ( Pu128 ( $id) , _) ,
107- ..
108- } )
109- } ;
162+ left_expr : & ' hir Expr < ' hir > ,
163+ right_expr : & ' hir Expr < ' hir > ,
164+ ) -> Option < ( LengthComparison , EvaluatedIntExpr < ' hir > , & ' hir Expr < ' hir > ) > {
165+ fn sniff_operands < ' hir > (
166+ cx : & LateContext < ' _ > ,
167+ left : & ' hir Expr < ' hir > ,
168+ right : & ' hir Expr < ' hir > ,
169+ ) -> Option < ( AssertionSide < ' hir > , AssertionSide < ' hir > ) > {
170+ // sniff as cheap as possible
171+ if let Some ( left) = AssertionSide :: asserted_len_from_int_lit_expr ( left) {
172+ Some ( ( left, AssertionSide :: from_expr ( cx, right) ?) )
173+ } else if let Some ( left) = AssertionSide :: slice_len_from_expr ( cx, left) {
174+ Some ( ( left, AssertionSide :: asserted_len_from_expr ( cx, right) ?) )
175+ } else {
176+ Some ( (
177+ AssertionSide :: asserted_len_from_possibly_const_expr ( cx, left) ?,
178+ AssertionSide :: slice_len_from_expr ( cx, right) ?,
179+ ) )
180+ }
110181 }
111182
183+ type Side < ' hir > = AssertionSide < ' hir > ;
184+
112185 // normalize comparison, `v.len() > 4` becomes `4 < v.len()`
113186 // this simplifies the logic a bit
114- let ( op, left, right) = normalize_comparison ( bin_op, left, right) ?;
115- match ( op, left. kind , right. kind ) {
116- ( Rel :: Lt , int_lit_pat ! ( left) , _) => Some ( ( LengthComparison :: IntLessThanLength , left as usize , right) ) ,
117- ( Rel :: Lt , _, int_lit_pat ! ( right) ) => Some ( ( LengthComparison :: LengthLessThanInt , right as usize , left) ) ,
118- ( Rel :: Le , int_lit_pat ! ( left) , _) => Some ( ( LengthComparison :: IntLessThanOrEqualLength , left as usize , right) ) ,
119- ( Rel :: Le , _, int_lit_pat ! ( right) ) => Some ( ( LengthComparison :: LengthLessThanOrEqualInt , right as usize , left) ) ,
120- ( Rel :: Eq , int_lit_pat ! ( left) , _) => Some ( ( LengthComparison :: LengthEqualInt , left as usize , right) ) ,
121- ( Rel :: Eq , _, int_lit_pat ! ( right) ) => Some ( ( LengthComparison :: LengthEqualInt , right as usize , left) ) ,
122- _ => None ,
187+ let ( op, left_expr, right_expr) = normalize_comparison ( bin_op, left_expr, right_expr) ?;
188+
189+ let ( left, right) = sniff_operands ( cx, left_expr, right_expr) ?;
190+ let ( swapped, asserted_len, slice) = match ( left, right) {
191+ // `A > B` (e.g. `5 > 4`)
192+ | ( Side :: AssertedLen ( _) , Side :: AssertedLen ( _) )
193+ // `v.len() > w.len()`
194+ | ( Side :: SliceLen { .. } , Side :: SliceLen { .. } ) => return None ,
195+ ( Side :: AssertedLen ( asserted_len) , Side :: SliceLen { slice } ) => {
196+ ( false , asserted_len, slice)
197+ } ,
198+ ( Side :: SliceLen { slice } , Side :: AssertedLen ( asserted_len) ) => {
199+ ( true , asserted_len, slice)
200+ } ,
201+ } ;
202+
203+ match op {
204+ Rel :: Lt => {
205+ let cmp = if swapped {
206+ LengthComparison :: LengthLessThanInt
207+ } else {
208+ LengthComparison :: IntLessThanLength
209+ } ;
210+ Some ( ( cmp, asserted_len, slice) )
211+ } ,
212+ Rel :: Le => {
213+ let cmp = if swapped {
214+ LengthComparison :: LengthLessThanOrEqualInt
215+ } else {
216+ LengthComparison :: IntLessThanOrEqualLength
217+ } ;
218+ Some ( ( cmp, asserted_len, slice) )
219+ } ,
220+ Rel :: Eq => Some ( ( LengthComparison :: LengthEqualInt , asserted_len, slice) ) ,
221+ Rel :: Ne => None ,
123222 }
124223}
125224
@@ -132,15 +231,15 @@ fn len_comparison<'hir>(
132231fn assert_len_expr < ' hir > (
133232 cx : & LateContext < ' _ > ,
134233 expr : & ' hir Expr < ' hir > ,
135- ) -> Option < ( LengthComparison , usize , & ' hir Expr < ' hir > , Symbol ) > {
136- let ( ( cmp, asserted_len, slice_len ) , macro_call) = if let Some ( If { cond, then, .. } ) = If :: hir ( expr)
234+ ) -> Option < ( LengthComparison , EvaluatedIntExpr < ' hir > , & ' hir Expr < ' hir > , Symbol ) > {
235+ let ( ( cmp, asserted_len, slice ) , macro_call) = if let Some ( If { cond, then, .. } ) = If :: hir ( expr)
137236 && let ExprKind :: Unary ( UnOp :: Not , condition) = & cond. kind
138237 && let ExprKind :: Binary ( bin_op, left, right) = & condition. kind
139238 // check if `then` block has a never type expression
140239 && let ExprKind :: Block ( Block { expr : Some ( then_expr) , .. } , _) = then. kind
141240 && cx. typeck_results ( ) . expr_ty ( then_expr) . is_never ( )
142241 {
143- ( len_comparison ( bin_op. node , left, right) ?, sym:: assert_macro)
242+ ( len_comparison ( cx , bin_op. node , left, right) ?, sym:: assert_macro)
144243 } else if let Some ( ( macro_call, bin_op) ) = first_node_macro_backtrace ( cx, expr) . find_map ( |macro_call| {
145244 match cx. tcx . get_diagnostic_name ( macro_call. def_id ) {
146245 Some ( sym:: assert_eq_macro) => Some ( ( macro_call, BinOpKind :: Eq ) ) ,
@@ -150,7 +249,7 @@ fn assert_len_expr<'hir>(
150249 } ) && let Some ( ( left, right, _) ) = find_assert_eq_args ( cx, expr, macro_call. expn )
151250 {
152251 (
153- len_comparison ( bin_op, left, right) ?,
252+ len_comparison ( cx , bin_op, left, right) ?,
154253 root_macro_call ( expr. span )
155254 . and_then ( |macro_call| cx. tcx . get_diagnostic_name ( macro_call. def_id ) )
156255 . unwrap_or ( sym:: assert_macro) ,
@@ -159,21 +258,14 @@ fn assert_len_expr<'hir>(
159258 return None ;
160259 } ;
161260
162- if let ExprKind :: MethodCall ( method, recv, [ ] , _) = & slice_len. kind
163- && cx. typeck_results ( ) . expr_ty_adjusted ( recv) . peel_refs ( ) . is_slice ( )
164- && method. ident . name == sym:: len
165- {
166- Some ( ( cmp, asserted_len, recv, macro_call) )
167- } else {
168- None
169- }
261+ Some ( ( cmp, asserted_len, slice, macro_call) )
170262}
171263
172264#[ derive( Debug ) ]
173265enum IndexEntry < ' hir > {
174266 /// `assert!` without any indexing (so far)
175267 StrayAssert {
176- asserted_len : usize ,
268+ asserted_len : EvaluatedIntExpr < ' hir > ,
177269 comparison : LengthComparison ,
178270 assert_span : Span ,
179271 slice : & ' hir Expr < ' hir > ,
@@ -186,7 +278,7 @@ enum IndexEntry<'hir> {
186278 AssertWithIndex {
187279 highest_index : usize ,
188280 is_first_highest : bool ,
189- asserted_len : usize ,
281+ asserted_len : EvaluatedIntExpr < ' hir > ,
190282 assert_span : Span ,
191283 slice : & ' hir Expr < ' hir > ,
192284 indexes : Vec < Span > ,
@@ -367,22 +459,29 @@ fn report_indexes(cx: &LateContext<'_>, map: UnindexMap<u64, Vec<IndexEntry<'_>>
367459 Some ( format ! ( "assert!({slice_str}.len() > {highest_index})" , ) )
368460 } ,
369461 // `5 < v.len()` == `v.len() > 5`
370- LengthComparison :: IntLessThanLength if asserted_len < highest_index => {
462+ LengthComparison :: IntLessThanLength if asserted_len. value < highest_index => {
371463 Some ( format ! ( "assert!({slice_str}.len() > {highest_index})" , ) )
372464 } ,
373465 // `5 <= v.len() == `v.len() >= 5`
374- LengthComparison :: IntLessThanOrEqualLength if asserted_len <= highest_index => {
375- Some ( format ! ( "assert!({slice_str}.len() > {highest_index})" , ) )
466+ LengthComparison :: IntLessThanOrEqualLength if asserted_len. value == highest_index => {
467+ let asserted_len_str =
468+ snippet_with_applicability ( cx, asserted_len. expr . span , "_" , & mut app) ;
469+ Some ( format ! ( "assert!({slice_str}.len() > {asserted_len_str})" , ) )
470+ } ,
471+ LengthComparison :: IntLessThanOrEqualLength if asserted_len. value < highest_index => {
472+ Some ( format ! ( "assert!({slice_str}.len() >= {})" , highest_index + 1 ) )
376473 } ,
377474 // `highest_index` here is rather a length, so we need to add 1 to it
378- LengthComparison :: LengthEqualInt if asserted_len < highest_index + 1 => match macro_call {
379- sym:: assert_eq_macro => {
380- Some ( format ! ( "assert_eq!({slice_str}.len(), {})" , highest_index + 1 ) )
381- } ,
382- sym:: debug_assert_eq_macro => {
383- Some ( format ! ( "debug_assert_eq!({slice_str}.len(), {})" , highest_index + 1 ) )
384- } ,
385- _ => Some ( format ! ( "assert!({slice_str}.len() == {})" , highest_index + 1 ) ) ,
475+ LengthComparison :: LengthEqualInt if asserted_len. value < highest_index + 1 => {
476+ match macro_call {
477+ sym:: assert_eq_macro => {
478+ Some ( format ! ( "assert_eq!({slice_str}.len(), {})" , highest_index + 1 ) )
479+ } ,
480+ sym:: debug_assert_eq_macro => {
481+ Some ( format ! ( "debug_assert_eq!({slice_str}.len(), {})" , highest_index + 1 ) )
482+ } ,
483+ _ => Some ( format ! ( "assert!({slice_str}.len() == {})" , highest_index + 1 ) ) ,
484+ }
386485 } ,
387486 _ => None ,
388487 } ;
0 commit comments