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,55 @@ 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+ /// 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+
6399#[ derive( Default ) ]
64100pub struct FormatImpl {
65101 // Whether we are inside Display or Debug trait impl - None for neither
@@ -74,33 +110,29 @@ impl FormatImpl {
74110 }
75111}
76112
77- impl_lint_pass ! ( FormatImpl => [ RECURSIVE_FORMAT_IMPL ] ) ;
113+ impl_lint_pass ! ( FormatImpl => [ RECURSIVE_FORMAT_IMPL , PRINT_IN_FORMAT_IMPL ] ) ;
78114
79115impl < ' 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- }
116+ fn check_impl_item ( & mut self , cx : & LateContext < ' _ > , impl_item : & ImplItem < ' _ > ) {
117+ self . format_trait_impl = is_format_trait_impl ( cx, impl_item) ;
84118 }
85119
86- fn check_item_post ( & mut self , cx : & LateContext < ' _ > , item : & Item < ' _ > ) {
120+ fn check_impl_item_post ( & mut self , cx : & LateContext < ' _ > , impl_item : & ImplItem < ' _ > ) {
87121 // Assume no nested Impl of Debug and Display within eachother
88- if is_format_trait_impl ( cx, item ) . is_some ( ) {
122+ if is_format_trait_impl ( cx, impl_item ) . is_some ( ) {
89123 self . format_trait_impl = None ;
90124 }
91125 }
92126
93127 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 => { } ,
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) ;
103132 }
133+
134+ check_self_in_format_args ( cx, expr, format_trait_impl) ;
135+ check_print_in_format_impl ( cx, expr, format_trait_impl) ;
104136 }
105137}
106138
@@ -139,7 +171,7 @@ fn check_self_in_format_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>,
139171 if let Some ( args) = format_args. args( ) ;
140172 then {
141173 for arg in args {
142- if arg. format_trait != impl_trait. name( ) {
174+ if arg. format_trait != impl_trait. name {
143175 continue ;
144176 }
145177 check_format_arg_self( cx, expr, & arg, impl_trait) ;
@@ -155,33 +187,65 @@ fn check_format_arg_self(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &FormatArgs
155187 let reference = peel_ref_operators ( cx, arg. value ) ;
156188 let map = cx. tcx . hir ( ) ;
157189 // Is the reference self?
158- let symbol_ident = impl_trait. name ( ) . to_ident_string ( ) ;
159190 if path_to_local ( reference) . map ( |x| map. name ( x) ) == Some ( kw:: SelfLower ) {
191+ let FormatTrait { name, .. } = impl_trait;
160192 span_lint (
161193 cx,
162194 RECURSIVE_FORMAT_IMPL ,
163195 expr. span ,
164- & format ! (
165- "using `self` as `{}` in `impl {}` will cause infinite recursion" ,
166- & symbol_ident, & symbol_ident
167- ) ,
196+ & format ! ( "using `self` as `{name}` in `impl {name}` will cause infinite recursion" ) ,
168197 ) ;
169198 }
170199}
171200
172- fn is_format_trait_impl ( cx : & LateContext < ' _ > , item : & Item < ' _ > ) -> Option < FormatTrait > {
201+ fn check_print_in_format_impl ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > , impl_trait : FormatTrait ) {
202+ if_chain ! {
203+ if let Some ( macro_call) = root_macro_call_first_node( cx, expr) ;
204+ if let Some ( name) = cx. tcx. get_diagnostic_name( macro_call. def_id) ;
205+ then {
206+ let replacement = match name {
207+ sym:: print_macro | sym:: eprint_macro => "write" ,
208+ sym:: println_macro | sym:: eprintln_macro => "writeln" ,
209+ _ => return ,
210+ } ;
211+
212+ let name = name. as_str( ) . strip_suffix( "_macro" ) . unwrap( ) ;
213+
214+ span_lint_and_sugg(
215+ cx,
216+ PRINT_IN_FORMAT_IMPL ,
217+ macro_call. span,
218+ & format!( "use of `{}!` in `{}` impl" , name, impl_trait. name) ,
219+ "replace with" ,
220+ if let Some ( formatter_name) = impl_trait. formatter_name {
221+ format!( "{}!({}, ..)" , replacement, formatter_name)
222+ } else {
223+ format!( "{}!(..)" , replacement)
224+ } ,
225+ Applicability :: HasPlaceholders ,
226+ ) ;
227+ }
228+ }
229+ }
230+
231+ fn is_format_trait_impl ( cx : & LateContext < ' _ > , impl_item : & ImplItem < ' _ > ) -> Option < FormatTrait > {
173232 if_chain ! {
174- // Are we at an Impl?
175- if let ItemKind :: Impl ( Impl { of_trait: Some ( trait_ref) , .. } ) = & item. kind;
233+ if impl_item. ident. name == sym:: fmt;
234+ if let ImplItemKind :: Fn ( _, body_id) = impl_item. kind;
235+ if let Some ( Impl { of_trait: Some ( trait_ref) , ..} ) = get_parent_as_impl( cx. tcx, impl_item. hir_id( ) ) ;
176236 if let Some ( did) = trait_ref. trait_def_id( ) ;
177237 if let Some ( name) = cx. tcx. get_diagnostic_name( did) ;
238+ if matches!( name, sym:: Debug | sym:: Display ) ;
178239 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- }
240+ let body = cx. tcx. hir( ) . body( body_id) ;
241+ let formatter_name = body. params. get( 1 )
242+ . and_then( |param| param. pat. simple_ident( ) )
243+ . map( |ident| ident. name) ;
244+
245+ Some ( FormatTrait {
246+ name,
247+ formatter_name,
248+ } )
185249 } else {
186250 None
187251 }
0 commit comments