1
- use clippy_utils:: diagnostics:: span_lint;
1
+ use clippy_utils:: diagnostics:: { span_lint, span_lint_and_sugg } ;
2
2
use 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} ;
4
4
use 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 } ;
6
7
use rustc_lint:: { LateContext , LateLintPass } ;
7
8
use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
8
9
use rustc_span:: { sym, symbol:: kw, Symbol } ;
9
10
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
11
declare_clippy_lint ! {
25
12
/// ### What it does
26
13
/// Checks for format trait implementations (e.g. `Display`) with a recursive call to itself
@@ -60,6 +47,56 @@ declare_clippy_lint! {
60
47
"Format trait method called while implementing the same Format trait"
61
48
}
62
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
+ /// 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
+
63
100
#[ derive( Default ) ]
64
101
pub struct FormatImpl {
65
102
// Whether we are inside Display or Debug trait impl - None for neither
@@ -74,33 +111,29 @@ impl FormatImpl {
74
111
}
75
112
}
76
113
77
- impl_lint_pass ! ( FormatImpl => [ RECURSIVE_FORMAT_IMPL ] ) ;
114
+ impl_lint_pass ! ( FormatImpl => [ RECURSIVE_FORMAT_IMPL , PRINT_IN_FORMAT_IMPL ] ) ;
78
115
79
116
impl < ' 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) ;
84
119
}
85
120
86
- fn check_item_post ( & mut self , cx : & LateContext < ' _ > , item : & Item < ' _ > ) {
121
+ fn check_impl_item_post ( & mut self , cx : & LateContext < ' _ > , impl_item : & ImplItem < ' _ > ) {
87
122
// 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 ( ) {
89
124
self . format_trait_impl = None ;
90
125
}
91
126
}
92
127
93
128
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) ;
103
133
}
134
+
135
+ check_self_in_format_args ( cx, expr, format_trait_impl) ;
136
+ check_print_in_format_impl ( cx, expr, format_trait_impl) ;
104
137
}
105
138
}
106
139
@@ -139,7 +172,7 @@ fn check_self_in_format_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>,
139
172
if let Some ( args) = format_args. args( ) ;
140
173
then {
141
174
for arg in args {
142
- if arg. format_trait != impl_trait. name( ) {
175
+ if arg. format_trait != impl_trait. name {
143
176
continue ;
144
177
}
145
178
check_format_arg_self( cx, expr, & arg, impl_trait) ;
@@ -155,33 +188,65 @@ fn check_format_arg_self(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &FormatArgs
155
188
let reference = peel_ref_operators ( cx, arg. value ) ;
156
189
let map = cx. tcx . hir ( ) ;
157
190
// Is the reference self?
158
- let symbol_ident = impl_trait. name ( ) . to_ident_string ( ) ;
159
191
if path_to_local ( reference) . map ( |x| map. name ( x) ) == Some ( kw:: SelfLower ) {
192
+ let FormatTrait { name, .. } = impl_trait;
160
193
span_lint (
161
194
cx,
162
195
RECURSIVE_FORMAT_IMPL ,
163
196
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" ) ,
168
198
) ;
169
199
}
170
200
}
171
201
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 > {
173
233
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( ) ) ;
176
237
if let Some ( did) = trait_ref. trait_def_id( ) ;
177
238
if let Some ( name) = cx. tcx. get_diagnostic_name( did) ;
239
+ if matches!( name, sym:: Debug | sym:: Display ) ;
178
240
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
+ } )
185
250
} else {
186
251
None
187
252
}
0 commit comments