Skip to content

Commit 0513202

Browse files
Darth-Revanflip1995
Darth-Revan
authored andcommitted
Implement lint for inherent to_string() method.
1 parent 7498a5f commit 0513202

File tree

7 files changed

+280
-2
lines changed

7 files changed

+280
-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: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
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,ignore
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+
/// // Good
30+
/// use std::fmt;
31+
///
32+
/// pub struct A;
33+
///
34+
/// impl fmt::Display for A {
35+
/// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
36+
/// write!(f, "I am A")
37+
/// }
38+
/// }
39+
/// ```
40+
pub INHERENT_TO_STRING,
41+
style,
42+
"type implements inherent method 'to_string()', but should instead implement the 'Display' trait"
43+
}
44+
45+
declare_clippy_lint! {
46+
/// **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.
47+
///
48+
/// **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`.
49+
///
50+
/// **Known problems:** The inherent method will shadow the implementation by `Display`. If they behave differently, this may lead to confusing situations for users of the respective type.
51+
///
52+
/// ** Example:**
53+
///
54+
/// ```rust,ignore
55+
/// // Bad
56+
/// use std::fmt;
57+
///
58+
/// pub struct A;
59+
///
60+
/// impl A {
61+
/// pub fn to_string(&self) -> String {
62+
/// "I am A".to_string()
63+
/// }
64+
/// }
65+
///
66+
/// impl fmt::Display for A {
67+
/// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
68+
/// write!(f, "I am A, too")
69+
/// }
70+
/// }
71+
/// // Good
72+
/// use std::fmt;
73+
///
74+
/// pub struct A;
75+
///
76+
/// impl fmt::Display for A {
77+
/// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
78+
/// write!(f, "I am A")
79+
/// }
80+
/// }
81+
/// ```
82+
pub INHERENT_TO_STRING_SHADOW_DISPLAY,
83+
correctness,
84+
"type implements inherent method 'to_string()', which gets shadowed by the implementation of the 'Display' trait "
85+
}
86+
87+
declare_lint_pass!(InherentToString => [INHERENT_TO_STRING, INHERENT_TO_STRING_SHADOW_DISPLAY]);
88+
89+
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for InherentToString {
90+
fn check_impl_item(&mut self, cx: &LateContext<'a, 'tcx>, impl_item: &'tcx ImplItem) {
91+
if in_macro_or_desugar(impl_item.span) {
92+
return;
93+
}
94+
95+
if_chain! {
96+
// Check if item is a method, called to_string and has a parameter 'self'
97+
if let ImplItemKind::Method(ref signature, _) = impl_item.node;
98+
if impl_item.ident.name.as_str() == "to_string";
99+
let decl = &signature.decl;
100+
if decl.implicit_self.has_implicit_self();
101+
102+
// Check if return type is String
103+
if match_type(cx, return_ty(cx, impl_item.hir_id), &paths::STRING);
104+
105+
// Filters instances of to_string which are required by a trait
106+
if trait_ref_of_method(cx, impl_item.hir_id).is_none();
107+
108+
then {
109+
show_lint(cx, impl_item);
110+
}
111+
}
112+
}
113+
}
114+
115+
fn show_lint(cx: &LateContext<'_, '_>, item: &ImplItem) {
116+
let display_trait_id =
117+
get_trait_def_id(cx, &["core", "fmt", "Display"]).expect("Failed to get trait ID of 'Display'!");
118+
119+
// Get the real type of 'self'
120+
let fn_def_id = cx.tcx.hir().local_def_id(item.hir_id);
121+
let self_type = cx.tcx.fn_sig(fn_def_id).input(0);
122+
let self_type = walk_ptrs_ty(self_type.skip_binder());
123+
124+
// Emit either a warning or an error
125+
if implements_trait(cx, self_type, display_trait_id, &[]) {
126+
span_help_and_lint(
127+
cx,
128+
INHERENT_TO_STRING_SHADOW_DISPLAY,
129+
item.span,
130+
&format!(
131+
"type '{}' implements inherent method 'to_string() -> String' which shadows the implementation of 'Display'",
132+
self_type.to_string()
133+
),
134+
&format!("remove the inherent method from type '{}'", self_type.to_string())
135+
);
136+
} else {
137+
span_help_and_lint(
138+
cx,
139+
INHERENT_TO_STRING,
140+
item.span,
141+
&format!(
142+
"implementation of inherent method 'to_string() -> String' for type '{}'",
143+
self_type.to_string()
144+
),
145+
&format!("implement trait 'Display' for type '{}' instead", self_type.to_string()),
146+
);
147+
}
148+
}

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)]
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: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
error: implementation of inherent method 'to_string() -> 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() -> 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: #[deny(clippy::inherent_to_string_shadow_display)] on by default
21+
= help: remove the inherent method from type 'C'
22+
23+
error: aborting due to 2 previous errors
24+

0 commit comments

Comments
 (0)