@@ -6,7 +6,6 @@ use clippy_utils::{
66 ty:: match_type,
77 visitors:: { for_each_expr, Visitable } ,
88} ;
9- use if_chain:: if_chain;
109use rustc_data_structures:: fx:: FxHashSet ;
1110use rustc_hir:: {
1211 def:: { DefKind , Res } ,
@@ -23,25 +22,24 @@ use rustc_span::{sym, Span};
2322
2423declare_clippy_lint ! {
2524 /// ### 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.
2726 ///
2827 /// ### Why is this bad?
2928 /// A common mistake is to forget to update manual `Debug` implementations when adding a new field
3029 /// to a struct or a new variant to an enum.
3130 ///
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)
3332 /// for the times when the user intentionally wants to leave out certain fields (e.g. to hide implementation details).
3433 ///
3534 /// ### 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
3938 /// to be on the conservative side and not lint in those cases in an attempt to prevent false positives.
4039 ///
4140 /// This lint also does not look through function calls, so calling `.field(self.as_slice())` for example
4241 /// does not consider fields used inside of `as_slice()` as used by the `Debug` impl.
4342 ///
44- ///
4543 /// ### Example
4644 /// ```rust
4745 /// use std::fmt;
@@ -83,21 +81,20 @@ declare_clippy_lint! {
8381}
8482declare_lint_pass ! ( MissingFieldInDebug => [ MISSING_FIELDS_IN_DEBUG ] ) ;
8583
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-
9484fn 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+ ) ;
10198}
10299
103100/// Checks if we should lint in a block of code
@@ -119,20 +116,14 @@ fn should_lint<'tcx>(
119116 } ;
120117 let recv_ty = typeck_results. expr_ty ( recv) . peel_refs ( ) ;
121118
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 ;
128123 }
129124
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 ;
136127 }
137128 ControlFlow :: Continue ( ( ) )
138129 } ) ;
@@ -152,13 +143,11 @@ fn check_struct<'tcx>(
152143 let mut field_accesses = FxHashSet :: default ( ) ;
153144
154145 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) ;
162151 }
163152 ControlFlow :: Continue ( ( ) )
164153 } ) ;
@@ -170,12 +159,14 @@ fn check_struct<'tcx>(
170159 if field_accesses. contains ( & field. ident ) {
171160 None
172161 } else {
173- Some ( ( field. span , HELP_FIELD_UNUSED ) )
162+ Some ( ( field. span , "this field is unused" ) )
174163 }
175164 } )
176165 . collect :: < Vec < _ > > ( ) ;
177166
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 ( ) {
179170 report_lints ( cx, item. span , span_notes) ;
180171 }
181172}
@@ -194,56 +185,54 @@ fn check_enum<'tcx>(
194185 item : & ' tcx Item < ' tcx > ,
195186) {
196187 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 ( ( ) )
206195 }
207196 } ) else {
208197 return ;
209198 } ;
210199
211200 let mut span_notes = Vec :: new ( ) ;
212201
213- for arm in * arms {
202+ for arm in arms {
214203 if !should_lint ( cx, typeck_results, arm. body ) {
215204 continue ;
216205 }
217206
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 `_`" ) ) ,
220209 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 `..`" ) )
222214 } ,
223- PatKind :: Struct ( .., true ) => span_notes. push ( ( pat. span , HELP_UNUSED_REST ) ) ,
224215 _ => { } ,
225216 } ) ;
226217
227218 let mut field_accesses = FxHashSet :: default ( ) ;
228219
229220 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+ } ) ;
240229 }
241230 ControlFlow :: Continue ( ( ) )
242231 } ) ;
243232
244233 arm. pat . each_binding ( |_, _, span, pat_ident| {
245234 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" ) ) ;
247236 }
248237 } ) ;
249238 }
@@ -255,38 +244,36 @@ fn check_enum<'tcx>(
255244
256245impl < ' tcx > LateLintPass < ' tcx > for MissingFieldInDebug {
257246 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+
265253 // 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+
269257 // 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+
275263 // 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+
281269 // 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+ _ => { }
290277 }
291278 }
292279 }
0 commit comments