@@ -6,7 +6,6 @@ use clippy_utils::{
6
6
ty:: match_type,
7
7
visitors:: { for_each_expr, Visitable } ,
8
8
} ;
9
- use if_chain:: if_chain;
10
9
use rustc_data_structures:: fx:: FxHashSet ;
11
10
use rustc_hir:: {
12
11
def:: { DefKind , Res } ,
@@ -23,25 +22,24 @@ use rustc_span::{sym, Span};
23
22
24
23
declare_clippy_lint ! {
25
24
/// ### What it does
26
- /// Checks for manual [`core::fmt::Debug`] implementations that do not use all fields.
25
+ /// Checks for manual [`core::fmt::Debug`](https://doc.rust-lang.org/core/fmt/trait.Debug.html) implementations that do not use all fields.
27
26
///
28
27
/// ### Why is this bad?
29
28
/// A common mistake is to forget to update manual `Debug` implementations when adding a new field
30
29
/// to a struct or a new variant to an enum.
31
30
///
32
- /// At the same time, it also acts as a style lint to suggest using [`core::fmt::DebugStruct::finish_non_exhaustive`]
31
+ /// At the same time, it also acts as a style lint to suggest using [`core::fmt::DebugStruct::finish_non_exhaustive`](https://doc.rust-lang.org/core/fmt/struct.DebugStruct.html#method.finish_non_exhaustive)
33
32
/// for the times when the user intentionally wants to leave out certain fields (e.g. to hide implementation details).
34
33
///
35
34
/// ### Known problems
36
- /// This lint works based on the [ `DebugStruct`] and [ `DebugTuple`] helper types provided by
37
- /// the [ `Formatter`] , so this won't detect `Debug` impls that use the `write!` macro.
38
- /// Oftentimes there is more logic to a `Debug` impl if it uses `write!` macro, so it tries
35
+ /// This lint works based on the `DebugStruct` and `DebugTuple` helper types provided by
36
+ /// the `Formatter`, so this won't detect `Debug` impls that use the `write!` macro.
37
+ /// Oftentimes there is more logic to a `Debug` impl if it uses `write!` macro, so it tries
39
38
/// to be on the conservative side and not lint in those cases in an attempt to prevent false positives.
40
39
///
41
40
/// This lint also does not look through function calls, so calling `.field(self.as_slice())` for example
42
41
/// does not consider fields used inside of `as_slice()` as used by the `Debug` impl.
43
42
///
44
- ///
45
43
/// ### Example
46
44
/// ```rust
47
45
/// use std::fmt;
@@ -83,21 +81,20 @@ declare_clippy_lint! {
83
81
}
84
82
declare_lint_pass ! ( MissingFieldInDebug => [ MISSING_FIELDS_IN_DEBUG ] ) ;
85
83
86
- const WARN : & str = "manual `Debug` impl does not include all fields" ;
87
- const HELP_WILDCARD : & str = "unused field here due to wildcard `_`" ;
88
- const HELP_UNUSED_REST : & str = "more unused fields here due to rest pattern `..`" ;
89
- const HELP_FIELD_UNUSED : & str = "this field is unused" ;
90
- const HELP_FIELD_REFERENCE : & str = "the field referenced by this binding is unused" ;
91
- const HELP_INCLUDE_ALL_FIELDS : & str = "consider including all fields in this `Debug` impl" ;
92
- const HELP_FINISH_NON_EXHAUSTIVE : & str = "consider calling `.finish_non_exhaustive()` if you intend to ignore fields" ;
93
-
94
84
fn report_lints ( cx : & LateContext < ' _ > , span : Span , span_notes : Vec < ( Span , & ' static str ) > ) {
95
- span_lint_and_then ( cx, MISSING_FIELDS_IN_DEBUG , span, WARN , |diag| {
96
- for ( span, note) in span_notes {
97
- diag. span_note ( span, note) ;
98
- }
99
- diag. help ( HELP_INCLUDE_ALL_FIELDS ) . help ( HELP_FINISH_NON_EXHAUSTIVE ) ;
100
- } ) ;
85
+ span_lint_and_then (
86
+ cx,
87
+ MISSING_FIELDS_IN_DEBUG ,
88
+ span,
89
+ "manual `Debug` impl does not include all fields" ,
90
+ |diag| {
91
+ for ( span, note) in span_notes {
92
+ diag. span_note ( span, note) ;
93
+ }
94
+ diag. help ( "consider including all fields in this `Debug` impl" )
95
+ . help ( "consider calling `.finish_non_exhaustive()` if you intend to ignore fields" ) ;
96
+ } ,
97
+ ) ;
101
98
}
102
99
103
100
/// Checks if we should lint in a block of code
@@ -119,20 +116,14 @@ fn should_lint<'tcx>(
119
116
} ;
120
117
let recv_ty = typeck_results. expr_ty ( recv) . peel_refs ( ) ;
121
118
122
- if_chain ! {
123
- if [ sym:: debug_struct, sym:: debug_tuple] . contains( & path. ident. name) ;
124
- if match_type( cx, recv_ty, & paths:: FORMATTER ) ;
125
- then {
126
- has_debug_struct_tuple = true ;
127
- }
119
+ if [ sym:: debug_struct, sym:: debug_tuple] . contains ( & path. ident . name )
120
+ && match_type ( cx, recv_ty, & paths:: FORMATTER )
121
+ {
122
+ has_debug_struct_tuple = true ;
128
123
}
129
124
130
- if_chain ! {
131
- if path. ident. name. as_str( ) == "finish_non_exhaustive" ;
132
- if match_type( cx, recv_ty, & paths:: DEBUG_STRUCT ) ;
133
- then {
134
- has_finish_non_exhaustive = true ;
135
- }
125
+ if path. ident . name . as_str ( ) == "finish_non_exhaustive" && match_type ( cx, recv_ty, & paths:: DEBUG_STRUCT ) {
126
+ has_finish_non_exhaustive = true ;
136
127
}
137
128
ControlFlow :: Continue ( ( ) )
138
129
} ) ;
@@ -152,13 +143,11 @@ fn check_struct<'tcx>(
152
143
let mut field_accesses = FxHashSet :: default ( ) ;
153
144
154
145
let _: Option < !> = for_each_expr ( block, |expr| {
155
- if_chain ! {
156
- if let ExprKind :: Field ( target, ident) = expr. kind;
157
- let target_ty = typeck_results. expr_ty( target) . peel_refs( ) ;
158
- if target_ty == self_ty;
159
- then {
160
- field_accesses. insert( ident) ;
161
- }
146
+ if let ExprKind :: Field ( target, ident) = expr. kind
147
+ && let target_ty = typeck_results. expr_ty ( target) . peel_refs ( )
148
+ && target_ty == self_ty
149
+ {
150
+ field_accesses. insert ( ident) ;
162
151
}
163
152
ControlFlow :: Continue ( ( ) )
164
153
} ) ;
@@ -170,12 +159,14 @@ fn check_struct<'tcx>(
170
159
if field_accesses. contains ( & field. ident ) {
171
160
None
172
161
} else {
173
- Some ( ( field. span , HELP_FIELD_UNUSED ) )
162
+ Some ( ( field. span , "this field is unused" ) )
174
163
}
175
164
} )
176
165
. collect :: < Vec < _ > > ( ) ;
177
166
178
- if !span_notes. is_empty ( ) {
167
+ // only lint if there's also at least one field access to allow patterns
168
+ // where one might have a newtype struct and uses fields from the wrapped type
169
+ if !span_notes. is_empty ( ) && !field_accesses. is_empty ( ) {
179
170
report_lints ( cx, item. span , span_notes) ;
180
171
}
181
172
}
@@ -194,56 +185,54 @@ fn check_enum<'tcx>(
194
185
item : & ' tcx Item < ' tcx > ,
195
186
) {
196
187
let Some ( arms) = for_each_expr ( block, |expr| {
197
- if_chain ! {
198
- if let ExprKind :: Match ( val, arms, MatchSource :: Normal ) = & expr. kind;
199
- if let match_ty = typeck_results. expr_ty( val) . peel_refs( ) ;
200
- if match_ty == self_ty;
201
- then {
202
- ControlFlow :: Break ( arms)
203
- } else {
204
- ControlFlow :: Continue ( ( ) )
205
- }
188
+ if let ExprKind :: Match ( val, arms, MatchSource :: Normal ) = expr. kind
189
+ && let match_ty = typeck_results. expr_ty ( val) . peel_refs ( )
190
+ && match_ty == self_ty
191
+ {
192
+ ControlFlow :: Break ( arms)
193
+ } else {
194
+ ControlFlow :: Continue ( ( ) )
206
195
}
207
196
} ) else {
208
197
return ;
209
198
} ;
210
199
211
200
let mut span_notes = Vec :: new ( ) ;
212
201
213
- for arm in * arms {
202
+ for arm in arms {
214
203
if !should_lint ( cx, typeck_results, arm. body ) {
215
204
continue ;
216
205
}
217
206
218
- arm. pat . walk_always ( |pat| match & pat. kind {
219
- PatKind :: Wild => span_notes. push ( ( pat. span , HELP_WILDCARD ) ) ,
207
+ arm. pat . walk_always ( |pat| match pat. kind {
208
+ PatKind :: Wild => span_notes. push ( ( pat. span , "unused field here due to wildcard `_`" ) ) ,
220
209
PatKind :: Tuple ( _, rest) | PatKind :: TupleStruct ( .., rest) if rest. as_opt_usize ( ) . is_some ( ) => {
221
- span_notes. push ( ( pat. span , HELP_UNUSED_REST ) ) ;
210
+ span_notes. push ( ( pat. span , "more unused fields here due to rest pattern `..`" ) ) ;
211
+ } ,
212
+ PatKind :: Struct ( .., true ) => {
213
+ span_notes. push ( ( pat. span , "more unused fields here due to rest pattern `..`" ) )
222
214
} ,
223
- PatKind :: Struct ( .., true ) => span_notes. push ( ( pat. span , HELP_UNUSED_REST ) ) ,
224
215
_ => { } ,
225
216
} ) ;
226
217
227
218
let mut field_accesses = FxHashSet :: default ( ) ;
228
219
229
220
let _: Option < !> = for_each_expr ( arm. body , |expr| {
230
- if_chain ! {
231
- if let ExprKind :: Path ( QPath :: Resolved ( _, path) ) = & expr. kind;
232
- if let Some ( segment) = path. segments. first( ) ;
233
- then {
234
- arm. pat. each_binding( |_, _, _, pat_ident| {
235
- if segment. ident == pat_ident {
236
- field_accesses. insert( pat_ident) ;
237
- }
238
- } ) ;
239
- }
221
+ if let ExprKind :: Path ( QPath :: Resolved ( _, path) ) = expr. kind
222
+ && let Some ( segment) = path. segments . first ( )
223
+ {
224
+ arm. pat . each_binding ( |_, _, _, pat_ident| {
225
+ if segment. ident == pat_ident {
226
+ field_accesses. insert ( pat_ident) ;
227
+ }
228
+ } ) ;
240
229
}
241
230
ControlFlow :: Continue ( ( ) )
242
231
} ) ;
243
232
244
233
arm. pat . each_binding ( |_, _, span, pat_ident| {
245
234
if !field_accesses. contains ( & pat_ident) {
246
- span_notes. push ( ( span, HELP_FIELD_REFERENCE ) ) ;
235
+ span_notes. push ( ( span, "the field referenced by this binding is unused" ) ) ;
247
236
}
248
237
} ) ;
249
238
}
@@ -255,38 +244,36 @@ fn check_enum<'tcx>(
255
244
256
245
impl < ' tcx > LateLintPass < ' tcx > for MissingFieldInDebug {
257
246
fn check_item ( & mut self , cx : & LateContext < ' tcx > , item : & ' tcx rustc_hir:: Item < ' tcx > ) {
258
- if_chain ! {
259
- // is this an `impl Debug for X` block?
260
- if let ItemKind :: Impl ( Impl { of_trait: Some ( trait_ref) , self_ty, items, .. } ) = item. kind;
261
- if let Res :: Def ( DefKind :: Trait , trait_def_id) = trait_ref. path. res;
262
- if let TyKind :: Path ( QPath :: Resolved ( _, self_path) ) = & self_ty. kind;
263
- if cx. match_def_path( trait_def_id, & [ sym:: core, sym:: fmt, sym:: Debug ] ) ;
264
-
247
+ // is this an `impl Debug for X` block?
248
+ if let ItemKind :: Impl ( Impl { of_trait : Some ( trait_ref) , self_ty, items, .. } ) = item. kind
249
+ && let Res :: Def ( DefKind :: Trait , trait_def_id) = trait_ref. path . res
250
+ && let TyKind :: Path ( QPath :: Resolved ( _, self_path) ) = & self_ty. kind
251
+ && cx. match_def_path ( trait_def_id, & [ sym:: core, sym:: fmt, sym:: Debug ] )
252
+
265
253
// don't trigger if this impl was derived
266
- if !cx. tcx. has_attr( item. owner_id, sym:: automatically_derived) ;
267
- if !item. span. from_expansion( ) ;
268
-
254
+ && !cx. tcx . has_attr ( item. owner_id , sym:: automatically_derived)
255
+ && !item. span . from_expansion ( )
256
+
269
257
// find `Debug::fmt` function
270
- if let Some ( fmt_item) = items. iter( ) . find( |i| i. ident. name == sym:: fmt) ;
271
- if let ImplItem { kind: ImplItemKind :: Fn ( _, body_id) , .. } = cx. tcx. hir( ) . impl_item( fmt_item. id) ;
272
- let body = cx. tcx. hir( ) . body( * body_id) ;
273
- if let ExprKind :: Block ( block, _) = body. value. kind;
274
-
258
+ && let Some ( fmt_item) = items. iter ( ) . find ( |i| i. ident . name == sym:: fmt)
259
+ && let ImplItem { kind : ImplItemKind :: Fn ( _, body_id) , .. } = cx. tcx . hir ( ) . impl_item ( fmt_item. id )
260
+ && let body = cx. tcx . hir ( ) . body ( * body_id)
261
+ && let ExprKind :: Block ( block, _) = body. value . kind
262
+
275
263
// inspect `self`
276
- let self_ty = cx. tcx. type_of( self_path. res. def_id( ) ) . 0 . peel_refs( ) ;
277
- if let Some ( self_adt) = self_ty. ty_adt_def( ) ;
278
- if let Some ( self_def_id) = self_adt. did( ) . as_local( ) ;
279
- if let Some ( Node :: Item ( self_item) ) = cx. tcx. hir( ) . find_by_def_id( self_def_id) ;
280
-
264
+ && let self_ty = cx. tcx . type_of ( self_path. res . def_id ( ) ) . 0 . peel_refs ( )
265
+ && let Some ( self_adt) = self_ty. ty_adt_def ( )
266
+ && let Some ( self_def_id) = self_adt. did ( ) . as_local ( )
267
+ && let Some ( Node :: Item ( self_item) ) = cx. tcx . hir ( ) . find_by_def_id ( self_def_id)
268
+
281
269
// NB: can't call cx.typeck_results() as we are not in a body
282
- let typeck_results = cx. tcx. typeck_body( * body_id) ;
283
- if should_lint( cx, typeck_results, block) ;
284
- then {
285
- match & self_item. kind {
286
- ItemKind :: Struct ( data, _) => check_struct( cx, typeck_results, block, self_ty, item, data) ,
287
- ItemKind :: Enum ( ..) => check_enum( cx, typeck_results, block, self_ty, item) ,
288
- _ => { }
289
- }
270
+ && let typeck_results = cx. tcx . typeck_body ( * body_id)
271
+ && should_lint ( cx, typeck_results, block)
272
+ {
273
+ match & self_item. kind {
274
+ ItemKind :: Struct ( data, _) => check_struct ( cx, typeck_results, block, self_ty, item, data) ,
275
+ ItemKind :: Enum ( ..) => check_enum ( cx, typeck_results, block, self_ty, item) ,
276
+ _ => { }
290
277
}
291
278
}
292
279
}
0 commit comments