Skip to content

Commit e522ca3

Browse files
committed
Auto merge of #5831 - chansuke:to_string_in_display, r=flip1995
Don't use `to_string` in impl Display fixes #3876 this PR is derived from [Toxyxer's implementation](#5574). changelog: add [`to_string_in_display`] lint
2 parents f8db258 + 8e54997 commit e522ca3

File tree

6 files changed

+178
-0
lines changed

6 files changed

+178
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1723,6 +1723,7 @@ Released 2018-09-13
17231723
[`temporary_assignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_assignment
17241724
[`temporary_cstring_as_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_cstring_as_ptr
17251725
[`to_digit_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_digit_is_some
1726+
[`to_string_in_display`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_display
17261727
[`todo`]: https://rust-lang.github.io/rust-clippy/master/index.html#todo
17271728
[`too_many_arguments`]: https://rust-lang.github.io/rust-clippy/master/index.html#too_many_arguments
17281729
[`too_many_lines`]: https://rust-lang.github.io/rust-clippy/master/index.html#too_many_lines

clippy_lints/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,7 @@ mod swap;
296296
mod tabs_in_doc_comments;
297297
mod temporary_assignment;
298298
mod to_digit_is_some;
299+
mod to_string_in_display;
299300
mod trait_bounds;
300301
mod transmute;
301302
mod transmuting_null;
@@ -788,6 +789,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
788789
&tabs_in_doc_comments::TABS_IN_DOC_COMMENTS,
789790
&temporary_assignment::TEMPORARY_ASSIGNMENT,
790791
&to_digit_is_some::TO_DIGIT_IS_SOME,
792+
&to_string_in_display::TO_STRING_IN_DISPLAY,
791793
&trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS,
792794
&trait_bounds::TYPE_REPETITION_IN_BOUNDS,
793795
&transmute::CROSSPOINTER_TRANSMUTE,
@@ -1017,6 +1019,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
10171019
store.register_early_pass(|| box reference::DerefAddrOf);
10181020
store.register_early_pass(|| box reference::RefInDeref);
10191021
store.register_early_pass(|| box double_parens::DoubleParens);
1022+
store.register_late_pass(|| box to_string_in_display::ToStringInDisplay::new());
10201023
store.register_early_pass(|| box unsafe_removed_from_name::UnsafeNameRemoval);
10211024
store.register_early_pass(|| box if_not_else::IfNotElse);
10221025
store.register_early_pass(|| box else_if_without_else::ElseIfWithoutElse);
@@ -1427,6 +1430,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
14271430
LintId::of(&tabs_in_doc_comments::TABS_IN_DOC_COMMENTS),
14281431
LintId::of(&temporary_assignment::TEMPORARY_ASSIGNMENT),
14291432
LintId::of(&to_digit_is_some::TO_DIGIT_IS_SOME),
1433+
LintId::of(&to_string_in_display::TO_STRING_IN_DISPLAY),
14301434
LintId::of(&transmute::CROSSPOINTER_TRANSMUTE),
14311435
LintId::of(&transmute::TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS),
14321436
LintId::of(&transmute::TRANSMUTE_BYTES_TO_STR),
@@ -1708,6 +1712,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
17081712
LintId::of(&suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL),
17091713
LintId::of(&suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL),
17101714
LintId::of(&swap::ALMOST_SWAPPED),
1715+
LintId::of(&to_string_in_display::TO_STRING_IN_DISPLAY),
17111716
LintId::of(&transmute::UNSOUND_COLLECTION_TRANSMUTE),
17121717
LintId::of(&transmute::WRONG_TRANSMUTE),
17131718
LintId::of(&transmuting_null::TRANSMUTING_NULL),
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
use crate::utils::{match_def_path, match_trait_method, paths, span_lint};
2+
use if_chain::if_chain;
3+
use rustc_hir::{Expr, ExprKind, Item, ItemKind};
4+
use rustc_lint::{LateContext, LateLintPass};
5+
use rustc_session::{declare_tool_lint, impl_lint_pass};
6+
7+
declare_clippy_lint! {
8+
/// **What it does:** Checks for uses of `to_string()` in `Display` traits.
9+
///
10+
/// **Why is this bad?** Usually `to_string` is implemented indirectly
11+
/// via `Display`. Hence using it while implementing `Display` would
12+
/// lead to infinite recursion.
13+
///
14+
/// **Known problems:** None.
15+
///
16+
/// **Example:**
17+
///
18+
/// ```rust
19+
/// use std::fmt;
20+
///
21+
/// struct Structure(i32);
22+
/// impl fmt::Display for Structure {
23+
/// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
24+
/// write!(f, "{}", self.to_string())
25+
/// }
26+
/// }
27+
///
28+
/// ```
29+
/// Use instead:
30+
/// ```rust
31+
/// use std::fmt;
32+
///
33+
/// struct Structure(i32);
34+
/// impl fmt::Display for Structure {
35+
/// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
36+
/// write!(f, "{}", self.0)
37+
/// }
38+
/// }
39+
/// ```
40+
pub TO_STRING_IN_DISPLAY,
41+
correctness,
42+
"to_string method used while implementing Display trait"
43+
}
44+
45+
#[derive(Default)]
46+
pub struct ToStringInDisplay {
47+
in_display_impl: bool,
48+
}
49+
50+
impl ToStringInDisplay {
51+
pub fn new() -> Self {
52+
Self { in_display_impl: false }
53+
}
54+
}
55+
56+
impl_lint_pass!(ToStringInDisplay => [TO_STRING_IN_DISPLAY]);
57+
58+
impl LateLintPass<'_> for ToStringInDisplay {
59+
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
60+
if is_display_impl(cx, item) {
61+
self.in_display_impl = true;
62+
}
63+
}
64+
65+
fn check_item_post(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
66+
if is_display_impl(cx, item) {
67+
self.in_display_impl = false;
68+
}
69+
}
70+
71+
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
72+
if_chain! {
73+
if let ExprKind::MethodCall(ref path, _, _, _) = expr.kind;
74+
if path.ident.name == sym!(to_string);
75+
if match_trait_method(cx, expr, &paths::TO_STRING);
76+
if self.in_display_impl;
77+
78+
then {
79+
span_lint(
80+
cx,
81+
TO_STRING_IN_DISPLAY,
82+
expr.span,
83+
"Using to_string in fmt::Display implementation might lead to infinite recursion",
84+
);
85+
}
86+
}
87+
}
88+
}
89+
90+
fn is_display_impl(cx: &LateContext<'_>, item: &Item<'_>) -> bool {
91+
if_chain! {
92+
if let ItemKind::Impl { of_trait: Some(trait_ref), .. } = &item.kind;
93+
if let Some(did) = trait_ref.trait_def_id();
94+
then {
95+
match_def_path(cx, did, &paths::DISPLAY_TRAIT)
96+
} else {
97+
false
98+
}
99+
}
100+
}

src/lintlist/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2166,6 +2166,13 @@ pub static ref ALL_LINTS: Vec<Lint> = vec![
21662166
deprecation: None,
21672167
module: "to_digit_is_some",
21682168
},
2169+
Lint {
2170+
name: "to_string_in_display",
2171+
group: "correctness",
2172+
desc: "to_string method used while implementing Display trait",
2173+
deprecation: None,
2174+
module: "to_string_in_display",
2175+
},
21692176
Lint {
21702177
name: "todo",
21712178
group: "restriction",

tests/ui/to_string_in_display.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#![warn(clippy::to_string_in_display)]
2+
#![allow(clippy::inherent_to_string_shadow_display)]
3+
4+
use std::fmt;
5+
6+
struct A;
7+
impl A {
8+
fn fmt(&self) {
9+
self.to_string();
10+
}
11+
}
12+
13+
trait B {
14+
fn fmt(&self) {}
15+
}
16+
17+
impl B for A {
18+
fn fmt(&self) {
19+
self.to_string();
20+
}
21+
}
22+
23+
impl fmt::Display for A {
24+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
25+
write!(f, "{}", self.to_string())
26+
}
27+
}
28+
29+
fn fmt(a: A) {
30+
a.to_string();
31+
}
32+
33+
struct C;
34+
35+
impl C {
36+
fn to_string(&self) -> String {
37+
String::from("I am C")
38+
}
39+
}
40+
41+
impl fmt::Display for C {
42+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
43+
write!(f, "{}", self.to_string())
44+
}
45+
}
46+
47+
fn main() {
48+
let a = A;
49+
a.to_string();
50+
a.fmt();
51+
fmt(a);
52+
53+
let c = C;
54+
c.to_string();
55+
}

tests/ui/to_string_in_display.stderr

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
error: Using to_string in fmt::Display implementation might lead to infinite recursion
2+
--> $DIR/to_string_in_display.rs:25:25
3+
|
4+
LL | write!(f, "{}", self.to_string())
5+
| ^^^^^^^^^^^^^^^^
6+
|
7+
= note: `-D clippy::to-string-in-display` implied by `-D warnings`
8+
9+
error: aborting due to previous error
10+

0 commit comments

Comments
 (0)