]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/to_string_in_display.rs
Rollup merge of #84484 - jyn514:check-tools, r=Mark-Simulacrum
[rust.git] / clippy_lints / src / to_string_in_display.rs
1 use clippy_utils::diagnostics::span_lint;
2 use clippy_utils::{is_diag_trait_item, match_def_path, path_to_local_id, paths};
3 use if_chain::if_chain;
4 use rustc_hir::{Expr, ExprKind, HirId, Impl, ImplItem, ImplItemKind, Item, ItemKind};
5 use rustc_lint::{LateContext, LateLintPass};
6 use rustc_session::{declare_tool_lint, impl_lint_pass};
7 use rustc_span::symbol::sym;
8
9 declare_clippy_lint! {
10     /// **What it does:** Checks for uses of `to_string()` in `Display` traits.
11     ///
12     /// **Why is this bad?** Usually `to_string` is implemented indirectly
13     /// via `Display`. Hence using it while implementing `Display` would
14     /// lead to infinite recursion.
15     ///
16     /// **Known problems:** None.
17     ///
18     /// **Example:**
19     ///
20     /// ```rust
21     /// use std::fmt;
22     ///
23     /// struct Structure(i32);
24     /// impl fmt::Display for Structure {
25     ///     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
26     ///         write!(f, "{}", self.to_string())
27     ///     }
28     /// }
29     ///
30     /// ```
31     /// Use instead:
32     /// ```rust
33     /// use std::fmt;
34     ///
35     /// struct Structure(i32);
36     /// impl fmt::Display for Structure {
37     ///     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
38     ///         write!(f, "{}", self.0)
39     ///     }
40     /// }
41     /// ```
42     pub TO_STRING_IN_DISPLAY,
43     correctness,
44     "`to_string` method used while implementing `Display` trait"
45 }
46
47 #[derive(Default)]
48 pub struct ToStringInDisplay {
49     in_display_impl: bool,
50     self_hir_id: Option<HirId>,
51 }
52
53 impl ToStringInDisplay {
54     pub fn new() -> Self {
55         Self {
56             in_display_impl: false,
57             self_hir_id: None,
58         }
59     }
60 }
61
62 impl_lint_pass!(ToStringInDisplay => [TO_STRING_IN_DISPLAY]);
63
64 impl LateLintPass<'_> for ToStringInDisplay {
65     fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
66         if is_display_impl(cx, item) {
67             self.in_display_impl = true;
68         }
69     }
70
71     fn check_item_post(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
72         if is_display_impl(cx, item) {
73             self.in_display_impl = false;
74             self.self_hir_id = None;
75         }
76     }
77
78     fn check_impl_item(&mut self, cx: &LateContext<'_>, impl_item: &ImplItem<'_>) {
79         if_chain! {
80             if self.in_display_impl;
81             if let ImplItemKind::Fn(.., body_id) = &impl_item.kind;
82             let body = cx.tcx.hir().body(*body_id);
83             if !body.params.is_empty();
84             then {
85                 let self_param = &body.params[0];
86                 self.self_hir_id = Some(self_param.pat.hir_id);
87             }
88         }
89     }
90
91     fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
92         if_chain! {
93             if self.in_display_impl;
94             if let Some(self_hir_id) = self.self_hir_id;
95             if let ExprKind::MethodCall(path, _, args, _) = expr.kind;
96             if path.ident.name == sym!(to_string);
97             if let Some(expr_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
98             if is_diag_trait_item(cx, expr_def_id, sym::ToString);
99             if path_to_local_id(&args[0], self_hir_id);
100             then {
101                 span_lint(
102                     cx,
103                     TO_STRING_IN_DISPLAY,
104                     expr.span,
105                     "using `to_string` in `fmt::Display` implementation might lead to infinite recursion",
106                 );
107             }
108         }
109     }
110 }
111
112 fn is_display_impl(cx: &LateContext<'_>, item: &Item<'_>) -> bool {
113     if_chain! {
114         if let ItemKind::Impl(Impl { of_trait: Some(trait_ref), .. }) = &item.kind;
115         if let Some(did) = trait_ref.trait_def_id();
116         then {
117             match_def_path(cx, did, &paths::DISPLAY_TRAIT)
118         } else {
119             false
120         }
121     }
122 }