1- use clippy_utils:: diagnostics:: span_lint;
1+ use clippy_utils:: diagnostics:: { span_lint, span_lint_and_sugg } ;
22use clippy_utils:: macros:: { is_format_macro, root_macro_call_first_node, FormatArgsArg , FormatArgsExpn } ;
3- use clippy_utils:: { is_diag_trait_item, path_to_local, peel_ref_operators} ;
3+ use clippy_utils:: { get_parent_as_impl , is_diag_trait_item, path_to_local, peel_ref_operators} ;
44use if_chain:: if_chain;
5- use rustc_hir:: { Expr , ExprKind , Impl , Item , ItemKind , QPath } ;
5+ use rustc_errors:: Applicability ;
6+ use rustc_hir:: { Expr , ExprKind , Impl , ImplItem , ImplItemKind , QPath } ;
67use rustc_lint:: { LateContext , LateLintPass } ;
78use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
89use rustc_span:: { sym, symbol:: kw, Symbol } ;
910
10- #[ derive( Clone , Copy ) ]
11- enum FormatTrait {
12- Debug ,
13- Display ,
14- }
15-
16- impl FormatTrait {
17- fn name ( self ) -> Symbol {
18- match self {
19- FormatTrait :: Debug => sym:: Debug ,
20- FormatTrait :: Display => sym:: Display ,
21- }
22- }
23- }
2411declare_clippy_lint ! {
2512 /// ### What it does
2613 /// Checks for format trait implementations (e.g. `Display`) with a recursive call to itself
@@ -60,6 +47,56 @@ declare_clippy_lint! {
6047 "Format trait method called while implementing the same Format trait"
6148}
6249
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+ /// Printing during a formatting trait impl is likely unintentional. It can
57+ /// cause output to be split between the wrong streams. If `format!` is used
58+ /// text would go to stdout/stderr even if not desired.
59+ ///
60+ /// ### Example
61+ /// ```rust
62+ /// use std::fmt::{Display, Error, Formatter};
63+ ///
64+ /// struct S;
65+ /// impl Display for S {
66+ /// fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
67+ /// println!("S");
68+ ///
69+ /// Ok(())
70+ /// }
71+ /// }
72+ /// ```
73+ /// Use instead:
74+ /// ```rust
75+ /// use std::fmt::{Display, Error, Formatter};
76+ ///
77+ /// struct S;
78+ /// impl Display for S {
79+ /// fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
80+ /// writeln!(f, "S");
81+ ///
82+ /// Ok(())
83+ /// }
84+ /// }
85+ /// ```
86+ #[ clippy:: version = "1.60.0" ]
87+ pub PRINT_IN_FORMAT_IMPL ,
88+ suspicious,
89+ "use of a print macro in a formatting trait impl"
90+ }
91+
92+ #[ derive( Clone , Copy ) ]
93+ struct FormatTrait {
94+ /// e.g. `sym::Display`
95+ name : Symbol ,
96+ /// `f` in `fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {}`
97+ formatter_name : Option < Symbol > ,
98+ }
99+
63100#[ derive( Default ) ]
64101pub struct FormatImpl {
65102 // Whether we are inside Display or Debug trait impl - None for neither
@@ -74,33 +111,29 @@ impl FormatImpl {
74111 }
75112}
76113
77- impl_lint_pass ! ( FormatImpl => [ RECURSIVE_FORMAT_IMPL ] ) ;
114+ impl_lint_pass ! ( FormatImpl => [ RECURSIVE_FORMAT_IMPL , PRINT_IN_FORMAT_IMPL ] ) ;
78115
79116impl < ' tcx > LateLintPass < ' tcx > for FormatImpl {
80- fn check_item ( & mut self , cx : & LateContext < ' _ > , item : & Item < ' _ > ) {
81- if let Some ( format_trait_impl) = is_format_trait_impl ( cx, item) {
82- self . format_trait_impl = Some ( format_trait_impl) ;
83- }
117+ fn check_impl_item ( & mut self , cx : & LateContext < ' _ > , impl_item : & ImplItem < ' _ > ) {
118+ self . format_trait_impl = is_format_trait_impl ( cx, impl_item) ;
84119 }
85120
86- fn check_item_post ( & mut self , cx : & LateContext < ' _ > , item : & Item < ' _ > ) {
121+ fn check_impl_item_post ( & mut self , cx : & LateContext < ' _ > , impl_item : & ImplItem < ' _ > ) {
87122 // Assume no nested Impl of Debug and Display within eachother
88- if is_format_trait_impl ( cx, item ) . is_some ( ) {
123+ if is_format_trait_impl ( cx, impl_item ) . is_some ( ) {
89124 self . format_trait_impl = None ;
90125 }
91126 }
92127
93128 fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) {
94- match self . format_trait_impl {
95- Some ( FormatTrait :: Display ) => {
96- check_to_string_in_display ( cx, expr) ;
97- check_self_in_format_args ( cx, expr, FormatTrait :: Display ) ;
98- } ,
99- Some ( FormatTrait :: Debug ) => {
100- check_self_in_format_args ( cx, expr, FormatTrait :: Debug ) ;
101- } ,
102- None => { } ,
129+ let Some ( format_trait_impl) = self . format_trait_impl else { return } ;
130+
131+ if format_trait_impl. name == sym:: Display {
132+ check_to_string_in_display ( cx, expr) ;
103133 }
134+
135+ check_self_in_format_args ( cx, expr, format_trait_impl) ;
136+ check_print_in_format_impl ( cx, expr, format_trait_impl) ;
104137 }
105138}
106139
@@ -139,7 +172,7 @@ fn check_self_in_format_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>,
139172 if let Some ( args) = format_args. args( ) ;
140173 then {
141174 for arg in args {
142- if arg. format_trait != impl_trait. name( ) {
175+ if arg. format_trait != impl_trait. name {
143176 continue ;
144177 }
145178 check_format_arg_self( cx, expr, & arg, impl_trait) ;
@@ -155,33 +188,65 @@ fn check_format_arg_self(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &FormatArgs
155188 let reference = peel_ref_operators ( cx, arg. value ) ;
156189 let map = cx. tcx . hir ( ) ;
157190 // Is the reference self?
158- let symbol_ident = impl_trait. name ( ) . to_ident_string ( ) ;
159191 if path_to_local ( reference) . map ( |x| map. name ( x) ) == Some ( kw:: SelfLower ) {
192+ let FormatTrait { name, .. } = impl_trait;
160193 span_lint (
161194 cx,
162195 RECURSIVE_FORMAT_IMPL ,
163196 expr. span ,
164- & format ! (
165- "using `self` as `{}` in `impl {}` will cause infinite recursion" ,
166- & symbol_ident, & symbol_ident
167- ) ,
197+ & format ! ( "using `self` as `{name}` in `impl {name}` will cause infinite recursion" ) ,
168198 ) ;
169199 }
170200}
171201
172- fn is_format_trait_impl ( cx : & LateContext < ' _ > , item : & Item < ' _ > ) -> Option < FormatTrait > {
202+ fn check_print_in_format_impl ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > , impl_trait : FormatTrait ) {
203+ if_chain ! {
204+ if let Some ( macro_call) = root_macro_call_first_node( cx, expr) ;
205+ if let Some ( name) = cx. tcx. get_diagnostic_name( macro_call. def_id) ;
206+ then {
207+ let replacement = match name {
208+ sym:: print_macro | sym:: eprint_macro => "write" ,
209+ sym:: println_macro | sym:: eprintln_macro => "writeln" ,
210+ _ => return ,
211+ } ;
212+
213+ let name = name. as_str( ) . strip_suffix( "_macro" ) . unwrap( ) ;
214+
215+ span_lint_and_sugg(
216+ cx,
217+ PRINT_IN_FORMAT_IMPL ,
218+ macro_call. span,
219+ & format!( "use of `{}!` in `{}` impl" , name, impl_trait. name) ,
220+ "replace with" ,
221+ if let Some ( formatter_name) = impl_trait. formatter_name {
222+ format!( "{}!({}, ..)" , replacement, formatter_name)
223+ } else {
224+ format!( "{}!(..)" , replacement)
225+ } ,
226+ Applicability :: HasPlaceholders ,
227+ ) ;
228+ }
229+ }
230+ }
231+
232+ fn is_format_trait_impl ( cx : & LateContext < ' _ > , impl_item : & ImplItem < ' _ > ) -> Option < FormatTrait > {
173233 if_chain ! {
174- // Are we at an Impl?
175- if let ItemKind :: Impl ( Impl { of_trait: Some ( trait_ref) , .. } ) = & item. kind;
234+ if impl_item. ident. name == sym:: fmt;
235+ if let ImplItemKind :: Fn ( _, body_id) = impl_item. kind;
236+ if let Some ( Impl { of_trait: Some ( trait_ref) , ..} ) = get_parent_as_impl( cx. tcx, impl_item. hir_id( ) ) ;
176237 if let Some ( did) = trait_ref. trait_def_id( ) ;
177238 if let Some ( name) = cx. tcx. get_diagnostic_name( did) ;
239+ if matches!( name, sym:: Debug | sym:: Display ) ;
178240 then {
179- // Is Impl for Debug or Display?
180- match name {
181- sym:: Debug => Some ( FormatTrait :: Debug ) ,
182- sym:: Display => Some ( FormatTrait :: Display ) ,
183- _ => None ,
184- }
241+ let body = cx. tcx. hir( ) . body( body_id) ;
242+ let formatter_name = body. params. get( 1 )
243+ . and_then( |param| param. pat. simple_ident( ) )
244+ . map( |ident| ident. name) ;
245+
246+ Some ( FormatTrait {
247+ name,
248+ formatter_name,
249+ } )
185250 } else {
186251 None
187252 }
0 commit comments