about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2023-09-18 21:31:48 +0000
committerbors <bors@rust-lang.org>2023-09-18 21:31:48 +0000
commit5cba09c5767e22b637aaeb966ae7b58ba5a85338 (patch)
tree56047fe592f8ccca64b2ce373f77c0d4ca42439e
parent4022591cfd7890e3db8e477986d47a336cd230a7 (diff)
parentd9d25e98fca4085cfe062700f6ca465abc20a52d (diff)
downloadrust-5cba09c5767e22b637aaeb966ae7b58ba5a85338.tar.gz
rust-5cba09c5767e22b637aaeb966ae7b58ba5a85338.zip
Auto merge of #11526 - Dev380:redundant-as-str, r=Manishearth
Add redundant_as_str lint

This lint checks for `as_str` on a `String` immediately followed by `as_bytes` or `is_empty` as those methods are available on `String` too. This could possibly also be extended to `&[u8]` in the future.

changelog: New lint [`redundant_as_str`] #11526
-rw-r--r--CHANGELOG.md1
-rw-r--r--clippy_lints/src/declared_lints.rs1
-rw-r--r--clippy_lints/src/methods/mod.rs29
-rw-r--r--clippy_lints/src/methods/redundant_as_str.rs34
-rw-r--r--tests/ui/redundant_as_str.fixed24
-rw-r--r--tests/ui/redundant_as_str.rs24
-rw-r--r--tests/ui/redundant_as_str.stderr17
7 files changed, 130 insertions, 0 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a2484203e9e..8c9ab1e2402 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5281,6 +5281,7 @@ Released 2018-09-13
 [`readonly_write_lock`]: https://rust-lang.github.io/rust-clippy/master/index.html#readonly_write_lock
 [`recursive_format_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#recursive_format_impl
 [`redundant_allocation`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_allocation
+[`redundant_as_str`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_as_str
 [`redundant_async_block`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_async_block
 [`redundant_at_rest_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_at_rest_pattern
 [`redundant_clone`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_clone
diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs
index b1ffc347812..aa2ca0e1149 100644
--- a/clippy_lints/src/declared_lints.rs
+++ b/clippy_lints/src/declared_lints.rs
@@ -403,6 +403,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
     crate::methods::RANGE_ZIP_WITH_LEN_INFO,
     crate::methods::READONLY_WRITE_LOCK_INFO,
     crate::methods::READ_LINE_WITHOUT_TRIM_INFO,
+    crate::methods::REDUNDANT_AS_STR_INFO,
     crate::methods::REPEAT_ONCE_INFO,
     crate::methods::RESULT_MAP_OR_INTO_OPTION_INFO,
     crate::methods::SEARCH_IS_SOME_INFO,
diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs
index f4682364806..bd07e6cbf2e 100644
--- a/clippy_lints/src/methods/mod.rs
+++ b/clippy_lints/src/methods/mod.rs
@@ -78,6 +78,7 @@ mod path_ends_with_ext;
 mod range_zip_with_len;
 mod read_line_without_trim;
 mod readonly_write_lock;
+mod redundant_as_str;
 mod repeat_once;
 mod search_is_some;
 mod seek_from_current;
@@ -3605,6 +3606,32 @@ declare_clippy_lint! {
     "attempting to compare file extensions using `Path::ends_with`"
 }
 
+declare_clippy_lint! {
+    /// ### What it does
+    /// Checks for usage of `as_str()` on a `String`` chained with a method available on the `String` itself.
+    ///
+    /// ### Why is this bad?
+    /// The `as_str()` conversion is pointless and can be removed for simplicity and cleanliness.
+    ///
+    /// ### Example
+    /// ```rust
+    /// # #![allow(unused)]
+    /// let owned_string = "This is a string".to_owned();
+    /// owned_string.as_str().as_bytes();
+    /// ```
+    ///
+    /// Use instead:
+    /// ```rust
+    /// # #![allow(unused)]
+    /// let owned_string = "This is a string".to_owned();
+    /// owned_string.as_bytes();
+    /// ```
+    #[clippy::version = "1.74.0"]
+    pub REDUNDANT_AS_STR,
+    complexity,
+    "`as_str` used to call a method on `str` that is also available on `String`"
+}
+
 pub struct Methods {
     avoid_breaking_exported_api: bool,
     msrv: Msrv,
@@ -3749,6 +3776,7 @@ impl_lint_pass!(Methods => [
     READONLY_WRITE_LOCK,
     ITER_OUT_OF_BOUNDS,
     PATH_ENDS_WITH_EXT,
+    REDUNDANT_AS_STR,
 ]);
 
 /// Extracts a method call name, args, and `Span` of the method name.
@@ -3975,6 +4003,7 @@ impl Methods {
                 ("as_deref" | "as_deref_mut", []) => {
                     needless_option_as_deref::check(cx, expr, recv, name);
                 },
+                ("as_bytes" | "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); },
                 ("as_mut", []) => useless_asref::check(cx, expr, "as_mut", recv),
                 ("as_ref", []) => useless_asref::check(cx, expr, "as_ref", recv),
                 ("assume_init", []) => uninit_assumed_init::check(cx, expr, recv),
diff --git a/clippy_lints/src/methods/redundant_as_str.rs b/clippy_lints/src/methods/redundant_as_str.rs
new file mode 100644
index 00000000000..98cd6afc2b7
--- /dev/null
+++ b/clippy_lints/src/methods/redundant_as_str.rs
@@ -0,0 +1,34 @@
+use super::REDUNDANT_AS_STR;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use rustc_errors::Applicability;
+use rustc_hir::Expr;
+use rustc_lint::LateContext;
+use rustc_middle::query::Key;
+use rustc_span::Span;
+
+pub(super) fn check(
+    cx: &LateContext<'_>,
+    _expr: &Expr<'_>,
+    recv: &Expr<'_>,
+    as_str_span: Span,
+    other_method_span: Span,
+) {
+    if cx
+        .tcx
+        .lang_items()
+        .string()
+        .is_some_and(|id| Some(id) == cx.typeck_results().expr_ty(recv).ty_adt_id())
+    {
+        let mut applicability = Applicability::MachineApplicable;
+        span_lint_and_sugg(
+            cx,
+            REDUNDANT_AS_STR,
+            as_str_span.to(other_method_span),
+            "this `as_str` is redundant and can be removed as the method immediately following exists on `String` too",
+            "try",
+            snippet_with_applicability(cx, other_method_span, "..", &mut applicability).into_owned(),
+            applicability,
+        );
+    }
+}
diff --git a/tests/ui/redundant_as_str.fixed b/tests/ui/redundant_as_str.fixed
new file mode 100644
index 00000000000..a38523a7c79
--- /dev/null
+++ b/tests/ui/redundant_as_str.fixed
@@ -0,0 +1,24 @@
+#![warn(clippy::redundant_as_str)]
+
+fn main() {
+    let string = "Hello, world!".to_owned();
+
+    // These methods are redundant and the `as_str` can be removed
+    let _redundant = string.as_bytes();
+    let _redundant = string.is_empty();
+
+    // These methods don't use `as_str` when they are redundant
+    let _no_as_str = string.as_bytes();
+    let _no_as_str = string.is_empty();
+
+    // These methods are not redundant, and are equivelant to
+    // doing dereferencing the string and applying the method
+    let _not_redundant = string.as_str().escape_unicode();
+    let _not_redundant = string.as_str().trim();
+    let _not_redundant = string.as_str().split_whitespace();
+
+    // These methods don't use `as_str` and are applied on a `str` directly
+    let borrowed_str = "Hello, world!";
+    let _is_str = borrowed_str.as_bytes();
+    let _is_str = borrowed_str.is_empty();
+}
diff --git a/tests/ui/redundant_as_str.rs b/tests/ui/redundant_as_str.rs
new file mode 100644
index 00000000000..33adb609996
--- /dev/null
+++ b/tests/ui/redundant_as_str.rs
@@ -0,0 +1,24 @@
+#![warn(clippy::redundant_as_str)]
+
+fn main() {
+    let string = "Hello, world!".to_owned();
+
+    // These methods are redundant and the `as_str` can be removed
+    let _redundant = string.as_str().as_bytes();
+    let _redundant = string.as_str().is_empty();
+
+    // These methods don't use `as_str` when they are redundant
+    let _no_as_str = string.as_bytes();
+    let _no_as_str = string.is_empty();
+
+    // These methods are not redundant, and are equivelant to
+    // doing dereferencing the string and applying the method
+    let _not_redundant = string.as_str().escape_unicode();
+    let _not_redundant = string.as_str().trim();
+    let _not_redundant = string.as_str().split_whitespace();
+
+    // These methods don't use `as_str` and are applied on a `str` directly
+    let borrowed_str = "Hello, world!";
+    let _is_str = borrowed_str.as_bytes();
+    let _is_str = borrowed_str.is_empty();
+}
diff --git a/tests/ui/redundant_as_str.stderr b/tests/ui/redundant_as_str.stderr
new file mode 100644
index 00000000000..0ea42a94a81
--- /dev/null
+++ b/tests/ui/redundant_as_str.stderr
@@ -0,0 +1,17 @@
+error: this `as_str` is redundant and can be removed as the method immediately following exists on `String` too
+  --> $DIR/redundant_as_str.rs:7:29
+   |
+LL |     let _redundant = string.as_str().as_bytes();
+   |                             ^^^^^^^^^^^^^^^^^ help: try: `as_bytes`
+   |
+   = note: `-D clippy::redundant-as-str` implied by `-D warnings`
+   = help: to override `-D warnings` add `#[allow(clippy::redundant_as_str)]`
+
+error: this `as_str` is redundant and can be removed as the method immediately following exists on `String` too
+  --> $DIR/redundant_as_str.rs:8:29
+   |
+LL |     let _redundant = string.as_str().is_empty();
+   |                             ^^^^^^^^^^^^^^^^^ help: try: `is_empty`
+
+error: aborting due to 2 previous errors
+