Skip to content

Commit 14a0f36

Browse files
committed
Auto merge of #4683 - HMPerson1:inefficient_to_string, r=Manishearth
Add `inefficient_to_string` lint Closes #4586 changelog: Add `inefficient_to_string` lint, which checks for calling `to_string` on `&&str`, which would bypass the `str`'s specialization
2 parents e979eb4 + 2106a23 commit 14a0f36

11 files changed

+216
-4
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1033,6 +1033,7 @@ Released 2018-09-13
10331033
[`inconsistent_digit_grouping`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_digit_grouping
10341034
[`indexing_slicing`]: https://rust-lang.github.io/rust-clippy/master/index.html#indexing_slicing
10351035
[`ineffective_bit_mask`]: https://rust-lang.github.io/rust-clippy/master/index.html#ineffective_bit_mask
1036+
[`inefficient_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#inefficient_to_string
10361037
[`infallible_destructuring_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#infallible_destructuring_match
10371038
[`infinite_iter`]: https://rust-lang.github.io/rust-clippy/master/index.html#infinite_iter
10381039
[`inherent_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#inherent_to_string

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code.
88

9-
[There are 325 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
9+
[There are 326 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
1010

1111
We have a bunch of lint categories to allow you to choose how much Clippy is supposed to ~~annoy~~ help you:
1212

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -808,6 +808,7 @@ pub fn register_plugins(reg: &mut rustc_driver::plugin::Registry<'_>, conf: &Con
808808
methods::EXPECT_FUN_CALL,
809809
methods::FILTER_NEXT,
810810
methods::FLAT_MAP_IDENTITY,
811+
methods::INEFFICIENT_TO_STRING,
811812
methods::INTO_ITER_ON_ARRAY,
812813
methods::INTO_ITER_ON_REF,
813814
methods::ITER_CLONED_COLLECT,
@@ -1184,6 +1185,7 @@ pub fn register_plugins(reg: &mut rustc_driver::plugin::Registry<'_>, conf: &Con
11841185
loops::MANUAL_MEMCPY,
11851186
loops::NEEDLESS_COLLECT,
11861187
methods::EXPECT_FUN_CALL,
1188+
methods::INEFFICIENT_TO_STRING,
11871189
methods::ITER_NTH,
11881190
methods::OR_FUN_CALL,
11891191
methods::SINGLE_CHAR_PATTERN,
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
use super::INEFFICIENT_TO_STRING;
2+
use crate::utils::{match_def_path, paths, snippet_with_applicability, span_lint_and_then, walk_ptrs_ty_depth};
3+
use if_chain::if_chain;
4+
use rustc::hir;
5+
use rustc::lint::LateContext;
6+
use rustc::ty::{self, Ty};
7+
use rustc_errors::Applicability;
8+
9+
/// Checks for the `INEFFICIENT_TO_STRING` lint
10+
pub fn lint<'tcx>(cx: &LateContext<'_, 'tcx>, expr: &hir::Expr, arg: &hir::Expr, arg_ty: Ty<'tcx>) {
11+
if_chain! {
12+
if let Some(to_string_meth_did) = cx.tables.type_dependent_def_id(expr.hir_id);
13+
if match_def_path(cx, to_string_meth_did, &paths::TO_STRING_METHOD);
14+
if let Some(substs) = cx.tables.node_substs_opt(expr.hir_id);
15+
let self_ty = substs.type_at(0);
16+
let (deref_self_ty, deref_count) = walk_ptrs_ty_depth(self_ty);
17+
if deref_count >= 1;
18+
if specializes_tostring(cx, deref_self_ty);
19+
then {
20+
span_lint_and_then(
21+
cx,
22+
INEFFICIENT_TO_STRING,
23+
expr.span,
24+
&format!("calling `to_string` on `{}`", arg_ty),
25+
|db| {
26+
db.help(&format!(
27+
"`{}` implements `ToString` through a slower blanket impl, but `{}` has a fast specialization of `ToString`",
28+
self_ty, deref_self_ty
29+
));
30+
let mut applicability = Applicability::MachineApplicable;
31+
let arg_snippet = snippet_with_applicability(cx, arg.span, "..", &mut applicability);
32+
db.span_suggestion(
33+
expr.span,
34+
"try dereferencing the receiver",
35+
format!("({}{}).to_string()", "*".repeat(deref_count), arg_snippet),
36+
applicability,
37+
);
38+
},
39+
);
40+
}
41+
}
42+
}
43+
44+
/// Returns whether `ty` specializes `ToString`.
45+
/// Currently, these are `str`, `String`, and `Cow<'_, str>`.
46+
fn specializes_tostring(cx: &LateContext<'_, '_>, ty: Ty<'_>) -> bool {
47+
match ty.kind {
48+
ty::Str => true,
49+
ty::Adt(adt, substs) => {
50+
match_def_path(cx, adt.did, &paths::STRING)
51+
|| (match_def_path(cx, adt.did, &paths::COW) && substs.type_at(1).is_str())
52+
},
53+
_ => false,
54+
}
55+
}

clippy_lints/src/methods/mod.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
mod inefficient_to_string;
12
mod manual_saturating_arithmetic;
23
mod option_map_unwrap_or;
34
mod unnecessary_filter_map;
@@ -589,6 +590,29 @@ declare_clippy_lint! {
589590
"using `clone` on `&&T`"
590591
}
591592

593+
declare_clippy_lint! {
594+
/// **What it does:** Checks for usage of `.to_string()` on an `&&T` where
595+
/// `T` implements `ToString` directly (like `&&str` or `&&String`).
596+
///
597+
/// **Why is this bad?** This bypasses the specialized implementation of
598+
/// `ToString` and instead goes through the more expensive string formatting
599+
/// facilities.
600+
///
601+
/// **Known problems:** None.
602+
///
603+
/// **Example:**
604+
/// ```rust
605+
/// // Generic implementation for `T: Display` is used (slow)
606+
/// ["foo", "bar"].iter().map(|s| s.to_string());
607+
///
608+
/// // OK, the specialized impl is used
609+
/// ["foo", "bar"].iter().map(|&s| s.to_string());
610+
/// ```
611+
pub INEFFICIENT_TO_STRING,
612+
perf,
613+
"using `to_string` on `&&T` where `T: ToString`"
614+
}
615+
592616
declare_clippy_lint! {
593617
/// **What it does:** Checks for `new` not returning `Self`.
594618
///
@@ -1029,6 +1053,7 @@ declare_lint_pass!(Methods => [
10291053
CLONE_ON_COPY,
10301054
CLONE_ON_REF_PTR,
10311055
CLONE_DOUBLE_REF,
1056+
INEFFICIENT_TO_STRING,
10321057
NEW_RET_NO_SELF,
10331058
SINGLE_CHAR_PATTERN,
10341059
SEARCH_IS_SOME,
@@ -1122,6 +1147,9 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Methods {
11221147
lint_clone_on_copy(cx, expr, &args[0], self_ty);
11231148
lint_clone_on_ref_ptr(cx, expr, &args[0]);
11241149
}
1150+
if args.len() == 1 && method_call.ident.name == sym!(to_string) {
1151+
inefficient_to_string::lint(cx, expr, &args[0], self_ty);
1152+
}
11251153

11261154
match self_ty.kind {
11271155
ty::Ref(_, ty, _) if ty.kind == ty::Str => {

clippy_lints/src/replace_consts.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ReplaceConsts {
3939
if let hir::ExprKind::Path(ref qp) = expr.kind;
4040
if let Res::Def(DefKind::Const, def_id) = cx.tables.qpath_res(qp, expr.hir_id);
4141
then {
42-
for (const_path, repl_snip) in &REPLACEMENTS {
42+
for &(ref const_path, repl_snip) in &REPLACEMENTS {
4343
if match_def_path(cx, def_id, const_path) {
4444
span_lint_and_sugg(
4545
cx,

clippy_lints/src/utils/attrs.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ pub fn get_attr<'a>(
7171
})
7272
{
7373
let mut db = sess.struct_span_err(attr_segments[1].ident.span, "Usage of deprecated attribute");
74-
match deprecation_status {
74+
match *deprecation_status {
7575
DeprecationStatus::Deprecated => {
7676
db.emit();
7777
false

src/lintlist/mod.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ pub use lint::Lint;
66
pub use lint::LINT_LEVELS;
77

88
// begin lint list, do not remove this comment, it’s used in `update_lints`
9-
pub const ALL_LINTS: [Lint; 325] = [
9+
pub const ALL_LINTS: [Lint; 326] = [
1010
Lint {
1111
name: "absurd_extreme_comparisons",
1212
group: "correctness",
@@ -735,6 +735,13 @@ pub const ALL_LINTS: [Lint; 325] = [
735735
deprecation: None,
736736
module: "bit_mask",
737737
},
738+
Lint {
739+
name: "inefficient_to_string",
740+
group: "perf",
741+
desc: "using `to_string` on `&&T` where `T: ToString`",
742+
deprecation: None,
743+
module: "methods",
744+
},
738745
Lint {
739746
name: "infallible_destructuring_match",
740747
group: "style",

tests/ui/inefficient_to_string.fixed

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// run-rustfix
2+
#![deny(clippy::inefficient_to_string)]
3+
4+
use std::borrow::Cow;
5+
use std::string::ToString;
6+
7+
fn main() {
8+
let rstr: &str = "hello";
9+
let rrstr: &&str = &rstr;
10+
let rrrstr: &&&str = &rrstr;
11+
let _: String = rstr.to_string();
12+
let _: String = (*rrstr).to_string();
13+
let _: String = (**rrrstr).to_string();
14+
15+
let string: String = String::from("hello");
16+
let rstring: &String = &string;
17+
let rrstring: &&String = &rstring;
18+
let rrrstring: &&&String = &rrstring;
19+
let _: String = string.to_string();
20+
let _: String = rstring.to_string();
21+
let _: String = (*rrstring).to_string();
22+
let _: String = (**rrrstring).to_string();
23+
24+
let cow: Cow<'_, str> = Cow::Borrowed("hello");
25+
let rcow: &Cow<'_, str> = &cow;
26+
let rrcow: &&Cow<'_, str> = &rcow;
27+
let rrrcow: &&&Cow<'_, str> = &rrcow;
28+
let _: String = cow.to_string();
29+
let _: String = rcow.to_string();
30+
let _: String = (*rrcow).to_string();
31+
let _: String = (**rrrcow).to_string();
32+
}

tests/ui/inefficient_to_string.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// run-rustfix
2+
#![deny(clippy::inefficient_to_string)]
3+
4+
use std::borrow::Cow;
5+
use std::string::ToString;
6+
7+
fn main() {
8+
let rstr: &str = "hello";
9+
let rrstr: &&str = &rstr;
10+
let rrrstr: &&&str = &rrstr;
11+
let _: String = rstr.to_string();
12+
let _: String = rrstr.to_string();
13+
let _: String = rrrstr.to_string();
14+
15+
let string: String = String::from("hello");
16+
let rstring: &String = &string;
17+
let rrstring: &&String = &rstring;
18+
let rrrstring: &&&String = &rrstring;
19+
let _: String = string.to_string();
20+
let _: String = rstring.to_string();
21+
let _: String = rrstring.to_string();
22+
let _: String = rrrstring.to_string();
23+
24+
let cow: Cow<'_, str> = Cow::Borrowed("hello");
25+
let rcow: &Cow<'_, str> = &cow;
26+
let rrcow: &&Cow<'_, str> = &rcow;
27+
let rrrcow: &&&Cow<'_, str> = &rrcow;
28+
let _: String = cow.to_string();
29+
let _: String = rcow.to_string();
30+
let _: String = rrcow.to_string();
31+
let _: String = rrrcow.to_string();
32+
}

tests/ui/inefficient_to_string.stderr

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
error: calling `to_string` on `&&str`
2+
--> $DIR/inefficient_to_string.rs:12:21
3+
|
4+
LL | let _: String = rrstr.to_string();
5+
| ^^^^^^^^^^^^^^^^^ help: try dereferencing the receiver: `(*rrstr).to_string()`
6+
|
7+
note: lint level defined here
8+
--> $DIR/inefficient_to_string.rs:2:9
9+
|
10+
LL | #![deny(clippy::inefficient_to_string)]
11+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
12+
= help: `&str` implements `ToString` through a slower blanket impl, but `str` has a fast specialization of `ToString`
13+
14+
error: calling `to_string` on `&&&str`
15+
--> $DIR/inefficient_to_string.rs:13:21
16+
|
17+
LL | let _: String = rrrstr.to_string();
18+
| ^^^^^^^^^^^^^^^^^^ help: try dereferencing the receiver: `(**rrrstr).to_string()`
19+
|
20+
= help: `&&str` implements `ToString` through a slower blanket impl, but `str` has a fast specialization of `ToString`
21+
22+
error: calling `to_string` on `&&std::string::String`
23+
--> $DIR/inefficient_to_string.rs:21:21
24+
|
25+
LL | let _: String = rrstring.to_string();
26+
| ^^^^^^^^^^^^^^^^^^^^ help: try dereferencing the receiver: `(*rrstring).to_string()`
27+
|
28+
= help: `&std::string::String` implements `ToString` through a slower blanket impl, but `std::string::String` has a fast specialization of `ToString`
29+
30+
error: calling `to_string` on `&&&std::string::String`
31+
--> $DIR/inefficient_to_string.rs:22:21
32+
|
33+
LL | let _: String = rrrstring.to_string();
34+
| ^^^^^^^^^^^^^^^^^^^^^ help: try dereferencing the receiver: `(**rrrstring).to_string()`
35+
|
36+
= help: `&&std::string::String` implements `ToString` through a slower blanket impl, but `std::string::String` has a fast specialization of `ToString`
37+
38+
error: calling `to_string` on `&&std::borrow::Cow<'_, str>`
39+
--> $DIR/inefficient_to_string.rs:30:21
40+
|
41+
LL | let _: String = rrcow.to_string();
42+
| ^^^^^^^^^^^^^^^^^ help: try dereferencing the receiver: `(*rrcow).to_string()`
43+
|
44+
= help: `&std::borrow::Cow<'_, str>` implements `ToString` through a slower blanket impl, but `std::borrow::Cow<'_, str>` has a fast specialization of `ToString`
45+
46+
error: calling `to_string` on `&&&std::borrow::Cow<'_, str>`
47+
--> $DIR/inefficient_to_string.rs:31:21
48+
|
49+
LL | let _: String = rrrcow.to_string();
50+
| ^^^^^^^^^^^^^^^^^^ help: try dereferencing the receiver: `(**rrrcow).to_string()`
51+
|
52+
= help: `&&std::borrow::Cow<'_, str>` implements `ToString` through a slower blanket impl, but `std::borrow::Cow<'_, str>` has a fast specialization of `ToString`
53+
54+
error: aborting due to 6 previous errors
55+

0 commit comments

Comments
 (0)