diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bdbc91db93..b1f26373e54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index d8918d37afa..7da3ac9f2fd 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -424,6 +424,7 @@ 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, diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 722290fb68e..f54221fff2a 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -67,6 +67,7 @@ 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; @@ -4166,6 +4167,31 @@ "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, @@ -4327,6 +4353,7 @@ pub fn new(conf: &'static Conf, format_args: FormatArgsStorage) -> Self { NEEDLESS_CHARACTER_ITERATION, MANUAL_INSPECT, UNNECESSARY_MIN_OR_MAX, + NEEDLESS_AS_BYTES, ]); /// Extracts a method call name, args, and `Span` of the method name. @@ -4764,8 +4791,14 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { 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); }, @@ -4795,6 +4828,11 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { ); } }, + ("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); }, diff --git a/clippy_lints/src/methods/needless_as_bytes.rs b/clippy_lints/src/methods/needless_as_bytes.rs new file mode 100644 index 00000000000..75e9f317230 --- /dev/null +++ b/clippy_lints/src/methods/needless_as_bytes.rs @@ -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, + ); + } +} diff --git a/tests/ui/needless_as_bytes.fixed b/tests/ui/needless_as_bytes.fixed new file mode 100644 index 00000000000..042342311fd --- /dev/null +++ b/tests/ui/needless_as_bytes.fixed @@ -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] { + &[] + } +} diff --git a/tests/ui/needless_as_bytes.rs b/tests/ui/needless_as_bytes.rs new file mode 100644 index 00000000000..c481e041e0a --- /dev/null +++ b/tests/ui/needless_as_bytes.rs @@ -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() { + //~^ 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() { + //~^ 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] { + &[] + } +} diff --git a/tests/ui/needless_as_bytes.stderr b/tests/ui/needless_as_bytes.stderr new file mode 100644 index 00000000000..3391238a142 --- /dev/null +++ b/tests/ui/needless_as_bytes.stderr @@ -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 +