about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2022-05-24 16:55:06 +0000
committerbors <bors@rust-lang.org>2022-05-24 16:55:06 +0000
commitfbb9e565e399e0e67ca1a8235776db8e2889d12b (patch)
tree1dbefd86e72274e79c97dc3f44d31074f3c59ed9
parent050cdd6689254db96b47ff86732e286839a67584 (diff)
parentea62347a5a4b952b416dddc8053a495c9238e7d7 (diff)
downloadrust-fbb9e565e399e0e67ca1a8235776db8e2889d12b.tar.gz
rust-fbb9e565e399e0e67ca1a8235776db8e2889d12b.zip
Auto merge of #8754 - guerinoni:no_effect_replace, r=llogiq
New lint `no_effect_replace`

Closes #1595

Signed-off-by: Federico Guerinoni <guerinoni.federico@gmail.com>

changelog: Add [`no_effect_replace`] lint.
-rw-r--r--CHANGELOG.md1
-rw-r--r--clippy_lints/src/lib.register_all.rs1
-rw-r--r--clippy_lints/src/lib.register_lints.rs1
-rw-r--r--clippy_lints/src/lib.register_suspicious.rs1
-rw-r--r--clippy_lints/src/methods/mod.rs23
-rw-r--r--clippy_lints/src/methods/no_effect_replace.rs47
-rw-r--r--tests/ui/no_effect_replace.rs51
-rw-r--r--tests/ui/no_effect_replace.stderr52
8 files changed, 177 insertions, 0 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 24204f86b5e..f4a362e6c19 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3613,6 +3613,7 @@ Released 2018-09-13
 [`new_without_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#new_without_default
 [`new_without_default_derive`]: https://rust-lang.github.io/rust-clippy/master/index.html#new_without_default_derive
 [`no_effect`]: https://rust-lang.github.io/rust-clippy/master/index.html#no_effect
+[`no_effect_replace`]: https://rust-lang.github.io/rust-clippy/master/index.html#no_effect_replace
 [`no_effect_underscore_binding`]: https://rust-lang.github.io/rust-clippy/master/index.html#no_effect_underscore_binding
 [`non_ascii_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_ascii_literal
 [`non_octal_unix_permissions`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_octal_unix_permissions
diff --git a/clippy_lints/src/lib.register_all.rs b/clippy_lints/src/lib.register_all.rs
index be5c478900f..3b8d11449d2 100644
--- a/clippy_lints/src/lib.register_all.rs
+++ b/clippy_lints/src/lib.register_all.rs
@@ -189,6 +189,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
     LintId::of(methods::NEEDLESS_OPTION_TAKE),
     LintId::of(methods::NEEDLESS_SPLITN),
     LintId::of(methods::NEW_RET_NO_SELF),
+    LintId::of(methods::NO_EFFECT_REPLACE),
     LintId::of(methods::OK_EXPECT),
     LintId::of(methods::OPTION_AS_REF_DEREF),
     LintId::of(methods::OPTION_FILTER_MAP),
diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs
index 5552ea8aa80..50c135bf74b 100644
--- a/clippy_lints/src/lib.register_lints.rs
+++ b/clippy_lints/src/lib.register_lints.rs
@@ -330,6 +330,7 @@ store.register_lints(&[
     methods::NEEDLESS_OPTION_TAKE,
     methods::NEEDLESS_SPLITN,
     methods::NEW_RET_NO_SELF,
+    methods::NO_EFFECT_REPLACE,
     methods::OK_EXPECT,
     methods::OPTION_AS_REF_DEREF,
     methods::OPTION_FILTER_MAP,
diff --git a/clippy_lints/src/lib.register_suspicious.rs b/clippy_lints/src/lib.register_suspicious.rs
index 2de49f1624a..20bf5a245b1 100644
--- a/clippy_lints/src/lib.register_suspicious.rs
+++ b/clippy_lints/src/lib.register_suspicious.rs
@@ -23,6 +23,7 @@ store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec!
     LintId::of(loops::EMPTY_LOOP),
     LintId::of(loops::FOR_LOOPS_OVER_FALLIBLES),
     LintId::of(loops::MUT_RANGE_BOUND),
+    LintId::of(methods::NO_EFFECT_REPLACE),
     LintId::of(methods::SUSPICIOUS_MAP),
     LintId::of(mut_key::MUTABLE_KEY_TYPE),
     LintId::of(octal_escapes::OCTAL_ESCAPES),
diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs
index 35fc452ed7c..5cfb025ebfd 100644
--- a/clippy_lints/src/methods/mod.rs
+++ b/clippy_lints/src/methods/mod.rs
@@ -44,6 +44,7 @@ mod map_identity;
 mod map_unwrap_or;
 mod needless_option_as_deref;
 mod needless_option_take;
+mod no_effect_replace;
 mod ok_expect;
 mod option_as_ref_deref;
 mod option_map_or_none;
@@ -2195,6 +2196,24 @@ declare_clippy_lint! {
     "using `.as_ref().take()` on a temporary value"
 }
 
+declare_clippy_lint! {
+    /// ### What it does
+    /// Checks for `replace` statements which have no effect.
+    ///
+    /// ### Why is this bad?
+    /// It's either a mistake or confusing.
+    ///
+    /// ### Example
+    /// ```rust
+    /// "1234".replace("12", "12");
+    /// "1234".replacen("12", "12", 1);
+    /// ```
+    #[clippy::version = "1.62.0"]
+    pub NO_EFFECT_REPLACE,
+    suspicious,
+    "replace with no effect"
+}
+
 pub struct Methods {
     avoid_breaking_exported_api: bool,
     msrv: Option<RustcVersion>,
@@ -2294,6 +2313,7 @@ impl_lint_pass!(Methods => [
     NEEDLESS_OPTION_AS_DEREF,
     IS_DIGIT_ASCII_RADIX,
     NEEDLESS_OPTION_TAKE,
+    NO_EFFECT_REPLACE,
 ]);
 
 /// Extracts a method call name, args, and `Span` of the method name.
@@ -2705,6 +2725,9 @@ impl Methods {
                         unnecessary_lazy_eval::check(cx, expr, recv, u_arg, "unwrap_or");
                     },
                 },
+                ("replace" | "replacen", [arg1, arg2] | [arg1, arg2, _]) => {
+                    no_effect_replace::check(cx, expr, arg1, arg2);
+                },
                 _ => {},
             }
         }
diff --git a/clippy_lints/src/methods/no_effect_replace.rs b/clippy_lints/src/methods/no_effect_replace.rs
new file mode 100644
index 00000000000..a76341855b6
--- /dev/null
+++ b/clippy_lints/src/methods/no_effect_replace.rs
@@ -0,0 +1,47 @@
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::SpanlessEq;
+use if_chain::if_chain;
+use rustc_ast::LitKind;
+use rustc_hir::ExprKind;
+use rustc_lint::LateContext;
+use rustc_span::sym;
+
+use super::NO_EFFECT_REPLACE;
+
+pub(super) fn check<'tcx>(
+    cx: &LateContext<'tcx>,
+    expr: &'tcx rustc_hir::Expr<'_>,
+    arg1: &'tcx rustc_hir::Expr<'_>,
+    arg2: &'tcx rustc_hir::Expr<'_>,
+) {
+    let ty = cx.typeck_results().expr_ty(expr).peel_refs();
+    if !(ty.is_str() || is_type_diagnostic_item(cx, ty, sym::String)) {
+        return;
+    }
+
+    if_chain! {
+        if let ExprKind::Lit(spanned) = &arg1.kind;
+        if let Some(param1) = lit_string_value(&spanned.node);
+
+        if let ExprKind::Lit(spanned) = &arg2.kind;
+        if let LitKind::Str(param2, _) = &spanned.node;
+        if param1 == param2.as_str();
+
+        then {
+            span_lint(cx, NO_EFFECT_REPLACE, expr.span, "replacing text with itself");
+        }
+    }
+
+    if SpanlessEq::new(cx).eq_expr(arg1, arg2) {
+        span_lint(cx, NO_EFFECT_REPLACE, expr.span, "replacing text with itself");
+    }
+}
+
+fn lit_string_value(node: &LitKind) -> Option<String> {
+    match node {
+        LitKind::Char(value) => Some(value.to_string()),
+        LitKind::Str(value, _) => Some(value.as_str().to_owned()),
+        _ => None,
+    }
+}
diff --git a/tests/ui/no_effect_replace.rs b/tests/ui/no_effect_replace.rs
new file mode 100644
index 00000000000..ad17d53f789
--- /dev/null
+++ b/tests/ui/no_effect_replace.rs
@@ -0,0 +1,51 @@
+#![warn(clippy::no_effect_replace)]
+
+fn main() {
+    let _ = "12345".replace('1', "1");
+    let _ = "12345".replace("12", "12");
+    let _ = String::new().replace("12", "12");
+
+    let _ = "12345".replacen('1', "1", 1);
+    let _ = "12345".replacen("12", "12", 1);
+    let _ = String::new().replacen("12", "12", 1);
+
+    let _ = "12345".replace("12", "22");
+    let _ = "12345".replacen("12", "22", 1);
+
+    let mut x = X::default();
+    let _ = "hello".replace(&x.f(), &x.f());
+    let _ = "hello".replace(&x.f(), &x.ff());
+
+    let _ = "hello".replace(&y(), &y());
+    let _ = "hello".replace(&y(), &z());
+
+    let _ = Replaceme.replace("a", "a");
+}
+
+#[derive(Default)]
+struct X {}
+
+impl X {
+    fn f(&mut self) -> String {
+        "he".to_string()
+    }
+
+    fn ff(&mut self) -> String {
+        "hh".to_string()
+    }
+}
+
+fn y() -> String {
+    "he".to_string()
+}
+
+fn z() -> String {
+    "hh".to_string()
+}
+
+struct Replaceme;
+impl Replaceme {
+    pub fn replace(&mut self, a: &str, b: &str) -> Self {
+        Self
+    }
+}
diff --git a/tests/ui/no_effect_replace.stderr b/tests/ui/no_effect_replace.stderr
new file mode 100644
index 00000000000..53a28aa73b7
--- /dev/null
+++ b/tests/ui/no_effect_replace.stderr
@@ -0,0 +1,52 @@
+error: replacing text with itself
+  --> $DIR/no_effect_replace.rs:4:13
+   |
+LL |     let _ = "12345".replace('1', "1");
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: `-D clippy::no-effect-replace` implied by `-D warnings`
+
+error: replacing text with itself
+  --> $DIR/no_effect_replace.rs:5:13
+   |
+LL |     let _ = "12345".replace("12", "12");
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: replacing text with itself
+  --> $DIR/no_effect_replace.rs:6:13
+   |
+LL |     let _ = String::new().replace("12", "12");
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: replacing text with itself
+  --> $DIR/no_effect_replace.rs:8:13
+   |
+LL |     let _ = "12345".replacen('1', "1", 1);
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: replacing text with itself
+  --> $DIR/no_effect_replace.rs:9:13
+   |
+LL |     let _ = "12345".replacen("12", "12", 1);
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: replacing text with itself
+  --> $DIR/no_effect_replace.rs:10:13
+   |
+LL |     let _ = String::new().replacen("12", "12", 1);
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: replacing text with itself
+  --> $DIR/no_effect_replace.rs:16:13
+   |
+LL |     let _ = "hello".replace(&x.f(), &x.f());
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: replacing text with itself
+  --> $DIR/no_effect_replace.rs:19:13
+   |
+LL |     let _ = "hello".replace(&y(), &y());
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 8 previous errors
+