Skip to content

Commit 5dfb5ad

Browse files
committed
Auto merge of #4259 - Darth-Revan:origin/inherent_to_string, r=flip1995
Implement lint for inherent to_string() method. Fixes #4247 changelog: Implement two new lints: `inherent_to_string` and `inherent_to_string_shadow_display` 1) Emits a warning if a type implements an inherent method `to_string(&self) -> String` 2) Emits an error if a type implements an inherent method `to_string(&self) -> String` and also implements the `Display` trait
2 parents fb35311 + b7145fb commit 5dfb5ad

File tree

7 files changed

+290
-2
lines changed

7 files changed

+290
-2
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -973,6 +973,8 @@ Released 2018-09-13
973973
[`ineffective_bit_mask`]: https://rust-lang.github.io/rust-clippy/master/index.html#ineffective_bit_mask
974974
[`infallible_destructuring_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#infallible_destructuring_match
975975
[`infinite_iter`]: https://rust-lang.github.io/rust-clippy/master/index.html#infinite_iter
976+
[`inherent_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#inherent_to_string
977+
[`inherent_to_string_shadow_display`]: https://rust-lang.github.io/rust-clippy/master/index.html#inherent_to_string_shadow_display
976978
[`inline_always`]: https://rust-lang.github.io/rust-clippy/master/index.html#inline_always
977979
[`inline_fn_without_body`]: https://rust-lang.github.io/rust-clippy/master/index.html#inline_fn_without_body
978980
[`int_plus_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#int_plus_one

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 306 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
9+
[There are 308 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

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
use if_chain::if_chain;
2+
use rustc::hir::{ImplItem, ImplItemKind};
3+
use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
4+
use rustc::{declare_lint_pass, declare_tool_lint};
5+
6+
use crate::utils::{
7+
get_trait_def_id, implements_trait, in_macro_or_desugar, match_type, paths, return_ty, span_help_and_lint,
8+
trait_ref_of_method, walk_ptrs_ty,
9+
};
10+
11+
declare_clippy_lint! {
12+
/// **What id does:** Checks for the definition of inherent methods with a signature of `to_string(&self) -> String`.
13+
///
14+
/// **Why is this bad?** This method is also implicitly defined if a type implements the `Display` trait. As the functionality of `Display` is much more versatile, it should be preferred.
15+
///
16+
/// **Known problems:** None
17+
///
18+
/// ** Example:**
19+
///
20+
/// ```rust
21+
/// // Bad
22+
/// pub struct A;
23+
///
24+
/// impl A {
25+
/// pub fn to_string(&self) -> String {
26+
/// "I am A".to_string()
27+
/// }
28+
/// }
29+
/// ```
30+
///
31+
/// ```rust
32+
/// // Good
33+
/// use std::fmt;
34+
///
35+
/// pub struct A;
36+
///
37+
/// impl fmt::Display for A {
38+
/// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
39+
/// write!(f, "I am A")
40+
/// }
41+
/// }
42+
/// ```
43+
pub INHERENT_TO_STRING,
44+
style,
45+
"type implements inherent method `to_string()`, but should instead implement the `Display` trait"
46+
}
47+
48+
declare_clippy_lint! {
49+
/// **What id does:** Checks for the definition of inherent methods with a signature of `to_string(&self) -> String` and if the type implementing this method also implements the `Display` trait.
50+
///
51+
/// **Why is this bad?** This method is also implicitly defined if a type implements the `Display` trait. The less versatile inherent method will then shadow the implementation introduced by `Display`.
52+
///
53+
/// **Known problems:** None
54+
///
55+
/// ** Example:**
56+
///
57+
/// ```rust
58+
/// // Bad
59+
/// use std::fmt;
60+
///
61+
/// pub struct A;
62+
///
63+
/// impl A {
64+
/// pub fn to_string(&self) -> String {
65+
/// "I am A".to_string()
66+
/// }
67+
/// }
68+
///
69+
/// impl fmt::Display for A {
70+
/// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
71+
/// write!(f, "I am A, too")
72+
/// }
73+
/// }
74+
/// ```
75+
///
76+
/// ```rust
77+
/// // Good
78+
/// use std::fmt;
79+
///
80+
/// pub struct A;
81+
///
82+
/// impl fmt::Display for A {
83+
/// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
84+
/// write!(f, "I am A")
85+
/// }
86+
/// }
87+
/// ```
88+
pub INHERENT_TO_STRING_SHADOW_DISPLAY,
89+
correctness,
90+
"type implements inherent method `to_string()`, which gets shadowed by the implementation of the `Display` trait "
91+
}
92+
93+
declare_lint_pass!(InherentToString => [INHERENT_TO_STRING, INHERENT_TO_STRING_SHADOW_DISPLAY]);
94+
95+
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for InherentToString {
96+
fn check_impl_item(&mut self, cx: &LateContext<'a, 'tcx>, impl_item: &'tcx ImplItem) {
97+
if in_macro_or_desugar(impl_item.span) {
98+
return;
99+
}
100+
101+
if_chain! {
102+
// Check if item is a method, called to_string and has a parameter 'self'
103+
if let ImplItemKind::Method(ref signature, _) = impl_item.node;
104+
if impl_item.ident.name.as_str() == "to_string";
105+
let decl = &signature.decl;
106+
if decl.implicit_self.has_implicit_self();
107+
108+
// Check if return type is String
109+
if match_type(cx, return_ty(cx, impl_item.hir_id), &paths::STRING);
110+
111+
// Filters instances of to_string which are required by a trait
112+
if trait_ref_of_method(cx, impl_item.hir_id).is_none();
113+
114+
then {
115+
show_lint(cx, impl_item);
116+
}
117+
}
118+
}
119+
}
120+
121+
fn show_lint(cx: &LateContext<'_, '_>, item: &ImplItem) {
122+
let display_trait_id =
123+
get_trait_def_id(cx, &["core", "fmt", "Display"]).expect("Failed to get trait ID of `Display`!");
124+
125+
// Get the real type of 'self'
126+
let fn_def_id = cx.tcx.hir().local_def_id(item.hir_id);
127+
let self_type = cx.tcx.fn_sig(fn_def_id).input(0);
128+
let self_type = walk_ptrs_ty(self_type.skip_binder());
129+
130+
// Emit either a warning or an error
131+
if implements_trait(cx, self_type, display_trait_id, &[]) {
132+
span_help_and_lint(
133+
cx,
134+
INHERENT_TO_STRING_SHADOW_DISPLAY,
135+
item.span,
136+
&format!(
137+
"type `{}` implements inherent method `to_string(&self) -> String` which shadows the implementation of `Display`",
138+
self_type.to_string()
139+
),
140+
&format!("remove the inherent method from type `{}`", self_type.to_string())
141+
);
142+
} else {
143+
span_help_and_lint(
144+
cx,
145+
INHERENT_TO_STRING,
146+
item.span,
147+
&format!(
148+
"implementation of inherent method `to_string(&self) -> String` for type `{}`",
149+
self_type.to_string()
150+
),
151+
&format!("implement trait `Display` for type `{}` instead", self_type.to_string()),
152+
);
153+
}
154+
}

clippy_lints/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ pub mod indexing_slicing;
195195
pub mod infallible_destructuring_match;
196196
pub mod infinite_iter;
197197
pub mod inherent_impl;
198+
pub mod inherent_to_string;
198199
pub mod inline_fn_without_body;
199200
pub mod int_plus_one;
200201
pub mod integer_division;
@@ -585,6 +586,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry<'_>, conf: &Conf) {
585586
reg.register_late_lint_pass(box path_buf_push_overwrite::PathBufPushOverwrite);
586587
reg.register_late_lint_pass(box checked_conversions::CheckedConversions);
587588
reg.register_late_lint_pass(box integer_division::IntegerDivision);
589+
reg.register_late_lint_pass(box inherent_to_string::InherentToString);
588590

589591
reg.register_lint_group("clippy::restriction", Some("clippy_restriction"), vec![
590592
arithmetic::FLOAT_ARITHMETIC,
@@ -725,6 +727,8 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry<'_>, conf: &Conf) {
725727
indexing_slicing::OUT_OF_BOUNDS_INDEXING,
726728
infallible_destructuring_match::INFALLIBLE_DESTRUCTURING_MATCH,
727729
infinite_iter::INFINITE_ITER,
730+
inherent_to_string::INHERENT_TO_STRING,
731+
inherent_to_string::INHERENT_TO_STRING_SHADOW_DISPLAY,
728732
inline_fn_without_body::INLINE_FN_WITHOUT_BODY,
729733
int_plus_one::INT_PLUS_ONE,
730734
invalid_ref::INVALID_REF,
@@ -913,6 +917,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry<'_>, conf: &Conf) {
913917
formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING,
914918
formatting::SUSPICIOUS_ELSE_FORMATTING,
915919
infallible_destructuring_match::INFALLIBLE_DESTRUCTURING_MATCH,
920+
inherent_to_string::INHERENT_TO_STRING,
916921
len_zero::LEN_WITHOUT_IS_EMPTY,
917922
len_zero::LEN_ZERO,
918923
let_if_seq::USELESS_LET_IF_SEQ,
@@ -1075,6 +1080,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry<'_>, conf: &Conf) {
10751080
functions::NOT_UNSAFE_PTR_ARG_DEREF,
10761081
indexing_slicing::OUT_OF_BOUNDS_INDEXING,
10771082
infinite_iter::INFINITE_ITER,
1083+
inherent_to_string::INHERENT_TO_STRING_SHADOW_DISPLAY,
10781084
inline_fn_without_body::INLINE_FN_WITHOUT_BODY,
10791085
invalid_ref::INVALID_REF,
10801086
literal_representation::MISTYPED_LITERAL_SUFFIXES,

src/lintlist/mod.rs

Lines changed: 15 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; 306] = [
9+
pub const ALL_LINTS: [Lint; 308] = [
1010
Lint {
1111
name: "absurd_extreme_comparisons",
1212
group: "correctness",
@@ -728,6 +728,20 @@ pub const ALL_LINTS: [Lint; 306] = [
728728
deprecation: None,
729729
module: "infinite_iter",
730730
},
731+
Lint {
732+
name: "inherent_to_string",
733+
group: "style",
734+
desc: "type implements inherent method `to_string()`, but should instead implement the `Display` trait",
735+
deprecation: None,
736+
module: "inherent_to_string",
737+
},
738+
Lint {
739+
name: "inherent_to_string_shadow_display",
740+
group: "correctness",
741+
desc: "type implements inherent method `to_string()`, which gets shadowed by the implementation of the `Display` trait ",
742+
deprecation: None,
743+
module: "inherent_to_string",
744+
},
731745
Lint {
732746
name: "inline_always",
733747
group: "pedantic",

tests/ui/inherent_to_string.rs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#![warn(clippy::inherent_to_string)]
2+
#![deny(clippy::inherent_to_string_shadow_display)]
3+
4+
use std::fmt;
5+
6+
trait FalsePositive {
7+
fn to_string(&self) -> String;
8+
}
9+
10+
struct A;
11+
struct B;
12+
struct C;
13+
struct D;
14+
struct E;
15+
16+
impl A {
17+
// Should be detected; emit warning
18+
fn to_string(&self) -> String {
19+
"A.to_string()".to_string()
20+
}
21+
22+
// Should not be detected as it does not match the function signature
23+
fn to_str(&self) -> String {
24+
"A.to_str()".to_string()
25+
}
26+
}
27+
28+
// Should not be detected as it is a free function
29+
fn to_string() -> String {
30+
"free to_string()".to_string()
31+
}
32+
33+
impl B {
34+
// Should not be detected, wrong return type
35+
fn to_string(&self) -> i32 {
36+
42
37+
}
38+
}
39+
40+
impl C {
41+
// Should be detected and emit error as C also implements Display
42+
fn to_string(&self) -> String {
43+
"C.to_string()".to_string()
44+
}
45+
}
46+
47+
impl fmt::Display for C {
48+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
49+
write!(f, "impl Display for C")
50+
}
51+
}
52+
53+
impl FalsePositive for D {
54+
// Should not be detected, as it is a trait function
55+
fn to_string(&self) -> String {
56+
"impl FalsePositive for D".to_string()
57+
}
58+
}
59+
60+
impl E {
61+
// Should not be detected, as it is not bound to an instance
62+
fn to_string() -> String {
63+
"E::to_string()".to_string()
64+
}
65+
}
66+
67+
fn main() {
68+
let a = A;
69+
a.to_string();
70+
a.to_str();
71+
72+
to_string();
73+
74+
let b = B;
75+
b.to_string();
76+
77+
let c = C;
78+
C.to_string();
79+
80+
let d = D;
81+
d.to_string();
82+
83+
E::to_string();
84+
}

tests/ui/inherent_to_string.stderr

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
error: implementation of inherent method `to_string(&self) -> String` for type `A`
2+
--> $DIR/inherent_to_string.rs:18:5
3+
|
4+
LL | / fn to_string(&self) -> String {
5+
LL | | "A.to_string()".to_string()
6+
LL | | }
7+
| |_____^
8+
|
9+
= note: `-D clippy::inherent-to-string` implied by `-D warnings`
10+
= help: implement trait `Display` for type `A` instead
11+
12+
error: type `C` implements inherent method `to_string(&self) -> String` which shadows the implementation of `Display`
13+
--> $DIR/inherent_to_string.rs:42:5
14+
|
15+
LL | / fn to_string(&self) -> String {
16+
LL | | "C.to_string()".to_string()
17+
LL | | }
18+
| |_____^
19+
|
20+
note: lint level defined here
21+
--> $DIR/inherent_to_string.rs:2:9
22+
|
23+
LL | #![deny(clippy::inherent_to_string_shadow_display)]
24+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
25+
= help: remove the inherent method from type `C`
26+
27+
error: aborting due to 2 previous errors
28+

0 commit comments

Comments
 (0)