@@ -91,6 +91,9 @@ enum LengthComparison {
9191 IntLessThanOrEqualLength ,
9292}
9393
94+ /// Extracts parts out of a length comparison expression.
95+ ///
96+ /// E.g. for `v.len() > 5` this returns `Some((LengthComparison::IntLessThanLength, 5, `v.len()`))`
9497fn len_comparison < ' hir > (
9598 bin_op : BinOp ,
9699 left : & ' hir Expr < ' hir > ,
@@ -105,7 +108,7 @@ fn len_comparison<'hir>(
105108 } ;
106109 }
107110
108- // normalize comparison, `v.len() > 4` becomes `4 <= v.len()`
111+ // normalize comparison, `v.len() > 4` becomes `4 < v.len()`
109112 // this simplifies the logic a bit
110113 let ( op, left, right) = normalize_comparison ( bin_op. node , left, right) ?;
111114 match ( op, & left. kind , & right. kind ) {
@@ -117,6 +120,8 @@ fn len_comparison<'hir>(
117120 }
118121}
119122
123+ /// Attempts to extract parts out of an `assert!`-like expression
124+ /// in the form `assert!(some_slice.len() > 5)`.
120125fn assert_len_expr < ' hir > (
121126 cx : & LateContext < ' _ > ,
122127 expr : & ' hir Expr < ' hir > ,
@@ -142,12 +147,17 @@ fn assert_len_expr<'hir>(
142147
143148#[ derive( Debug ) ]
144149enum IndexEntry {
150+ /// `assert!` without any indexing (so far)
145151 StrayAssert {
146152 asserted_len : usize ,
147153 cmp : LengthComparison ,
148154 assert_span : Span ,
149155 slice_span : Span ,
150156 } ,
157+ /// `assert!` with indexing
158+ ///
159+ /// We also store the highest index to be able to check
160+ /// if the `assert!` asserts the right length.
151161 AssertWithIndex {
152162 highest_index : usize ,
153163 asserted_len : usize ,
@@ -156,7 +166,8 @@ enum IndexEntry {
156166 indexes : Vec < Span > ,
157167 cmp : LengthComparison ,
158168 } ,
159- AssertWithoutIndex {
169+ /// Indexing without an `assert!`
170+ IndexWithoutAssert {
160171 highest_index : usize ,
161172 indexes : Vec < Span > ,
162173 slice_span : Span ,
@@ -167,13 +178,17 @@ impl IndexEntry {
167178 pub fn spans ( & self ) -> Option < & [ Span ] > {
168179 match self {
169180 IndexEntry :: StrayAssert { .. } => None ,
170- IndexEntry :: AssertWithIndex { indexes, .. } | IndexEntry :: AssertWithoutIndex { indexes, .. } => {
181+ IndexEntry :: AssertWithIndex { indexes, .. } | IndexEntry :: IndexWithoutAssert { indexes, .. } => {
171182 Some ( indexes)
172183 } ,
173184 }
174185 }
175186}
176187
188+ /// Extracts the upper index of a slice indexing expression.
189+ ///
190+ /// E.g. for `5` this returns `Some(5)`, for `..5` this returns `Some(4)`,
191+ /// for `..=5` this returns `Some(5)`
177192fn upper_index_expr ( expr : & Expr < ' _ > ) -> Option < usize > {
178193 if let ExprKind :: Lit ( lit) = & expr. kind && let LitKind :: Int ( index, _) = lit. node {
179194 Some ( index as usize )
@@ -190,35 +205,36 @@ fn upper_index_expr(expr: &Expr<'_>) -> Option<usize> {
190205 }
191206}
192207
208+ /// Checks if the expression is an index into a slice and adds it to the `indexes` map
193209fn check_index ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > , indexes : & mut FxHashMap < u64 , IndexEntry > ) {
194210 if let ExprKind :: Index ( target, index_lit) = expr. kind
195211 && cx. typeck_results ( ) . expr_ty ( target) . peel_refs ( ) . is_slice ( )
196212 && let Some ( index) = upper_index_expr ( index_lit)
197213 {
198214 let hash = hash_expr ( cx, target) ;
199215 let entry = indexes. entry ( hash)
200- . or_insert_with ( || IndexEntry :: AssertWithoutIndex {
216+ . or_insert_with ( || IndexEntry :: IndexWithoutAssert {
201217 highest_index : index,
202218 slice_span : target. span ,
203219 indexes : Vec :: new ( ) ,
204220 } ) ;
205221
206222 match entry {
207- IndexEntry :: StrayAssert { asserted_len, cmp, assert_span, slice_span : receiver_span } => {
223+ IndexEntry :: StrayAssert { asserted_len, cmp, assert_span, slice_span } => {
208224 let asserted_len = * asserted_len;
209225 let cmp = * cmp;
210226 let assert_span = * assert_span;
211- let receiver_span = * receiver_span ;
227+ let slice_span = * slice_span ;
212228 * entry = IndexEntry :: AssertWithIndex {
213229 highest_index : index,
214230 asserted_len,
215231 indexes : vec ! [ expr. span] ,
216232 assert_span,
217233 cmp,
218- slice_span : receiver_span
234+ slice_span
219235 } ;
220236 }
221- IndexEntry :: AssertWithoutIndex { highest_index, indexes, .. }
237+ IndexEntry :: IndexWithoutAssert { highest_index, indexes, .. }
222238 | IndexEntry :: AssertWithIndex { highest_index, indexes, .. } => {
223239 indexes. push ( expr. span ) ;
224240 * highest_index = ( * highest_index) . max ( index) ;
@@ -227,6 +243,7 @@ fn check_index(cx: &LateContext<'_>, expr: &Expr<'_>, indexes: &mut FxHashMap<u6
227243 }
228244}
229245
246+ /// Checks if the expression is an `assert!` expression and adds it to the `indexes` map
230247fn check_assert ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > , indexes : & mut FxHashMap < u64 , IndexEntry > ) {
231248 if let Some ( ( cmp, asserted_len, slice) ) = assert_len_expr ( cx, expr) {
232249 let hash = hash_expr ( cx, slice) ;
@@ -240,7 +257,7 @@ fn check_assert(cx: &LateContext<'_>, expr: &Expr<'_>, indexes: &mut FxHashMap<u
240257 } ) ;
241258 } ,
242259 Entry :: Occupied ( mut entry) => {
243- if let IndexEntry :: AssertWithoutIndex {
260+ if let IndexEntry :: IndexWithoutAssert {
244261 highest_index, indexes, ..
245262 } = entry. get_mut ( )
246263 {
@@ -261,6 +278,9 @@ fn check_assert(cx: &LateContext<'_>, expr: &Expr<'_>, indexes: &mut FxHashMap<u
261278 }
262279}
263280
281+ /// Inspects indexes and reports lints.
282+ ///
283+ /// Called at the end of this lint after all indexing and `assert!` expressions have been collected.
264284fn report_indexes ( cx : & LateContext < ' _ > , indexes : & mut FxHashMap < u64 , IndexEntry > ) {
265285 for entry in indexes. values ( ) {
266286 let Some ( full_span) = entry
@@ -280,7 +300,7 @@ fn report_indexes(cx: &LateContext<'_>, indexes: &mut FxHashMap<u64, IndexEntry>
280300 slice_span : receiver_span,
281301 } if indexes. len ( ) > 1 => {
282302 // if we have found an `assert!`, let's also check that it's actually right
283- // and convers the highest index and if not, suggest the correct length
303+ // and if it convers the highest index and if not, suggest the correct length
284304 let sugg = match cmp {
285305 // `v.len() < 5` and `v.len() <= 5` does nothing in terms of bounds checks.
286306 // The user probably meant `v.len() > 5`
@@ -318,11 +338,13 @@ fn report_indexes(cx: &LateContext<'_>, indexes: &mut FxHashMap<u64, IndexEntry>
318338 ) ;
319339 }
320340 } ,
321- IndexEntry :: AssertWithoutIndex {
341+ IndexEntry :: IndexWithoutAssert {
322342 indexes,
323343 highest_index,
324344 slice_span : receiver_span,
325345 } if indexes. len ( ) > 1 => {
346+ // if there was no `assert!` but more than one index, suggest
347+ // adding an `assert!` that covers the highest index
326348 report_lint (
327349 cx,
328350 full_span,
0 commit comments