@@ -6,10 +6,11 @@ use clippy_utils::{
6
6
ty:: match_type,
7
7
visitors:: { for_each_expr, Visitable } ,
8
8
} ;
9
+ use rustc_ast:: LitKind ;
9
10
use rustc_data_structures:: fx:: FxHashSet ;
10
11
use rustc_hir:: {
11
12
def:: { DefKind , Res } ,
12
- ImplItemKind , MatchSource , Node ,
13
+ Expr , ImplItemKind , MatchSource , Node ,
13
14
} ;
14
15
use rustc_hir:: { Block , PatKind } ;
15
16
use rustc_hir:: { ExprKind , Impl , ItemKind , QPath , TyKind } ;
@@ -18,7 +19,7 @@ use rustc_lint::{LateContext, LateLintPass};
18
19
use rustc_middle:: ty:: Ty ;
19
20
use rustc_middle:: ty:: TypeckResults ;
20
21
use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
21
- use rustc_span:: { sym, Span } ;
22
+ use rustc_span:: { sym, Span , Symbol } ;
22
23
23
24
declare_clippy_lint ! {
24
25
/// ### What it does
@@ -32,8 +33,8 @@ declare_clippy_lint! {
32
33
/// for the times when the user intentionally wants to leave out certain fields (e.g. to hide implementation details).
33
34
///
34
35
/// ### 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.
37
38
/// Oftentimes there is more logic to a `Debug` impl if it uses `write!` macro, so it tries
38
39
/// to be on the conservative side and not lint in those cases in an attempt to prevent false positives.
39
40
///
@@ -79,7 +80,7 @@ declare_clippy_lint! {
79
80
pedantic,
80
81
"missing fields in manual `Debug` implementation"
81
82
}
82
- declare_lint_pass ! ( MissingFieldInDebug => [ MISSING_FIELDS_IN_DEBUG ] ) ;
83
+ declare_lint_pass ! ( MissingFieldsInDebug => [ MISSING_FIELDS_IN_DEBUG ] ) ;
83
84
84
85
fn report_lints ( cx : & LateContext < ' _ > , span : Span , span_notes : Vec < ( Span , & ' static str ) > ) {
85
86
span_lint_and_then (
@@ -100,35 +101,49 @@ fn report_lints(cx: &LateContext<'_>, span: Span, span_notes: Vec<(Span, &'stati
100
101
/// Checks if we should lint in a block of code
101
102
///
102
103
/// 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()`.
105
105
fn should_lint < ' tcx > (
106
106
cx : & LateContext < ' tcx > ,
107
107
typeck_results : & TypeckResults < ' tcx > ,
108
108
block : impl Visitable < ' tcx > ,
109
109
) -> bool {
110
110
let mut has_finish_non_exhaustive = false ;
111
- let mut has_debug_struct_tuple = false ;
111
+ let mut has_debug_struct = false ;
112
112
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 ( ) ;
124
116
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
+ }
127
122
}
128
- ControlFlow :: Continue ( ( ) )
123
+ ControlFlow :: < ! , _ > :: Continue ( ( ) )
129
124
} ) ;
130
125
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
+ }
132
147
}
133
148
134
149
/// Attempts to find unused fields assuming that the item is a struct
@@ -140,33 +155,37 @@ fn check_struct<'tcx>(
140
155
item : & ' tcx Item < ' tcx > ,
141
156
data : & VariantData < ' _ > ,
142
157
) {
158
+ let mut has_direct_field_access = false ;
143
159
let mut field_accesses = FxHashSet :: default ( ) ;
144
160
145
- let _ : Option < ! > = for_each_expr ( block, |expr| {
161
+ for_each_expr ( block, |expr| {
146
162
if let ExprKind :: Field ( target, ident) = expr. kind
147
163
&& let target_ty = typeck_results. expr_ty ( target) . peel_refs ( )
148
164
&& target_ty == self_ty
149
165
{
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) ;
151
170
}
152
- ControlFlow :: Continue ( ( ) )
171
+ ControlFlow :: < ! , _ > :: Continue ( ( ) )
153
172
} ) ;
154
173
155
174
let span_notes = data
156
175
. fields ( )
157
176
. iter ( )
158
177
. filter_map ( |field| {
159
- if field_accesses. contains ( & field. ident ) {
178
+ if field_accesses. contains ( & field. ident . name ) {
160
179
None
161
180
} else {
162
181
Some ( ( field. span , "this field is unused" ) )
163
182
}
164
183
} )
165
184
. collect :: < Vec < _ > > ( ) ;
166
185
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
168
187
// 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 {
170
189
report_lints ( cx, item. span , span_notes) ;
171
190
}
172
191
}
@@ -216,18 +235,22 @@ fn check_enum<'tcx>(
216
235
} ) ;
217
236
218
237
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
+ } ;
219
245
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 ( )
223
248
{
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) ;
229
252
}
230
- ControlFlow :: Continue ( ( ) )
253
+ ControlFlow :: < ! , _ > :: Continue ( ( ) )
231
254
} ) ;
232
255
233
256
arm. pat . each_binding ( |_, _, span, pat_ident| {
@@ -242,7 +265,7 @@ fn check_enum<'tcx>(
242
265
}
243
266
}
244
267
245
- impl < ' tcx > LateLintPass < ' tcx > for MissingFieldInDebug {
268
+ impl < ' tcx > LateLintPass < ' tcx > for MissingFieldsInDebug {
246
269
fn check_item ( & mut self , cx : & LateContext < ' tcx > , item : & ' tcx rustc_hir:: Item < ' tcx > ) {
247
270
// is this an `impl Debug for X` block?
248
271
if let ItemKind :: Impl ( Impl { of_trait : Some ( trait_ref) , self_ty, items, .. } ) = item. kind
0 commit comments