@@ -6,10 +6,11 @@ use clippy_utils::{
66 ty:: match_type,
77 visitors:: { for_each_expr, Visitable } ,
88} ;
9+ use rustc_ast:: LitKind ;
910use rustc_data_structures:: fx:: FxHashSet ;
1011use rustc_hir:: {
1112 def:: { DefKind , Res } ,
12- ImplItemKind , MatchSource , Node ,
13+ Expr , ImplItemKind , MatchSource , Node ,
1314} ;
1415use rustc_hir:: { Block , PatKind } ;
1516use rustc_hir:: { ExprKind , Impl , ItemKind , QPath , TyKind } ;
@@ -18,7 +19,7 @@ use rustc_lint::{LateContext, LateLintPass};
1819use rustc_middle:: ty:: Ty ;
1920use rustc_middle:: ty:: TypeckResults ;
2021use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
21- use rustc_span:: { sym, Span } ;
22+ use rustc_span:: { sym, Span , Symbol } ;
2223
2324declare_clippy_lint ! {
2425 /// ### What it does
@@ -32,8 +33,8 @@ declare_clippy_lint! {
3233 /// for the times when the user intentionally wants to leave out certain fields (e.g. to hide implementation details).
3334 ///
3435 /// ### Known problems
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.
36+ /// This lint works based on the `DebugStruct` helper types provided by the `Formatter`,
37+ /// so this won't detect `Debug` impls that use the `write!` macro.
3738 /// Oftentimes there is more logic to a `Debug` impl if it uses `write!` macro, so it tries
3839 /// to be on the conservative side and not lint in those cases in an attempt to prevent false positives.
3940 ///
@@ -79,7 +80,7 @@ declare_clippy_lint! {
7980 pedantic,
8081 "missing fields in manual `Debug` implementation"
8182}
82- declare_lint_pass ! ( MissingFieldInDebug => [ MISSING_FIELDS_IN_DEBUG ] ) ;
83+ declare_lint_pass ! ( MissingFieldsInDebug => [ MISSING_FIELDS_IN_DEBUG ] ) ;
8384
8485fn report_lints ( cx : & LateContext < ' _ > , span : Span , span_notes : Vec < ( Span , & ' static str ) > ) {
8586 span_lint_and_then (
@@ -100,35 +101,49 @@ fn report_lints(cx: &LateContext<'_>, span: Span, span_notes: Vec<(Span, &'stati
100101/// Checks if we should lint in a block of code
101102///
102103/// The way we check for this condition is by checking if there is
103- /// a call to `Formatter::debug_struct` or `Formatter::debug_tuple`
104- /// and no call to `.finish_non_exhaustive()`.
104+ /// a call to `Formatter::debug_struct` but no call to `.finish_non_exhaustive()`.
105105fn should_lint < ' tcx > (
106106 cx : & LateContext < ' tcx > ,
107107 typeck_results : & TypeckResults < ' tcx > ,
108108 block : impl Visitable < ' tcx > ,
109109) -> bool {
110110 let mut has_finish_non_exhaustive = false ;
111- let mut has_debug_struct_tuple = false ;
111+ let mut has_debug_struct = false ;
112112
113- let _: Option < !> = for_each_expr ( block, |expr| {
114- let ExprKind :: MethodCall ( path, recv, ..) = & expr. kind else {
115- return ControlFlow :: Continue ( ( ) ) ;
116- } ;
117- let recv_ty = typeck_results. expr_ty ( recv) . peel_refs ( ) ;
118-
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 ;
123- }
113+ for_each_expr ( block, |expr| {
114+ if let ExprKind :: MethodCall ( path, recv, ..) = & expr. kind {
115+ let recv_ty = typeck_results. expr_ty ( recv) . peel_refs ( ) ;
124116
125- if path. ident . name . as_str ( ) == "finish_non_exhaustive" && match_type ( cx, recv_ty, & paths:: DEBUG_STRUCT ) {
126- has_finish_non_exhaustive = true ;
117+ if path. ident . name == sym:: debug_struct && match_type ( cx, recv_ty, & paths:: FORMATTER ) {
118+ has_debug_struct = true ;
119+ } else if path. ident . name == sym ! ( finish_non_exhaustive) && match_type ( cx, recv_ty, & paths:: DEBUG_STRUCT ) {
120+ has_finish_non_exhaustive = true ;
121+ }
127122 }
128- ControlFlow :: Continue ( ( ) )
123+ ControlFlow :: < ! , _ > :: Continue ( ( ) )
129124 } ) ;
130125
131- !has_finish_non_exhaustive && has_debug_struct_tuple
126+ !has_finish_non_exhaustive && has_debug_struct
127+ }
128+
129+ /// Checks if the given expression is a call to `DebugStruct::field`
130+ /// and the first argument to it is a string literal and if so, returns it
131+ fn as_field_call < ' tcx > (
132+ cx : & LateContext < ' tcx > ,
133+ typeck_results : & TypeckResults < ' tcx > ,
134+ expr : & Expr < ' _ > ,
135+ ) -> Option < Symbol > {
136+ if let ExprKind :: MethodCall ( path, recv, [ debug_field, _] , _) = & expr. kind
137+ && let recv_ty = typeck_results. expr_ty ( recv) . peel_refs ( )
138+ && match_type ( cx, recv_ty, & paths:: DEBUG_STRUCT )
139+ && path. ident . name == sym ! ( field)
140+ && let ExprKind :: Lit ( lit) = & debug_field. kind
141+ && let LitKind :: Str ( sym, ..) = lit. node
142+ {
143+ Some ( sym)
144+ } else {
145+ None
146+ }
132147}
133148
134149/// Attempts to find unused fields assuming that the item is a struct
@@ -140,33 +155,37 @@ fn check_struct<'tcx>(
140155 item : & ' tcx Item < ' tcx > ,
141156 data : & VariantData < ' _ > ,
142157) {
158+ let mut has_direct_field_access = false ;
143159 let mut field_accesses = FxHashSet :: default ( ) ;
144160
145- let _ : Option < ! > = for_each_expr ( block, |expr| {
161+ for_each_expr ( block, |expr| {
146162 if let ExprKind :: Field ( target, ident) = expr. kind
147163 && let target_ty = typeck_results. expr_ty ( target) . peel_refs ( )
148164 && target_ty == self_ty
149165 {
150- field_accesses. insert ( ident) ;
166+ has_direct_field_access = true ;
167+ field_accesses. insert ( ident. name ) ;
168+ } else if let Some ( sym) = as_field_call ( cx, typeck_results, expr) {
169+ field_accesses. insert ( sym) ;
151170 }
152- ControlFlow :: Continue ( ( ) )
171+ ControlFlow :: < ! , _ > :: Continue ( ( ) )
153172 } ) ;
154173
155174 let span_notes = data
156175 . fields ( )
157176 . iter ( )
158177 . filter_map ( |field| {
159- if field_accesses. contains ( & field. ident ) {
178+ if field_accesses. contains ( & field. ident . name ) {
160179 None
161180 } else {
162181 Some ( ( field. span , "this field is unused" ) )
163182 }
164183 } )
165184 . collect :: < Vec < _ > > ( ) ;
166185
167- // only lint if there's also at least one field access to allow patterns
186+ // only lint if there's also at least one direct field access to allow patterns
168187 // where one might have a newtype struct and uses fields from the wrapped type
169- if !span_notes. is_empty ( ) && !field_accesses . is_empty ( ) {
188+ if !span_notes. is_empty ( ) && has_direct_field_access {
170189 report_lints ( cx, item. span , span_notes) ;
171190 }
172191}
@@ -216,18 +235,22 @@ fn check_enum<'tcx>(
216235 } ) ;
217236
218237 let mut field_accesses = FxHashSet :: default ( ) ;
238+ let mut check_field_access = |sym| {
239+ arm. pat . each_binding ( |_, _, _, pat_ident| {
240+ if sym == pat_ident. name {
241+ field_accesses. insert ( pat_ident) ;
242+ }
243+ } ) ;
244+ } ;
219245
220- let _: Option < !> = for_each_expr ( arm. body , |expr| {
221- if let ExprKind :: Path ( QPath :: Resolved ( _, path) ) = expr. kind
222- && let Some ( segment) = path. segments . first ( )
246+ for_each_expr ( arm. body , |expr| {
247+ if let ExprKind :: Path ( QPath :: Resolved ( _, path) ) = expr. kind && let Some ( segment) = path. segments . first ( )
223248 {
224- arm. pat . each_binding ( |_, _, _, pat_ident| {
225- if segment. ident == pat_ident {
226- field_accesses. insert ( pat_ident) ;
227- }
228- } ) ;
249+ check_field_access ( segment. ident . name ) ;
250+ } else if let Some ( sym) = as_field_call ( cx, typeck_results, expr) {
251+ check_field_access ( sym) ;
229252 }
230- ControlFlow :: Continue ( ( ) )
253+ ControlFlow :: < ! , _ > :: Continue ( ( ) )
231254 } ) ;
232255
233256 arm. pat . each_binding ( |_, _, span, pat_ident| {
@@ -242,7 +265,7 @@ fn check_enum<'tcx>(
242265 }
243266}
244267
245- impl < ' tcx > LateLintPass < ' tcx > for MissingFieldInDebug {
268+ impl < ' tcx > LateLintPass < ' tcx > for MissingFieldsInDebug {
246269 fn check_item ( & mut self , cx : & LateContext < ' tcx > , item : & ' tcx rustc_hir:: Item < ' tcx > ) {
247270 // is this an `impl Debug for X` block?
248271 if let ItemKind :: Impl ( Impl { of_trait : Some ( trait_ref) , self_ty, items, .. } ) = item. kind
0 commit comments