Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New lint needless_as_bytes #13437

Merged
merged 2 commits into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5761,6 +5761,7 @@ Released 2018-09-13
[`mutex_integer`]: https://rust-lang.github.io/rust-clippy/master/index.html#mutex_integer
[`naive_bytecount`]: https://rust-lang.github.io/rust-clippy/master/index.html#naive_bytecount
[`needless_arbitrary_self_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_arbitrary_self_type
[`needless_as_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_as_bytes
[`needless_bitwise_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_bitwise_bool
[`needless_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_bool
[`needless_bool_assign`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_bool_assign
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code.

[There are over 700 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
[There are over 750 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)

Lints are divided into categories, each with a default [lint level](https://doc.rust-lang.org/rustc/lints/levels.html).
You can choose how much Clippy is supposed to ~~annoy~~ help you by changing the lint level by category.
Expand Down
2 changes: 1 addition & 1 deletion book/src/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
A collection of lints to catch common mistakes and improve your
[Rust](https://github.com/rust-lang/rust) code.

[There are over 700 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
[There are over 750 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)

Lints are divided into categories, each with a default [lint
level](https://doc.rust-lang.org/rustc/lints/levels.html). You can choose how
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/declared_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::methods::MAP_UNWRAP_OR_INFO,
crate::methods::MUT_MUTEX_LOCK_INFO,
crate::methods::NAIVE_BYTECOUNT_INFO,
crate::methods::NEEDLESS_AS_BYTES_INFO,
crate::methods::NEEDLESS_CHARACTER_ITERATION_INFO,
crate::methods::NEEDLESS_COLLECT_INFO,
crate::methods::NEEDLESS_OPTION_AS_DEREF_INFO,
Expand Down
42 changes: 40 additions & 2 deletions clippy_lints/src/methods/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ mod map_flatten;
mod map_identity;
mod map_unwrap_or;
mod mut_mutex_lock;
mod needless_as_bytes;
mod needless_character_iteration;
mod needless_collect;
mod needless_option_as_deref;
Expand Down Expand Up @@ -4166,6 +4167,31 @@ declare_clippy_lint! {
"calling `.first().is_some()` or `.first().is_none()` instead of `.is_empty()`"
}

declare_clippy_lint! {
/// ### What it does
/// It detects useless calls to `str::as_bytes()` before calling `len()` or `is_empty()`.
///
/// ### Why is this bad?
/// The `len()` and `is_empty()` methods are also directly available on strings, and they
/// return identical results. In particular, `len()` on a string returns the number of
/// bytes.
///
/// ### Example
/// ```
/// let len = "some string".as_bytes().len();
/// let b = "some string".as_bytes().is_empty();
/// ```
/// Use instead:
/// ```
/// let len = "some string".len();
/// let b = "some string".is_empty();
/// ```
#[clippy::version = "1.84.0"]
pub NEEDLESS_AS_BYTES,
complexity,
"detect useless calls to `as_bytes()`"
}

pub struct Methods {
avoid_breaking_exported_api: bool,
msrv: Msrv,
Expand Down Expand Up @@ -4327,6 +4353,7 @@ impl_lint_pass!(Methods => [
NEEDLESS_CHARACTER_ITERATION,
MANUAL_INSPECT,
UNNECESSARY_MIN_OR_MAX,
NEEDLESS_AS_BYTES,
]);

/// Extracts a method call name, args, and `Span` of the method name.
Expand Down Expand Up @@ -4764,8 +4791,14 @@ impl Methods {
unit_hash::check(cx, expr, recv, arg);
},
("is_empty", []) => {
if let Some(("as_str", recv, [], as_str_span, _)) = method_call(recv) {
redundant_as_str::check(cx, expr, recv, as_str_span, span);
match method_call(recv) {
Some(("as_bytes", prev_recv, [], _, _)) => {
needless_as_bytes::check(cx, "is_empty", recv, prev_recv, expr.span);
},
Some(("as_str", recv, [], as_str_span, _)) => {
redundant_as_str::check(cx, expr, recv, as_str_span, span);
},
_ => {},
}
is_empty::check(cx, expr, recv);
},
Expand Down Expand Up @@ -4795,6 +4828,11 @@ impl Methods {
);
}
},
("len", []) => {
if let Some(("as_bytes", prev_recv, [], _, _)) = method_call(recv) {
needless_as_bytes::check(cx, "len", recv, prev_recv, expr.span);
}
},
("lock", []) => {
mut_mutex_lock::check(cx, expr, recv, span);
},
Expand Down
28 changes: 28 additions & 0 deletions clippy_lints/src/methods/needless_as_bytes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::sugg::Sugg;
use clippy_utils::ty::is_type_lang_item;
use rustc_errors::Applicability;
use rustc_hir::{Expr, LangItem};
use rustc_lint::LateContext;
use rustc_span::Span;

use super::NEEDLESS_AS_BYTES;

pub fn check(cx: &LateContext<'_>, method: &str, recv: &Expr<'_>, prev_recv: &Expr<'_>, span: Span) {
if cx.typeck_results().expr_ty_adjusted(recv).peel_refs().is_slice()
&& let ty1 = cx.typeck_results().expr_ty_adjusted(prev_recv).peel_refs()
&& (is_type_lang_item(cx, ty1, LangItem::String) || ty1.is_str())
{
let mut app = Applicability::MachineApplicable;
let sugg = Sugg::hir_with_context(cx, prev_recv, span.ctxt(), "..", &mut app);
span_lint_and_sugg(
cx,
NEEDLESS_AS_BYTES,
span,
"needless call to `as_bytes()`",
format!("`{method}()` can be called directly on strings"),
format!("{sugg}.{method}()"),
app,
);
}
}
50 changes: 50 additions & 0 deletions tests/ui/needless_as_bytes.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#![warn(clippy::needless_as_bytes)]
#![allow(clippy::const_is_empty)]

struct S;

impl S {
fn as_bytes(&self) -> &[u8] {
&[]
}
}

fn main() {
if "some string".is_empty() {
//~^ needless_as_bytes
println!("len = {}", "some string".len());
//~^ needless_as_bytes
}

let s = String::from("yet another string");
if s.is_empty() {
//~^ needless_as_bytes
println!("len = {}", s.len());
//~^ needless_as_bytes
}

// Do not lint
let _ = S.as_bytes().is_empty();
let _ = S.as_bytes().len();
let _ = (&String::new() as &dyn AsBytes).as_bytes().len();
macro_rules! m {
(1) => {
""
};
(2) => {
"".as_bytes()
};
}
m!(1).as_bytes().len();
m!(2).len();
}

pub trait AsBytes {
fn as_bytes(&self) -> &[u8];
}

impl AsBytes for String {
fn as_bytes(&self) -> &[u8] {
&[]
}
}
50 changes: 50 additions & 0 deletions tests/ui/needless_as_bytes.rs
samueltardieu marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#![warn(clippy::needless_as_bytes)]
#![allow(clippy::const_is_empty)]

struct S;

impl S {
fn as_bytes(&self) -> &[u8] {
&[]
}
}

fn main() {
if "some string".as_bytes().is_empty() {
samueltardieu marked this conversation as resolved.
Show resolved Hide resolved
//~^ needless_as_bytes
println!("len = {}", "some string".as_bytes().len());
//~^ needless_as_bytes
}

let s = String::from("yet another string");
if s.as_bytes().is_empty() {
samueltardieu marked this conversation as resolved.
Show resolved Hide resolved
//~^ needless_as_bytes
println!("len = {}", s.as_bytes().len());
//~^ needless_as_bytes
}

// Do not lint
let _ = S.as_bytes().is_empty();
let _ = S.as_bytes().len();
let _ = (&String::new() as &dyn AsBytes).as_bytes().len();
macro_rules! m {
(1) => {
""
};
(2) => {
"".as_bytes()
};
}
m!(1).as_bytes().len();
m!(2).len();
}

pub trait AsBytes {
fn as_bytes(&self) -> &[u8];
}

impl AsBytes for String {
fn as_bytes(&self) -> &[u8] {
&[]
}
}
29 changes: 29 additions & 0 deletions tests/ui/needless_as_bytes.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
error: needless call to `as_bytes()`
--> tests/ui/needless_as_bytes.rs:13:8
|
LL | if "some string".as_bytes().is_empty() {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: `is_empty()` can be called directly on strings: `"some string".is_empty()`
|
= note: `-D clippy::needless-as-bytes` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::needless_as_bytes)]`

error: needless call to `as_bytes()`
--> tests/ui/needless_as_bytes.rs:15:30
|
LL | println!("len = {}", "some string".as_bytes().len());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: `len()` can be called directly on strings: `"some string".len()`

error: needless call to `as_bytes()`
--> tests/ui/needless_as_bytes.rs:20:8
|
LL | if s.as_bytes().is_empty() {
| ^^^^^^^^^^^^^^^^^^^^^^^ help: `is_empty()` can be called directly on strings: `s.is_empty()`

error: needless call to `as_bytes()`
--> tests/ui/needless_as_bytes.rs:22:30
|
LL | println!("len = {}", s.as_bytes().len());
| ^^^^^^^^^^^^^^^^^^ help: `len()` can be called directly on strings: `s.len()`

error: aborting due to 4 previous errors