fix(str_to_string): false positive non-str types (#16571)

Fix rust-lang/rust-clippy#16569 bug introduced in
rust-lang/rust-clippy#16512

changelog: [`str_to_string`]: fix false positive

r? @samueltardieu
This commit is contained in:
Samuel Tardieu
2026-02-19 07:30:24 +00:00
committed by GitHub
4 changed files with 52 additions and 46 deletions
+3 -1
View File
@@ -417,6 +417,8 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) {
&& args.iter().any(|a| a.hir_id == expr.hir_id)
&& let Res::Def(DefKind::AssocFn, def_id) = expr.res(cx)
&& cx.tcx.is_diagnostic_item(sym::to_string_method, def_id)
&& let Some(args) = cx.typeck_results().node_args_opt(expr.hir_id)
&& args.type_at(0).is_str()
{
// Detected `ToString::to_string` passed as an argument (generic: any call or method call)
span_lint_and_sugg(
@@ -425,7 +427,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) {
expr.span,
"`ToString::to_string` used as `&str` to `String` converter",
"try",
"ToOwned::to_owned".to_string(),
"str::to_owned".to_string(),
Applicability::MachineApplicable,
);
}
+15 -10
View File
@@ -1,10 +1,10 @@
#![warn(clippy::str_to_string)]
fn main() {
let hello = "hello world".to_owned();
let hello: String = "hello world".to_owned();
//~^ str_to_string
let msg = &hello[..];
let msg: &str = &hello[..];
msg.to_owned();
//~^ str_to_string
}
@@ -19,7 +19,7 @@ fn issue16271(key: &[u8]) {
};
}
let _value = t!(str::from_utf8(key)).to_owned();
let _value: String = t!(str::from_utf8(key)).to_owned();
//~^ str_to_string
}
@@ -32,22 +32,27 @@ impl<T> GenericWrapper<T> {
}
fn issue16511(x: Option<&str>) {
let _ = x.map(ToOwned::to_owned);
let _: Option<String> = x.map(str::to_owned);
//~^ str_to_string
let _ = x.map(ToOwned::to_owned);
let _: Option<String> = x.map(str::to_owned);
//~^ str_to_string
let _ = ["a", "b"].iter().map(ToOwned::to_owned);
//~^ str_to_string
// This should not trigger the lint because ToOwned::to_owned would produce &str, not String.
let _: Vec<String> = ["a", "b"].iter().map(ToString::to_string).collect();
fn mapper<F: Fn(&str) -> String>(f: F) -> String {
f("hello")
}
let _ = mapper(ToOwned::to_owned);
let _: String = mapper(str::to_owned);
//~^ str_to_string
let w = GenericWrapper("hello");
let _ = w.mapper(ToOwned::to_owned);
let w: GenericWrapper<&str> = GenericWrapper("hello");
let _: String = w.mapper(str::to_owned);
//~^ str_to_string
}
// No lint: non-str types should not trigger str_to_string. See #16569
fn no_lint_non_str() {
let _: Vec<String> = [1, 2].iter().map(i32::to_string).collect();
}
+15 -10
View File
@@ -1,10 +1,10 @@
#![warn(clippy::str_to_string)]
fn main() {
let hello = "hello world".to_string();
let hello: String = "hello world".to_string();
//~^ str_to_string
let msg = &hello[..];
let msg: &str = &hello[..];
msg.to_string();
//~^ str_to_string
}
@@ -19,7 +19,7 @@ macro_rules! t {
};
}
let _value = t!(str::from_utf8(key)).to_string();
let _value: String = t!(str::from_utf8(key)).to_string();
//~^ str_to_string
}
@@ -32,22 +32,27 @@ fn mapper<U, F: FnOnce(T) -> U>(self, f: F) -> U {
}
fn issue16511(x: Option<&str>) {
let _ = x.map(ToString::to_string);
let _: Option<String> = x.map(ToString::to_string);
//~^ str_to_string
let _ = x.map(str::to_string);
let _: Option<String> = x.map(str::to_string);
//~^ str_to_string
let _ = ["a", "b"].iter().map(ToString::to_string);
//~^ str_to_string
// This should not trigger the lint because ToOwned::to_owned would produce &str, not String.
let _: Vec<String> = ["a", "b"].iter().map(ToString::to_string).collect();
fn mapper<F: Fn(&str) -> String>(f: F) -> String {
f("hello")
}
let _ = mapper(ToString::to_string);
let _: String = mapper(ToString::to_string);
//~^ str_to_string
let w = GenericWrapper("hello");
let _ = w.mapper(ToString::to_string);
let w: GenericWrapper<&str> = GenericWrapper("hello");
let _: String = w.mapper(ToString::to_string);
//~^ str_to_string
}
// No lint: non-str types should not trigger str_to_string. See #16569
fn no_lint_non_str() {
let _: Vec<String> = [1, 2].iter().map(i32::to_string).collect();
}
+19 -25
View File
@@ -1,8 +1,8 @@
error: `to_string()` called on a `&str`
--> tests/ui/str_to_string.rs:4:17
--> tests/ui/str_to_string.rs:4:25
|
LL | let hello = "hello world".to_string();
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `"hello world".to_owned()`
LL | let hello: String = "hello world".to_string();
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `"hello world".to_owned()`
|
= note: `-D clippy::str-to-string` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::str_to_string)]`
@@ -14,40 +14,34 @@ LL | msg.to_string();
| ^^^^^^^^^^^^^^^ help: try: `msg.to_owned()`
error: `to_string()` called on a `&str`
--> tests/ui/str_to_string.rs:22:18
--> tests/ui/str_to_string.rs:22:26
|
LL | let _value = t!(str::from_utf8(key)).to_string();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `t!(str::from_utf8(key)).to_owned()`
LL | let _value: String = t!(str::from_utf8(key)).to_string();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `t!(str::from_utf8(key)).to_owned()`
error: `ToString::to_string` used as `&str` to `String` converter
--> tests/ui/str_to_string.rs:35:19
--> tests/ui/str_to_string.rs:35:35
|
LL | let _ = x.map(ToString::to_string);
| ^^^^^^^^^^^^^^^^^^^ help: try: `ToOwned::to_owned`
LL | let _: Option<String> = x.map(ToString::to_string);
| ^^^^^^^^^^^^^^^^^^^ help: try: `str::to_owned`
error: `ToString::to_string` used as `&str` to `String` converter
--> tests/ui/str_to_string.rs:38:19
--> tests/ui/str_to_string.rs:38:35
|
LL | let _ = x.map(str::to_string);
| ^^^^^^^^^^^^^^ help: try: `ToOwned::to_owned`
LL | let _: Option<String> = x.map(str::to_string);
| ^^^^^^^^^^^^^^ help: try: `str::to_owned`
error: `ToString::to_string` used as `&str` to `String` converter
--> tests/ui/str_to_string.rs:41:35
--> tests/ui/str_to_string.rs:47:28
|
LL | let _ = ["a", "b"].iter().map(ToString::to_string);
| ^^^^^^^^^^^^^^^^^^^ help: try: `ToOwned::to_owned`
LL | let _: String = mapper(ToString::to_string);
| ^^^^^^^^^^^^^^^^^^^ help: try: `str::to_owned`
error: `ToString::to_string` used as `&str` to `String` converter
--> tests/ui/str_to_string.rs:47:20
--> tests/ui/str_to_string.rs:51:30
|
LL | let _ = mapper(ToString::to_string);
| ^^^^^^^^^^^^^^^^^^^ help: try: `ToOwned::to_owned`
LL | let _: String = w.mapper(ToString::to_string);
| ^^^^^^^^^^^^^^^^^^^ help: try: `str::to_owned`
error: `ToString::to_string` used as `&str` to `String` converter
--> tests/ui/str_to_string.rs:51:22
|
LL | let _ = w.mapper(ToString::to_string);
| ^^^^^^^^^^^^^^^^^^^ help: try: `ToOwned::to_owned`
error: aborting due to 8 previous errors
error: aborting due to 7 previous errors