Skip to content

Commit d5d644f

Browse files
authored
refactor(missing_asserts_for_indexing): overhaul diagnostics (#16120)
changelog: [`missing_asserts_for_indexing`]: overhaul diagnostics
2 parents 6278cc8 + 129a39d commit d5d644f

File tree

3 files changed

+114
-494
lines changed

3 files changed

+114
-494
lines changed

clippy_lints/src/missing_asserts_for_indexing.rs

Lines changed: 32 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use clippy_utils::comparisons::{Rel, normalize_comparison};
55
use clippy_utils::diagnostics::span_lint_and_then;
66
use clippy_utils::higher::{If, Range};
77
use clippy_utils::macros::{find_assert_eq_args, first_node_macro_backtrace, root_macro_call};
8-
use clippy_utils::source::snippet;
8+
use clippy_utils::source::{snippet, snippet_with_applicability};
99
use clippy_utils::visitors::for_each_expr_without_closures;
1010
use clippy_utils::{eq_expr_value, hash_expr};
1111
use rustc_ast::{BinOpKind, LitKind, RangeLimits};
@@ -67,16 +67,13 @@ declare_clippy_lint! {
6767
}
6868
declare_lint_pass!(MissingAssertsForIndexing => [MISSING_ASSERTS_FOR_INDEXING]);
6969

70-
fn report_lint<F>(cx: &LateContext<'_>, full_span: Span, msg: &'static str, indexes: &[Span], f: F)
70+
fn report_lint<F>(cx: &LateContext<'_>, index_spans: Vec<Span>, msg: &'static str, f: F)
7171
where
7272
F: FnOnce(&mut Diag<'_, ()>),
7373
{
74-
span_lint_and_then(cx, MISSING_ASSERTS_FOR_INDEXING, full_span, msg, |diag| {
74+
span_lint_and_then(cx, MISSING_ASSERTS_FOR_INDEXING, index_spans, msg, |diag| {
7575
f(diag);
76-
for span in indexes {
77-
diag.span_note(*span, "slice indexed here");
78-
}
79-
diag.note("asserting the length before indexing will elide bounds checks");
76+
diag.note_once("asserting the length before indexing will elide bounds checks");
8077
});
8178
}
8279

@@ -213,15 +210,6 @@ impl<'hir> IndexEntry<'hir> {
213210
| IndexEntry::IndexWithoutAssert { slice, .. } => slice,
214211
}
215212
}
216-
217-
pub fn index_spans(&self) -> Option<&[Span]> {
218-
match self {
219-
IndexEntry::StrayAssert { .. } => None,
220-
IndexEntry::AssertWithIndex { indexes, .. } | IndexEntry::IndexWithoutAssert { indexes, .. } => {
221-
Some(indexes)
222-
},
223-
}
224-
}
225213
}
226214

227215
/// Extracts the upper index of a slice indexing expression.
@@ -354,86 +342,69 @@ fn check_assert<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut Un
354342
/// Inspects indexes and reports lints.
355343
///
356344
/// Called at the end of this lint after all indexing and `assert!` expressions have been collected.
357-
fn report_indexes(cx: &LateContext<'_>, map: &UnindexMap<u64, Vec<IndexEntry<'_>>>) {
358-
for bucket in map.values() {
345+
fn report_indexes(cx: &LateContext<'_>, map: UnindexMap<u64, Vec<IndexEntry<'_>>>) {
346+
for bucket in map.into_values() {
359347
for entry in bucket {
360-
let Some(full_span) = entry
361-
.index_spans()
362-
.and_then(|spans| spans.first().zip(spans.last()))
363-
.map(|(low, &high)| low.to(high))
364-
else {
365-
continue;
366-
};
367-
368-
match *entry {
348+
match entry {
369349
IndexEntry::AssertWithIndex {
370350
highest_index,
371351
is_first_highest,
372352
asserted_len,
373-
ref indexes,
353+
indexes,
374354
comparison,
375355
assert_span,
376356
slice,
377357
macro_call,
378358
} if indexes.len() > 1 && !is_first_highest => {
359+
let mut app = Applicability::MachineApplicable;
360+
let slice_str = snippet_with_applicability(cx, slice.span, "_", &mut app);
379361
// if we have found an `assert!`, let's also check that it's actually right
380362
// and if it covers the highest index and if not, suggest the correct length
381363
let sugg = match comparison {
382364
// `v.len() < 5` and `v.len() <= 5` does nothing in terms of bounds checks.
383365
// The user probably meant `v.len() > 5`
384-
LengthComparison::LengthLessThanInt | LengthComparison::LengthLessThanOrEqualInt => Some(
385-
format!("assert!({}.len() > {highest_index})", snippet(cx, slice.span, "..")),
386-
),
366+
LengthComparison::LengthLessThanInt | LengthComparison::LengthLessThanOrEqualInt => {
367+
Some(format!("assert!({slice_str}.len() > {highest_index})",))
368+
},
387369
// `5 < v.len()` == `v.len() > 5`
388-
LengthComparison::IntLessThanLength if asserted_len < highest_index => Some(format!(
389-
"assert!({}.len() > {highest_index})",
390-
snippet(cx, slice.span, "..")
391-
)),
370+
LengthComparison::IntLessThanLength if asserted_len < highest_index => {
371+
Some(format!("assert!({slice_str}.len() > {highest_index})",))
372+
},
392373
// `5 <= v.len() == `v.len() >= 5`
393-
LengthComparison::IntLessThanOrEqualLength if asserted_len <= highest_index => Some(format!(
394-
"assert!({}.len() > {highest_index})",
395-
snippet(cx, slice.span, "..")
396-
)),
374+
LengthComparison::IntLessThanOrEqualLength if asserted_len <= highest_index => {
375+
Some(format!("assert!({slice_str}.len() > {highest_index})",))
376+
},
397377
// `highest_index` here is rather a length, so we need to add 1 to it
398378
LengthComparison::LengthEqualInt if asserted_len < highest_index + 1 => match macro_call {
399-
sym::assert_eq_macro => Some(format!(
400-
"assert_eq!({}.len(), {})",
401-
snippet(cx, slice.span, ".."),
402-
highest_index + 1
403-
)),
404-
sym::debug_assert_eq_macro => Some(format!(
405-
"debug_assert_eq!({}.len(), {})",
406-
snippet(cx, slice.span, ".."),
407-
highest_index + 1
408-
)),
409-
_ => Some(format!(
410-
"assert!({}.len() == {})",
411-
snippet(cx, slice.span, ".."),
412-
highest_index + 1
413-
)),
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)),
414386
},
415387
_ => None,
416388
};
417389

418390
if let Some(sugg) = sugg {
419391
report_lint(
420392
cx,
421-
full_span,
422-
"indexing into a slice multiple times with an `assert` that does not cover the highest index",
423393
indexes,
394+
"indexing into a slice multiple times with an `assert` that does not cover the highest index",
424395
|diag| {
425-
diag.span_suggestion(
396+
diag.span_suggestion_verbose(
426397
assert_span,
427398
"provide the highest index that is indexed with",
428399
sugg,
429-
Applicability::MachineApplicable,
400+
app,
430401
);
431402
},
432403
);
433404
}
434405
},
435406
IndexEntry::IndexWithoutAssert {
436-
ref indexes,
407+
indexes,
437408
highest_index,
438409
is_first_highest,
439410
slice,
@@ -442,9 +413,8 @@ fn report_indexes(cx: &LateContext<'_>, map: &UnindexMap<u64, Vec<IndexEntry<'_>
442413
// adding an `assert!` that covers the highest index
443414
report_lint(
444415
cx,
445-
full_span,
446-
"indexing into a slice multiple times without an `assert`",
447416
indexes,
417+
"indexing into a slice multiple times without an `assert`",
448418
|diag| {
449419
diag.help(format!(
450420
"consider asserting the length before indexing: `assert!({}.len() > {highest_index});`",
@@ -469,6 +439,6 @@ impl LateLintPass<'_> for MissingAssertsForIndexing {
469439
ControlFlow::<!, ()>::Continue(())
470440
});
471441

472-
report_indexes(cx, &map);
442+
report_indexes(cx, map);
473443
}
474444
}

0 commit comments

Comments
 (0)