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
-
25
11
declare_clippy_lint ! {
26
12
/// ### What it does
27
13
/// Checks for format trait implementations (e.g. `Display`) with a recursive call to itself
@@ -61,47 +47,92 @@ declare_clippy_lint! {
61
47
"Format trait method called while implementing the same Format trait"
62
48
}
63
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
+ /// 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
+
64
99
#[ derive( Default ) ]
65
- pub struct RecursiveFormatImpl {
100
+ pub struct FormatImpl {
66
101
// Whether we are inside Display or Debug trait impl - None for neither
67
102
format_trait_impl : Option < FormatTrait > ,
68
103
}
69
104
70
- impl RecursiveFormatImpl {
105
+ impl FormatImpl {
71
106
pub fn new ( ) -> Self {
72
107
Self {
73
108
format_trait_impl : None ,
74
109
}
75
110
}
76
111
}
77
112
78
- impl_lint_pass ! ( RecursiveFormatImpl => [ RECURSIVE_FORMAT_IMPL ] ) ;
113
+ impl_lint_pass ! ( FormatImpl => [ RECURSIVE_FORMAT_IMPL , PRINT_IN_FORMAT_IMPL ] ) ;
79
114
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) ;
85
118
}
86
119
87
- fn check_item_post ( & mut self , cx : & LateContext < ' _ > , item : & Item < ' _ > ) {
120
+ fn check_impl_item_post ( & mut self , cx : & LateContext < ' _ > , impl_item : & ImplItem < ' _ > ) {
88
121
// 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 ( ) {
90
123
self . format_trait_impl = None ;
91
124
}
92
125
}
93
126
94
127
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) ;
104
132
}
133
+
134
+ check_self_in_format_args ( cx, expr, format_trait_impl) ;
135
+ check_print_in_format_impl ( cx, expr, format_trait_impl) ;
105
136
}
106
137
}
107
138
@@ -140,7 +171,7 @@ fn check_self_in_format_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>,
140
171
if let Some ( args) = format_args. args( ) ;
141
172
then {
142
173
for arg in args {
143
- if arg. format_trait != impl_trait. name( ) {
174
+ if arg. format_trait != impl_trait. name {
144
175
continue ;
145
176
}
146
177
check_format_arg_self( cx, expr, & arg, impl_trait) ;
@@ -156,33 +187,65 @@ fn check_format_arg_self(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &FormatArgs
156
187
let reference = peel_ref_operators ( cx, arg. value ) ;
157
188
let map = cx. tcx . hir ( ) ;
158
189
// Is the reference self?
159
- let symbol_ident = impl_trait. name ( ) . to_ident_string ( ) ;
160
190
if path_to_local ( reference) . map ( |x| map. name ( x) ) == Some ( kw:: SelfLower ) {
191
+ let FormatTrait { name, .. } = impl_trait;
161
192
span_lint (
162
193
cx,
163
194
RECURSIVE_FORMAT_IMPL ,
164
195
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" ) ,
169
197
) ;
170
198
}
171
199
}
172
200
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 ) {
174
202
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( ) ) ;
177
236
if let Some ( did) = trait_ref. trait_def_id( ) ;
178
237
if let Some ( name) = cx. tcx. get_diagnostic_name( did) ;
238
+ if matches!( name, sym:: Debug | sym:: Display ) ;
179
239
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
+ } )
186
249
} else {
187
250
None
188
251
}
0 commit comments