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};
11 declare_clippy_lint! {
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()`.
17 /// ### Why is this bad?
18 /// This will lead to infinite recursion and a stack overflow.
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())
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)
44 #[clippy::version = "1.48.0"]
45 pub RECURSIVE_FORMAT_IMPL,
47 "Format trait method called while implementing the same Format trait"
50 declare_clippy_lint! {
52 /// Checks for use of `println`, `print`, `eprintln` or `eprint` in an
53 /// implementation of a formatting trait.
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.
61 /// use std::fmt::{Display, Error, Formatter};
64 /// impl Display for S {
65 /// fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
74 /// use std::fmt::{Display, Error, Formatter};
77 /// impl Display for S {
78 /// fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
85 #[clippy::version = "1.61.0"]
86 pub PRINT_IN_FORMAT_IMPL,
88 "use of a print macro in a formatting trait impl"
91 #[derive(Clone, Copy)]
93 /// e.g. `sym::Display`
95 /// `f` in `fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {}`
96 formatter_name: Option<Symbol>,
100 pub struct FormatImpl {
101 // Whether we are inside Display or Debug trait impl - None for neither
102 format_trait_impl: Option<FormatTrait>,
106 pub fn new() -> Self {
108 format_trait_impl: None,
113 impl_lint_pass!(FormatImpl => [RECURSIVE_FORMAT_IMPL, PRINT_IN_FORMAT_IMPL]);
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);
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;
127 fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
128 let Some(format_trait_impl) = self.format_trait_impl else { return };
130 if format_trait_impl.name == sym::Display {
131 check_to_string_in_display(cx, expr);
134 check_self_in_format_args(cx, expr, format_trait_impl);
135 check_print_in_format_impl(cx, expr, format_trait_impl);
139 fn check_to_string_in_display(cx: &LateContext<'_>, expr: &Expr<'_>) {
141 // Get the hir_id of the object we are calling the method on
142 if let ExprKind::MethodCall(path, [ref 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
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;
156 RECURSIVE_FORMAT_IMPL,
158 "using `self.to_string` in `fmt::Display` implementation will cause infinite recursion",
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)?
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);
172 for arg in format_args.args {
173 if arg.format.r#trait != impl_trait.name {
176 check_format_arg_self(cx, expr, &arg, impl_trait);
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;
193 RECURSIVE_FORMAT_IMPL,
195 &format!("using `self` as `{name}` in `impl {name}` will cause infinite recursion"),
200 fn check_print_in_format_impl(cx: &LateContext<'_>, expr: &Expr<'_>, impl_trait: FormatTrait) {
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);
205 let replacement = match name {
206 sym::print_macro | sym::eprint_macro => "write",
207 sym::println_macro | sym::eprintln_macro => "writeln",
211 let name = name.as_str().strip_suffix("_macro").unwrap();
215 PRINT_IN_FORMAT_IMPL,
217 &format!("use of `{}!` in `{}` impl", name, impl_trait.name),
219 if let Some(formatter_name) = impl_trait.formatter_name {
220 format!("{}!({}, ..)", replacement, formatter_name)
222 format!("{}!(..)", replacement)
224 Applicability::HasPlaceholders,
230 fn is_format_trait_impl(cx: &LateContext<'_>, impl_item: &ImplItem<'_>) -> Option<FormatTrait> {
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);
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);