Fix sliced_string_as_bytes FP with a RangeFull (#15873)

changelog: [`sliced_string_as_bytes`]: don't fire on
`str[..].as_bytes()`

So I ran into this in some codebase I was working on,
where the lint fired on this line:
```rust
let string: &str;
string[..].as_bytes()
```
So I was trying to understand the rationale behind this lint, and it
says:
> It involves doing an unnecessary UTF-8 alignment check which is less
efficient, and can cause a panic.

This is obviously not true in the case where a `RangeFull` slice is
being taken, since there is no UTF-8 boundary check, and no panic can be
caused. So I created an exemption for `RangeFull`s.

Two other notes:
1. I'm not sure the word "alignment" in the lint's description (quoted
above) is really correct, should probably say "char boundary" instead?
2. I might be missing something, but isn't there a lint for doing
superfluous slice indexing, and then calling a slice method? e.g.
`str[..].len()` or `slice[..].len()`, where `str` and `slice` are `&str`
and `&[T]`, respectively. If we had one, I would expect *it* to fire for
the example code I quoted above.
This commit is contained in:
llogiq 2025-11-15 08:53:12 +00:00 committed by GitHub
commit cd61be7c56
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 20 additions and 2 deletions

View file

@ -1,15 +1,21 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::higher;
use clippy_utils::res::MaybeDef;
use clippy_utils::source::snippet_with_applicability;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, LangItem, is_range_literal};
use rustc_hir::{Expr, ExprKind, LangItem};
use rustc_lint::LateContext;
use super::SLICED_STRING_AS_BYTES;
/// Checks if `index` is any type of range except `RangeFull` (i.e. `..`)
fn is_bounded_range_literal(cx: &LateContext<'_>, index: &Expr<'_>) -> bool {
higher::Range::hir(cx, index).is_some_and(|range| Option::or(range.start, range.end).is_some())
}
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>) {
if let ExprKind::Index(indexed, index, _) = recv.kind
&& is_range_literal(index)
&& is_bounded_range_literal(cx, index)
&& let ty = cx.typeck_results().expr_ty(indexed).peel_refs()
&& (ty.is_str() || ty.is_lang_item(cx, LangItem::String))
{

View file

@ -32,6 +32,12 @@ fn main() {
let bytes = &"consectetur adipiscing".as_bytes()[..=5];
//~^ sliced_string_as_bytes
// this lint is a perf lint meant to catch utf-8 alignment checks.
// while the slicing here *is* redundant, it's more like a needless borrow, and shouldn't affect
// perf
let bytes = s[..].as_bytes();
let bytes = string[..].as_bytes();
let f = Foo;
let bytes = f[0..4].as_bytes();
}

View file

@ -32,6 +32,12 @@ fn main() {
let bytes = "consectetur adipiscing"[..=5].as_bytes();
//~^ sliced_string_as_bytes
// this lint is a perf lint meant to catch utf-8 alignment checks.
// while the slicing here *is* redundant, it's more like a needless borrow, and shouldn't affect
// perf
let bytes = s[..].as_bytes();
let bytes = string[..].as_bytes();
let f = Foo;
let bytes = f[0..4].as_bytes();
}