@@ -2,22 +2,22 @@ use std::ops::ControlFlow;
22
33use clippy_utils:: {
44 diagnostics:: span_lint_and_then,
5- paths,
5+ is_path_lang_item , paths,
66 ty:: match_type,
77 visitors:: { for_each_expr, Visitable } ,
88} ;
99use rustc_ast:: LitKind ;
1010use rustc_data_structures:: fx:: FxHashSet ;
11+ use rustc_hir:: Block ;
1112use rustc_hir:: {
1213 def:: { DefKind , Res } ,
13- Expr , ImplItemKind , MatchSource , Node ,
14+ Expr , ImplItemKind , LangItem , Node ,
1415} ;
15- use rustc_hir:: { Block , PatKind } ;
1616use rustc_hir:: { ExprKind , Impl , ItemKind , QPath , TyKind } ;
1717use rustc_hir:: { ImplItem , Item , VariantData } ;
1818use rustc_lint:: { LateContext , LateLintPass } ;
19+ use rustc_middle:: ty:: Ty ;
1920use rustc_middle:: ty:: TypeckResults ;
20- use rustc_middle:: ty:: { EarlyBinder , Ty } ;
2121use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
2222use rustc_span:: { sym, Span , Symbol } ;
2323
@@ -38,11 +38,12 @@ declare_clippy_lint! {
3838 /// Oftentimes there is more logic to a `Debug` impl if it uses `write!` macro, so it tries
3939 /// to be on the conservative side and not lint in those cases in an attempt to prevent false positives.
4040 ///
41- /// This lint also does not look through function calls, so calling `.field(self.as_slice())` for example
42- /// does not consider fields used inside of `as_slice()` as used by the `Debug` impl.
41+ /// This lint also does not look through function calls, so calling a function does not consider fields
42+ /// used inside of that function as used by the `Debug` impl.
4343 ///
4444 /// Lastly, it also ignores tuple structs as their `DebugTuple` formatter does not have a `finish_non_exhaustive`
45- /// method.
45+ /// method, as well as enums because their exhaustiveness is already checked by the compiler when matching on the enum,
46+ /// making it much less likely to accidentally forget to update the `Debug` impl when adding a new variant.
4647 ///
4748 /// ### Example
4849 /// ```rust
@@ -185,8 +186,7 @@ fn check_struct<'tcx>(
185186 . fields ( )
186187 . iter ( )
187188 . filter_map ( |field| {
188- let EarlyBinder ( field_ty) = cx. tcx . type_of ( field. def_id ) ;
189- if field_accesses. contains ( & field. ident . name ) || field_ty. is_phantom_data ( ) {
189+ if field_accesses. contains ( & field. ident . name ) || is_path_lang_item ( cx, field. ty , LangItem :: PhantomData ) {
190190 None
191191 } else {
192192 Some ( ( field. span , "this field is unused" ) )
@@ -201,82 +201,6 @@ fn check_struct<'tcx>(
201201 }
202202}
203203
204- /// Attempts to find unused fields in variants assuming that
205- /// the item is an enum.
206- ///
207- /// Currently, only simple cases are detected where the user
208- /// matches on `self` and calls `debug_struct` inside of the arms
209- fn check_enum < ' tcx > (
210- cx : & LateContext < ' tcx > ,
211- typeck_results : & TypeckResults < ' tcx > ,
212- block : & ' tcx Block < ' tcx > ,
213- self_ty : Ty < ' tcx > ,
214- item : & ' tcx Item < ' tcx > ,
215- ) {
216- let Some ( arms) = for_each_expr ( block, |expr| {
217- if let ExprKind :: Match ( val, arms, MatchSource :: Normal ) = expr. kind
218- && let match_ty = typeck_results. expr_ty_adjusted ( val) . peel_refs ( )
219- && match_ty == self_ty
220- {
221- ControlFlow :: Break ( arms)
222- } else {
223- ControlFlow :: Continue ( ( ) )
224- }
225- } ) else {
226- return ;
227- } ;
228-
229- let mut span_notes = Vec :: new ( ) ;
230-
231- for arm in arms {
232- if !should_lint ( cx, typeck_results, arm. body ) {
233- continue ;
234- }
235-
236- arm. pat . walk_always ( |pat| match pat. kind {
237- PatKind :: Wild => span_notes. push ( ( pat. span , "unused field here due to wildcard `_`" ) ) ,
238- PatKind :: Tuple ( _, rest) | PatKind :: TupleStruct ( .., rest) if rest. as_opt_usize ( ) . is_some ( ) => {
239- span_notes. push ( ( pat. span , "more unused fields here due to rest pattern `..`" ) ) ;
240- } ,
241- PatKind :: Struct ( .., true ) => {
242- span_notes. push ( ( pat. span , "more unused fields here due to rest pattern `..`" ) ) ;
243- } ,
244- _ => { } ,
245- } ) ;
246-
247- let mut field_accesses = FxHashSet :: default ( ) ;
248- let mut check_field_access = |sym, expr| {
249- if !typeck_results. expr_ty ( expr) . is_phantom_data ( ) {
250- arm. pat . each_binding ( |_, _, _, pat_ident| {
251- if sym == pat_ident. name {
252- field_accesses. insert ( pat_ident) ;
253- }
254- } ) ;
255- }
256- } ;
257-
258- for_each_expr ( arm. body , |expr| {
259- if let ExprKind :: Path ( QPath :: Resolved ( _, path) ) = expr. kind && let Some ( segment) = path. segments . first ( )
260- {
261- check_field_access ( segment. ident . name , expr) ;
262- } else if let Some ( sym) = as_field_call ( cx, typeck_results, expr) {
263- check_field_access ( sym, expr) ;
264- }
265- ControlFlow :: < !, _ > :: Continue ( ( ) )
266- } ) ;
267-
268- arm. pat . each_binding ( |_, _, span, pat_ident| {
269- if !field_accesses. contains ( & pat_ident) {
270- span_notes. push ( ( span, "the field referenced by this binding is unused" ) ) ;
271- }
272- } ) ;
273- }
274-
275- if !span_notes. is_empty ( ) {
276- report_lints ( cx, item. span , span_notes) ;
277- }
278- }
279-
280204impl < ' tcx > LateLintPass < ' tcx > for MissingFieldsInDebug {
281205 fn check_item ( & mut self , cx : & LateContext < ' tcx > , item : & ' tcx rustc_hir:: Item < ' tcx > ) {
282206 // is this an `impl Debug for X` block?
@@ -301,10 +225,9 @@ impl<'tcx> LateLintPass<'tcx> for MissingFieldsInDebug {
301225 && let typeck_results = cx. tcx . typeck_body ( * body_id)
302226 && should_lint ( cx, typeck_results, block)
303227 {
304- match & self_item. kind {
305- ItemKind :: Struct ( data, _) => check_struct ( cx, typeck_results, block, self_ty, item, data) ,
306- ItemKind :: Enum ( ..) => check_enum ( cx, typeck_results, block, self_ty, item) ,
307- _ => { }
228+ // we intentionally only lint structs, see lint description
229+ if let ItemKind :: Struct ( data, _) = & self_item. kind {
230+ check_struct ( cx, typeck_results, block, self_ty, item, data) ;
308231 }
309232 }
310233 }
0 commit comments