@@ -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,113 @@ 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 slice_len_from_expr ( cx : & LateContext < ' _ > , expr : & ' hir Expr < ' hir > ) -> Option < Self > {
112+ if let ExprKind :: MethodCall ( method, recv, [ ] , _) = expr. kind
113+ // checking method name first rather than receiver's type could improve performance
114+ && method. ident . name == sym:: len
115+ && cx. typeck_results ( ) . expr_ty_adjusted ( recv) . peel_refs ( ) . is_slice ( )
116+ {
117+ Some ( Self :: SliceLen { slice : recv } )
118+ } else {
119+ None
120+ }
121+ }
122+ pub fn asserted_len_from_expr ( cx : & LateContext < ' _ > , expr : & ' hir Expr < ' hir > ) -> Option < Self > {
123+ if let ExprKind :: Lit ( Spanned { node, .. } ) = expr. kind {
124+ // cases like `b'A'` or `5.0` would be extremely rare so
125+ // ignore those and consider only integers
126+ if let LitKind :: Int ( Pu128 ( x) , _) = node {
127+ // integer literals
128+ Some ( Self :: AssertedLen ( EvaluatedIntExpr {
129+ expr,
130+ value : x as usize ,
131+ } ) )
132+ } else {
133+ None
134+ }
135+ } else if let Some ( Constant :: Int ( x) ) = ConstEvalCtxt :: new ( cx) . eval ( expr) {
136+ Some ( Self :: AssertedLen ( EvaluatedIntExpr {
137+ expr,
138+ value : x as usize ,
139+ } ) )
140+ } else {
141+ None
142+ }
143+ }
144+ }
94145
95146/// Extracts parts out of a length comparison expression.
96147///
97148/// E.g. for `v.len() > 5` this returns `Some((LengthComparison::IntLessThanLength, 5, v.len()))`
98149fn len_comparison < ' hir > (
150+ cx : & LateContext < ' _ > ,
99151 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- } ;
110- }
152+ left_expr : & ' hir Expr < ' hir > ,
153+ right_expr : & ' hir Expr < ' hir > ,
154+ ) -> Option < ( LengthComparison , EvaluatedIntExpr < ' hir > , & ' hir Expr < ' hir > ) > {
155+ type Side < ' hir > = AssertionSide < ' hir > ;
111156
112157 // normalize comparison, `v.len() > 4` becomes `4 < v.len()`
113158 // 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 ,
159+ let ( op, left_expr, right_expr) = normalize_comparison ( bin_op, left_expr, right_expr) ?;
160+
161+ // sniff operands as cheap as possible
162+ // eliminate possibility of both sides being `v.len()` variant
163+ let ( left, right) = match AssertionSide :: slice_len_from_expr ( cx, left_expr) {
164+ Some ( left) => ( left, AssertionSide :: asserted_len_from_expr ( cx, right_expr) ?) ,
165+ None => (
166+ AssertionSide :: asserted_len_from_expr ( cx, left_expr) ?,
167+ AssertionSide :: slice_len_from_expr ( cx, right_expr) ?,
168+ ) ,
169+ } ;
170+ let ( swapped, asserted_len, slice) = match ( left, right) {
171+ // `A > B` (e.g. `5 > 4`)
172+ | ( Side :: AssertedLen ( _) , Side :: AssertedLen ( _) )
173+ // `v.len() > w.len()`
174+ | ( Side :: SliceLen { .. } , Side :: SliceLen { .. } ) => return None ,
175+ ( Side :: AssertedLen ( asserted_len) , Side :: SliceLen { slice } ) => {
176+ ( false , asserted_len, slice)
177+ } ,
178+ ( Side :: SliceLen { slice } , Side :: AssertedLen ( asserted_len) ) => {
179+ ( true , asserted_len, slice)
180+ } ,
181+ } ;
182+
183+ match op {
184+ Rel :: Lt => {
185+ let cmp = if swapped {
186+ LengthComparison :: LengthLessThanInt
187+ } else {
188+ LengthComparison :: IntLessThanLength
189+ } ;
190+ Some ( ( cmp, asserted_len, slice) )
191+ } ,
192+ Rel :: Le => {
193+ let cmp = if swapped {
194+ LengthComparison :: LengthLessThanOrEqualInt
195+ } else {
196+ LengthComparison :: IntLessThanOrEqualLength
197+ } ;
198+ Some ( ( cmp, asserted_len, slice) )
199+ } ,
200+ Rel :: Eq => Some ( ( LengthComparison :: LengthEqualInt , asserted_len, slice) ) ,
201+ Rel :: Ne => None ,
123202 }
124203}
125204
@@ -132,15 +211,15 @@ fn len_comparison<'hir>(
132211fn assert_len_expr < ' hir > (
133212 cx : & LateContext < ' _ > ,
134213 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)
214+ ) -> Option < ( LengthComparison , EvaluatedIntExpr < ' hir > , & ' hir Expr < ' hir > , Symbol ) > {
215+ let ( ( cmp, asserted_len, slice ) , macro_call) = if let Some ( If { cond, then, .. } ) = If :: hir ( expr)
137216 && let ExprKind :: Unary ( UnOp :: Not , condition) = & cond. kind
138217 && let ExprKind :: Binary ( bin_op, left, right) = & condition. kind
139218 // check if `then` block has a never type expression
140219 && let ExprKind :: Block ( Block { expr : Some ( then_expr) , .. } , _) = then. kind
141220 && cx. typeck_results ( ) . expr_ty ( then_expr) . is_never ( )
142221 {
143- ( len_comparison ( bin_op. node , left, right) ?, sym:: assert_macro)
222+ ( len_comparison ( cx , bin_op. node , left, right) ?, sym:: assert_macro)
144223 } else if let Some ( ( macro_call, bin_op) ) = first_node_macro_backtrace ( cx, expr) . find_map ( |macro_call| {
145224 match cx. tcx . get_diagnostic_name ( macro_call. def_id ) {
146225 Some ( sym:: assert_eq_macro) => Some ( ( macro_call, BinOpKind :: Eq ) ) ,
@@ -150,7 +229,7 @@ fn assert_len_expr<'hir>(
150229 } ) && let Some ( ( left, right, _) ) = find_assert_eq_args ( cx, expr, macro_call. expn )
151230 {
152231 (
153- len_comparison ( bin_op, left, right) ?,
232+ len_comparison ( cx , bin_op, left, right) ?,
154233 root_macro_call ( expr. span )
155234 . and_then ( |macro_call| cx. tcx . get_diagnostic_name ( macro_call. def_id ) )
156235 . unwrap_or ( sym:: assert_macro) ,
@@ -159,21 +238,14 @@ fn assert_len_expr<'hir>(
159238 return None ;
160239 } ;
161240
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- }
241+ Some ( ( cmp, asserted_len, slice, macro_call) )
170242}
171243
172244#[ derive( Debug ) ]
173245enum IndexEntry < ' hir > {
174246 /// `assert!` without any indexing (so far)
175247 StrayAssert {
176- asserted_len : usize ,
248+ asserted_len : EvaluatedIntExpr < ' hir > ,
177249 comparison : LengthComparison ,
178250 assert_span : Span ,
179251 slice : & ' hir Expr < ' hir > ,
@@ -186,7 +258,7 @@ enum IndexEntry<'hir> {
186258 AssertWithIndex {
187259 highest_index : usize ,
188260 is_first_highest : bool ,
189- asserted_len : usize ,
261+ asserted_len : EvaluatedIntExpr < ' hir > ,
190262 assert_span : Span ,
191263 slice : & ' hir Expr < ' hir > ,
192264 indexes : Vec < Span > ,
@@ -367,22 +439,29 @@ fn report_indexes(cx: &LateContext<'_>, map: UnindexMap<u64, Vec<IndexEntry<'_>>
367439 Some ( format ! ( "assert!({slice_str}.len() > {highest_index})" , ) )
368440 } ,
369441 // `5 < v.len()` == `v.len() > 5`
370- LengthComparison :: IntLessThanLength if asserted_len < highest_index => {
442+ LengthComparison :: IntLessThanLength if asserted_len. value < highest_index => {
371443 Some ( format ! ( "assert!({slice_str}.len() > {highest_index})" , ) )
372444 } ,
373445 // `5 <= v.len() == `v.len() >= 5`
374- LengthComparison :: IntLessThanOrEqualLength if asserted_len <= highest_index => {
375- Some ( format ! ( "assert!({slice_str}.len() > {highest_index})" , ) )
446+ LengthComparison :: IntLessThanOrEqualLength if asserted_len. value == highest_index => {
447+ let asserted_len_str =
448+ snippet_with_applicability ( cx, asserted_len. expr . span , "_" , & mut app) ;
449+ Some ( format ! ( "assert!({slice_str}.len() > {asserted_len_str})" , ) )
450+ } ,
451+ LengthComparison :: IntLessThanOrEqualLength if asserted_len. value < highest_index => {
452+ Some ( format ! ( "assert!({slice_str}.len() >= {})" , highest_index + 1 ) )
376453 } ,
377454 // `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 ) ) ,
455+ LengthComparison :: LengthEqualInt if asserted_len. value < highest_index + 1 => {
456+ match macro_call {
457+ sym:: assert_eq_macro => {
458+ Some ( format ! ( "assert_eq!({slice_str}.len(), {})" , highest_index + 1 ) )
459+ } ,
460+ sym:: debug_assert_eq_macro => {
461+ Some ( format ! ( "debug_assert_eq!({slice_str}.len(), {})" , highest_index + 1 ) )
462+ } ,
463+ _ => Some ( format ! ( "assert!({slice_str}.len() == {})" , highest_index + 1 ) ) ,
464+ }
386465 } ,
387466 _ => None ,
388467 } ;
0 commit comments