diff --git a/clippy_lints/src/strings.rs b/clippy_lints/src/strings.rs index 509ad4e4fcb3..d14a56115cbd 100644 --- a/clippy_lints/src/strings.rs +++ b/clippy_lints/src/strings.rs @@ -417,6 +417,8 @@ impl<'tcx> LateLintPass<'tcx> for StrToString { && 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 @@ impl<'tcx> LateLintPass<'tcx> for StrToString { expr.span, "`ToString::to_string` used as `&str` to `String` converter", "try", - "ToOwned::to_owned".to_string(), + "str::to_owned".to_string(), Applicability::MachineApplicable, ); } diff --git a/tests/ui/str_to_string.fixed b/tests/ui/str_to_string.fixed index 5b76cf78f069..a8950ab70e56 100644 --- a/tests/ui/str_to_string.fixed +++ b/tests/ui/str_to_string.fixed @@ -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 GenericWrapper { } fn issue16511(x: Option<&str>) { - let _ = x.map(ToOwned::to_owned); + let _: Option = x.map(str::to_owned); //~^ str_to_string - let _ = x.map(ToOwned::to_owned); + let _: Option = 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 = ["a", "b"].iter().map(ToString::to_string).collect(); fn mapper 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 = [1, 2].iter().map(i32::to_string).collect(); +} diff --git a/tests/ui/str_to_string.rs b/tests/ui/str_to_string.rs index f099eb29b1b5..5d78893f53f6 100644 --- a/tests/ui/str_to_string.rs +++ b/tests/ui/str_to_string.rs @@ -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 @@ fn issue16271(key: &[u8]) { }; } - 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 @@ impl GenericWrapper { } fn issue16511(x: Option<&str>) { - let _ = x.map(ToString::to_string); + let _: Option = x.map(ToString::to_string); //~^ str_to_string - let _ = x.map(str::to_string); + let _: Option = 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 = ["a", "b"].iter().map(ToString::to_string).collect(); fn mapper 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 = [1, 2].iter().map(i32::to_string).collect(); +} diff --git a/tests/ui/str_to_string.stderr b/tests/ui/str_to_string.stderr index 296b8e36f28c..cd4db83e6370 100644 --- a/tests/ui/str_to_string.stderr +++ b/tests/ui/str_to_string.stderr @@ -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 = 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 = 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