@@ -91,6 +91,9 @@ enum LengthComparison {
91
91
IntLessThanOrEqualLength ,
92
92
}
93
93
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()`))`
94
97
fn len_comparison < ' hir > (
95
98
bin_op : BinOp ,
96
99
left : & ' hir Expr < ' hir > ,
@@ -105,7 +108,7 @@ fn len_comparison<'hir>(
105
108
} ;
106
109
}
107
110
108
- // normalize comparison, `v.len() > 4` becomes `4 <= v.len()`
111
+ // normalize comparison, `v.len() > 4` becomes `4 < v.len()`
109
112
// this simplifies the logic a bit
110
113
let ( op, left, right) = normalize_comparison ( bin_op. node , left, right) ?;
111
114
match ( op, & left. kind , & right. kind ) {
@@ -117,6 +120,8 @@ fn len_comparison<'hir>(
117
120
}
118
121
}
119
122
123
+ /// Attempts to extract parts out of an `assert!`-like expression
124
+ /// in the form `assert!(some_slice.len() > 5)`.
120
125
fn assert_len_expr < ' hir > (
121
126
cx : & LateContext < ' _ > ,
122
127
expr : & ' hir Expr < ' hir > ,
@@ -142,12 +147,17 @@ fn assert_len_expr<'hir>(
142
147
143
148
#[ derive( Debug ) ]
144
149
enum IndexEntry {
150
+ /// `assert!` without any indexing (so far)
145
151
StrayAssert {
146
152
asserted_len : usize ,
147
153
cmp : LengthComparison ,
148
154
assert_span : Span ,
149
155
slice_span : Span ,
150
156
} ,
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.
151
161
AssertWithIndex {
152
162
highest_index : usize ,
153
163
asserted_len : usize ,
@@ -156,7 +166,8 @@ enum IndexEntry {
156
166
indexes : Vec < Span > ,
157
167
cmp : LengthComparison ,
158
168
} ,
159
- AssertWithoutIndex {
169
+ /// Indexing without an `assert!`
170
+ IndexWithoutAssert {
160
171
highest_index : usize ,
161
172
indexes : Vec < Span > ,
162
173
slice_span : Span ,
@@ -167,13 +178,17 @@ impl IndexEntry {
167
178
pub fn spans ( & self ) -> Option < & [ Span ] > {
168
179
match self {
169
180
IndexEntry :: StrayAssert { .. } => None ,
170
- IndexEntry :: AssertWithIndex { indexes, .. } | IndexEntry :: AssertWithoutIndex { indexes, .. } => {
181
+ IndexEntry :: AssertWithIndex { indexes, .. } | IndexEntry :: IndexWithoutAssert { indexes, .. } => {
171
182
Some ( indexes)
172
183
} ,
173
184
}
174
185
}
175
186
}
176
187
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)`
177
192
fn upper_index_expr ( expr : & Expr < ' _ > ) -> Option < usize > {
178
193
if let ExprKind :: Lit ( lit) = & expr. kind && let LitKind :: Int ( index, _) = lit. node {
179
194
Some ( index as usize )
@@ -190,35 +205,36 @@ fn upper_index_expr(expr: &Expr<'_>) -> Option<usize> {
190
205
}
191
206
}
192
207
208
+ /// Checks if the expression is an index into a slice and adds it to the `indexes` map
193
209
fn check_index ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > , indexes : & mut FxHashMap < u64 , IndexEntry > ) {
194
210
if let ExprKind :: Index ( target, index_lit) = expr. kind
195
211
&& cx. typeck_results ( ) . expr_ty ( target) . peel_refs ( ) . is_slice ( )
196
212
&& let Some ( index) = upper_index_expr ( index_lit)
197
213
{
198
214
let hash = hash_expr ( cx, target) ;
199
215
let entry = indexes. entry ( hash)
200
- . or_insert_with ( || IndexEntry :: AssertWithoutIndex {
216
+ . or_insert_with ( || IndexEntry :: IndexWithoutAssert {
201
217
highest_index : index,
202
218
slice_span : target. span ,
203
219
indexes : Vec :: new ( ) ,
204
220
} ) ;
205
221
206
222
match entry {
207
- IndexEntry :: StrayAssert { asserted_len, cmp, assert_span, slice_span : receiver_span } => {
223
+ IndexEntry :: StrayAssert { asserted_len, cmp, assert_span, slice_span } => {
208
224
let asserted_len = * asserted_len;
209
225
let cmp = * cmp;
210
226
let assert_span = * assert_span;
211
- let receiver_span = * receiver_span ;
227
+ let slice_span = * slice_span ;
212
228
* entry = IndexEntry :: AssertWithIndex {
213
229
highest_index : index,
214
230
asserted_len,
215
231
indexes : vec ! [ expr. span] ,
216
232
assert_span,
217
233
cmp,
218
- slice_span : receiver_span
234
+ slice_span
219
235
} ;
220
236
}
221
- IndexEntry :: AssertWithoutIndex { highest_index, indexes, .. }
237
+ IndexEntry :: IndexWithoutAssert { highest_index, indexes, .. }
222
238
| IndexEntry :: AssertWithIndex { highest_index, indexes, .. } => {
223
239
indexes. push ( expr. span ) ;
224
240
* highest_index = ( * highest_index) . max ( index) ;
@@ -227,6 +243,7 @@ fn check_index(cx: &LateContext<'_>, expr: &Expr<'_>, indexes: &mut FxHashMap<u6
227
243
}
228
244
}
229
245
246
+ /// Checks if the expression is an `assert!` expression and adds it to the `indexes` map
230
247
fn check_assert ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > , indexes : & mut FxHashMap < u64 , IndexEntry > ) {
231
248
if let Some ( ( cmp, asserted_len, slice) ) = assert_len_expr ( cx, expr) {
232
249
let hash = hash_expr ( cx, slice) ;
@@ -240,7 +257,7 @@ fn check_assert(cx: &LateContext<'_>, expr: &Expr<'_>, indexes: &mut FxHashMap<u
240
257
} ) ;
241
258
} ,
242
259
Entry :: Occupied ( mut entry) => {
243
- if let IndexEntry :: AssertWithoutIndex {
260
+ if let IndexEntry :: IndexWithoutAssert {
244
261
highest_index, indexes, ..
245
262
} = entry. get_mut ( )
246
263
{
@@ -261,6 +278,9 @@ fn check_assert(cx: &LateContext<'_>, expr: &Expr<'_>, indexes: &mut FxHashMap<u
261
278
}
262
279
}
263
280
281
+ /// Inspects indexes and reports lints.
282
+ ///
283
+ /// Called at the end of this lint after all indexing and `assert!` expressions have been collected.
264
284
fn report_indexes ( cx : & LateContext < ' _ > , indexes : & mut FxHashMap < u64 , IndexEntry > ) {
265
285
for entry in indexes. values ( ) {
266
286
let Some ( full_span) = entry
@@ -280,7 +300,7 @@ fn report_indexes(cx: &LateContext<'_>, indexes: &mut FxHashMap<u64, IndexEntry>
280
300
slice_span : receiver_span,
281
301
} if indexes. len ( ) > 1 => {
282
302
// 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
284
304
let sugg = match cmp {
285
305
// `v.len() < 5` and `v.len() <= 5` does nothing in terms of bounds checks.
286
306
// The user probably meant `v.len() > 5`
@@ -318,11 +338,13 @@ fn report_indexes(cx: &LateContext<'_>, indexes: &mut FxHashMap<u64, IndexEntry>
318
338
) ;
319
339
}
320
340
} ,
321
- IndexEntry :: AssertWithoutIndex {
341
+ IndexEntry :: IndexWithoutAssert {
322
342
indexes,
323
343
highest_index,
324
344
slice_span : receiver_span,
325
345
} 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
326
348
report_lint (
327
349
cx,
328
350
full_span,
0 commit comments