]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/format_impl.rs
separate the receiver from arguments in HIR under /clippy
[rust.git] / clippy_lints / src / format_impl.rs
1 use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
2 use clippy_utils::macros::{is_format_macro, root_macro_call_first_node, FormatArg, FormatArgsExpn};
3 use clippy_utils::{get_parent_as_impl, is_diag_trait_item, path_to_local, peel_ref_operators};
4 use if_chain::if_chain;
5 use rustc_errors::Applicability;
6 use rustc_hir::{Expr, ExprKind, Impl, ImplItem, ImplItemKind, QPath};
7 use rustc_lint::{LateContext, LateLintPass};
8 use rustc_session::{declare_tool_lint, impl_lint_pass};
9 use rustc_span::{sym, symbol::kw, Symbol};
10
11 declare_clippy_lint! {
12     /// ### What it does
13     /// Checks for format trait implementations (e.g. `Display`) with a recursive call to itself
14     /// which uses `self` as a parameter.
15     /// This is typically done indirectly with the `write!` macro or with `to_string()`.
16     ///
17     /// ### Why is this bad?
18     /// This will lead to infinite recursion and a stack overflow.
19     ///
20     /// ### Example
21     ///
22     /// ```rust
23     /// use std::fmt;
24     ///
25     /// struct Structure(i32);
26     /// impl fmt::Display for Structure {
27     ///     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
28     ///         write!(f, "{}", self.to_string())
29     ///     }
30     /// }
31     ///
32     /// ```
33     /// Use instead:
34     /// ```rust
35     /// use std::fmt;
36     ///
37     /// struct Structure(i32);
38     /// impl fmt::Display for Structure {
39     ///     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
40     ///         write!(f, "{}", self.0)
41     ///     }
42     /// }
43     /// ```
44     #[clippy::version = "1.48.0"]
45     pub RECURSIVE_FORMAT_IMPL,
46     correctness,
47     "Format trait method called while implementing the same Format trait"
48 }
49
50 declare_clippy_lint! {
51     /// ### What it does
52     /// Checks for use of `println`, `print`, `eprintln` or `eprint` in an
53     /// implementation of a formatting trait.
54     ///
55     /// ### Why is this bad?
56     /// Using a print macro is likely unintentional since formatting traits
57     /// should write to the `Formatter`, not stdout/stderr.
58     ///
59     /// ### Example
60     /// ```rust
61     /// use std::fmt::{Display, Error, Formatter};
62     ///
63     /// struct S;
64     /// impl Display for S {
65     ///     fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
66     ///         println!("S");
67     ///
68     ///         Ok(())
69     ///     }
70     /// }
71     /// ```
72     /// Use instead:
73     /// ```rust
74     /// use std::fmt::{Display, Error, Formatter};
75     ///
76     /// struct S;
77     /// impl Display for S {
78     ///     fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
79     ///         writeln!(f, "S");
80     ///
81     ///         Ok(())
82     ///     }
83     /// }
84     /// ```
85     #[clippy::version = "1.61.0"]
86     pub PRINT_IN_FORMAT_IMPL,
87     suspicious,
88     "use of a print macro in a formatting trait impl"
89 }
90
91 #[derive(Clone, Copy)]
92 struct FormatTrait {
93     /// e.g. `sym::Display`
94     name: Symbol,
95     /// `f` in `fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {}`
96     formatter_name: Option<Symbol>,
97 }
98
99 #[derive(Default)]
100 pub struct FormatImpl {
101     // Whether we are inside Display or Debug trait impl - None for neither
102     format_trait_impl: Option<FormatTrait>,
103 }
104
105 impl FormatImpl {
106     pub fn new() -> Self {
107         Self {
108             format_trait_impl: None,
109         }
110     }
111 }
112
113 impl_lint_pass!(FormatImpl => [RECURSIVE_FORMAT_IMPL, PRINT_IN_FORMAT_IMPL]);
114
115 impl<'tcx> LateLintPass<'tcx> for FormatImpl {
116     fn check_impl_item(&mut self, cx: &LateContext<'_>, impl_item: &ImplItem<'_>) {
117         self.format_trait_impl = is_format_trait_impl(cx, impl_item);
118     }
119
120     fn check_impl_item_post(&mut self, cx: &LateContext<'_>, impl_item: &ImplItem<'_>) {
121         // Assume no nested Impl of Debug and Display within eachother
122         if is_format_trait_impl(cx, impl_item).is_some() {
123             self.format_trait_impl = None;
124         }
125     }
126
127     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
128         let Some(format_trait_impl) = self.format_trait_impl else { return };
129
130         if format_trait_impl.name == sym::Display {
131             check_to_string_in_display(cx, expr);
132         }
133
134         check_self_in_format_args(cx, expr, format_trait_impl);
135         check_print_in_format_impl(cx, expr, format_trait_impl);
136     }
137 }
138
139 fn check_to_string_in_display(cx: &LateContext<'_>, expr: &Expr<'_>) {
140     if_chain! {
141         // Get the hir_id of the object we are calling the method on
142         if let ExprKind::MethodCall(path, self_arg, ..) = expr.kind;
143         // Is the method to_string() ?
144         if path.ident.name == sym::to_string;
145         // Is the method a part of the ToString trait? (i.e. not to_string() implemented
146         // separately)
147         if let Some(expr_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
148         if is_diag_trait_item(cx, expr_def_id, sym::ToString);
149         // Is the method is called on self
150         if let ExprKind::Path(QPath::Resolved(_, path)) = self_arg.kind;
151         if let [segment] = path.segments;
152         if segment.ident.name == kw::SelfLower;
153         then {
154             span_lint(
155                 cx,
156                 RECURSIVE_FORMAT_IMPL,
157                 expr.span,
158                 "using `self.to_string` in `fmt::Display` implementation will cause infinite recursion",
159             );
160         }
161     }
162 }
163
164 fn check_self_in_format_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, impl_trait: FormatTrait) {
165     // Check each arg in format calls - do we ever use Display on self (directly or via deref)?
166     if_chain! {
167         if let Some(outer_macro) = root_macro_call_first_node(cx, expr);
168         if let macro_def_id = outer_macro.def_id;
169         if let Some(format_args) = FormatArgsExpn::find_nested(cx, expr, outer_macro.expn);
170         if is_format_macro(cx, macro_def_id);
171         then {
172             for arg in format_args.args {
173                 if arg.format.r#trait != impl_trait.name {
174                     continue;
175                 }
176                 check_format_arg_self(cx, expr, &arg, impl_trait);
177             }
178         }
179     }
180 }
181
182 fn check_format_arg_self(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &FormatArg<'_>, impl_trait: FormatTrait) {
183     // Handle multiple dereferencing of references e.g. &&self
184     // Handle dereference of &self -> self that is equivalent (i.e. via *self in fmt() impl)
185     // Since the argument to fmt is itself a reference: &self
186     let reference = peel_ref_operators(cx, arg.param.value);
187     let map = cx.tcx.hir();
188     // Is the reference self?
189     if path_to_local(reference).map(|x| map.name(x)) == Some(kw::SelfLower) {
190         let FormatTrait { name, .. } = impl_trait;
191         span_lint(
192             cx,
193             RECURSIVE_FORMAT_IMPL,
194             expr.span,
195             &format!("using `self` as `{name}` in `impl {name}` will cause infinite recursion"),
196         );
197     }
198 }
199
200 fn check_print_in_format_impl(cx: &LateContext<'_>, expr: &Expr<'_>, impl_trait: FormatTrait) {
201     if_chain! {
202         if let Some(macro_call) = root_macro_call_first_node(cx, expr);
203         if let Some(name) = cx.tcx.get_diagnostic_name(macro_call.def_id);
204         then {
205             let replacement = match name {
206                 sym::print_macro | sym::eprint_macro => "write",
207                 sym::println_macro | sym::eprintln_macro => "writeln",
208                 _ => return,
209             };
210
211             let name = name.as_str().strip_suffix("_macro").unwrap();
212
213             span_lint_and_sugg(
214                 cx,
215                 PRINT_IN_FORMAT_IMPL,
216                 macro_call.span,
217                 &format!("use of `{}!` in `{}` impl", name, impl_trait.name),
218                 "replace with",
219                 if let Some(formatter_name) = impl_trait.formatter_name {
220                     format!("{}!({}, ..)", replacement, formatter_name)
221                 } else {
222                     format!("{}!(..)", replacement)
223                 },
224                 Applicability::HasPlaceholders,
225             );
226         }
227     }
228 }
229
230 fn is_format_trait_impl(cx: &LateContext<'_>, impl_item: &ImplItem<'_>) -> Option<FormatTrait> {
231     if_chain! {
232         if impl_item.ident.name == sym::fmt;
233         if let ImplItemKind::Fn(_, body_id) = impl_item.kind;
234         if let Some(Impl { of_trait: Some(trait_ref),..}) = get_parent_as_impl(cx.tcx, impl_item.hir_id());
235         if let Some(did) = trait_ref.trait_def_id();
236         if let Some(name) = cx.tcx.get_diagnostic_name(did);
237         if matches!(name, sym::Debug | sym::Display);
238         then {
239             let body = cx.tcx.hir().body(body_id);
240             let formatter_name = body.params.get(1)
241                 .and_then(|param| param.pat.simple_ident())
242                 .map(|ident| ident.name);
243
244             Some(FormatTrait {
245                 name,
246                 formatter_name,
247             })
248         } else {
249             None
250         }
251     }
252 }