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- }
24-
2511declare_clippy_lint ! {
2612 /// ### What it does
2713 /// Checks for format trait implementations (e.g. `Display`) with a recursive call to itself
@@ -61,47 +47,92 @@ declare_clippy_lint! {
6147 "Format trait method called while implementing the same Format trait"
6248}
6349
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+
6499#[ derive( Default ) ]
65- pub struct RecursiveFormatImpl {
100+ pub struct FormatImpl {
66101 // Whether we are inside Display or Debug trait impl - None for neither
67102 format_trait_impl : Option < FormatTrait > ,
68103}
69104
70- impl RecursiveFormatImpl {
105+ impl FormatImpl {
71106 pub fn new ( ) -> Self {
72107 Self {
73108 format_trait_impl : None ,
74109 }
75110 }
76111}
77112
78- impl_lint_pass ! ( RecursiveFormatImpl => [ RECURSIVE_FORMAT_IMPL ] ) ;
113+ impl_lint_pass ! ( FormatImpl => [ RECURSIVE_FORMAT_IMPL , PRINT_IN_FORMAT_IMPL ] ) ;
79114
80- impl < ' tcx > LateLintPass < ' tcx > for RecursiveFormatImpl {
81- fn check_item ( & mut self , cx : & LateContext < ' _ > , item : & Item < ' _ > ) {
82- if let Some ( format_trait_impl) = is_format_trait_impl ( cx, item) {
83- self . format_trait_impl = Some ( format_trait_impl) ;
84- }
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) ;
85118 }
86119
87- fn check_item_post ( & mut self , cx : & LateContext < ' _ > , item : & Item < ' _ > ) {
120+ fn check_impl_item_post ( & mut self , cx : & LateContext < ' _ > , impl_item : & ImplItem < ' _ > ) {
88121 // Assume no nested Impl of Debug and Display within eachother
89- if is_format_trait_impl ( cx, item ) . is_some ( ) {
122+ if is_format_trait_impl ( cx, impl_item ) . is_some ( ) {
90123 self . format_trait_impl = None ;
91124 }
92125 }
93126
94127 fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) {
95- match self . format_trait_impl {
96- Some ( FormatTrait :: Display ) => {
97- check_to_string_in_display ( cx, expr) ;
98- check_self_in_format_args ( cx, expr, FormatTrait :: Display ) ;
99- } ,
100- Some ( FormatTrait :: Debug ) => {
101- check_self_in_format_args ( cx, expr, FormatTrait :: Debug ) ;
102- } ,
103- 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) ;
104132 }
133+
134+ check_self_in_format_args ( cx, expr, format_trait_impl) ;
135+ check_print_in_format_impl ( cx, expr, format_trait_impl) ;
105136 }
106137}
107138
@@ -140,7 +171,7 @@ fn check_self_in_format_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>,
140171 if let Some ( args) = format_args. args( ) ;
141172 then {
142173 for arg in args {
143- if arg. format_trait != impl_trait. name( ) {
174+ if arg. format_trait != impl_trait. name {
144175 continue ;
145176 }
146177 check_format_arg_self( cx, expr, & arg, impl_trait) ;
@@ -156,33 +187,65 @@ fn check_format_arg_self(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &FormatArgs
156187 let reference = peel_ref_operators ( cx, arg. value ) ;
157188 let map = cx. tcx . hir ( ) ;
158189 // Is the reference self?
159- let symbol_ident = impl_trait. name ( ) . to_ident_string ( ) ;
160190 if path_to_local ( reference) . map ( |x| map. name ( x) ) == Some ( kw:: SelfLower ) {
191+ let FormatTrait { name, .. } = impl_trait;
161192 span_lint (
162193 cx,
163194 RECURSIVE_FORMAT_IMPL ,
164195 expr. span ,
165- & format ! (
166- "using `self` as `{}` in `impl {}` will cause infinite recursion" ,
167- & symbol_ident, & symbol_ident
168- ) ,
196+ & format ! ( "using `self` as `{name}` in `impl {name}` will cause infinite recursion" ) ,
169197 ) ;
170198 }
171199}
172200
173- 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 ) {
174202 if_chain ! {
175- // Are we at an Impl?
176- if let ItemKind :: Impl ( Impl { of_trait: Some ( trait_ref) , .. } ) = & item. kind;
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 > {
232+ if_chain ! {
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( ) ) ;
177236 if let Some ( did) = trait_ref. trait_def_id( ) ;
178237 if let Some ( name) = cx. tcx. get_diagnostic_name( did) ;
238+ if matches!( name, sym:: Debug | sym:: Display ) ;
179239 then {
180- // Is Impl for Debug or Display?
181- match name {
182- sym:: Debug => Some ( FormatTrait :: Debug ) ,
183- sym:: Display => Some ( FormatTrait :: Display ) ,
184- _ => None ,
185- }
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+ } )
186249 } else {
187250 None
188251 }
0 commit comments