@@ -16,8 +16,8 @@ use rustc_hir::{Block, PatKind};
16
16
use rustc_hir:: { ExprKind , Impl , ItemKind , QPath , TyKind } ;
17
17
use rustc_hir:: { ImplItem , Item , VariantData } ;
18
18
use rustc_lint:: { LateContext , LateLintPass } ;
19
- use rustc_middle:: ty:: Ty ;
20
19
use rustc_middle:: ty:: TypeckResults ;
20
+ use rustc_middle:: ty:: { EarlyBinder , Ty } ;
21
21
use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
22
22
use rustc_span:: { sym, Span , Symbol } ;
23
23
@@ -41,6 +41,9 @@ declare_clippy_lint! {
41
41
/// This lint also does not look through function calls, so calling `.field(self.as_slice())` for example
42
42
/// does not consider fields used inside of `as_slice()` as used by the `Debug` impl.
43
43
///
44
+ /// Lastly, it also ignores tuple structs as their `DebugTuple` formatter does not have a `finish_non_exhaustive`
45
+ /// method.
46
+ ///
44
47
/// ### Example
45
48
/// ```rust
46
49
/// use std::fmt;
@@ -107,7 +110,9 @@ fn should_lint<'tcx>(
107
110
typeck_results : & TypeckResults < ' tcx > ,
108
111
block : impl Visitable < ' tcx > ,
109
112
) -> bool {
113
+ // Is there a call to `DebugStruct::finish_non_exhaustive`? Don't lint if there is.
110
114
let mut has_finish_non_exhaustive = false ;
115
+ // Is there a call to `DebugStruct::debug_struct`? Do lint if there is.
111
116
let mut has_debug_struct = false ;
112
117
113
118
for_each_expr ( block, |expr| {
@@ -128,6 +133,8 @@ fn should_lint<'tcx>(
128
133
129
134
/// Checks if the given expression is a call to `DebugStruct::field`
130
135
/// and the first argument to it is a string literal and if so, returns it
136
+ ///
137
+ /// Example: `.field("foo", ....)` returns `Some("foo")`
131
138
fn as_field_call < ' tcx > (
132
139
cx : & LateContext < ' tcx > ,
133
140
typeck_results : & TypeckResults < ' tcx > ,
@@ -155,16 +162,19 @@ fn check_struct<'tcx>(
155
162
item : & ' tcx Item < ' tcx > ,
156
163
data : & VariantData < ' _ > ,
157
164
) {
165
+ // Is there a "direct" field access anywhere (i.e. self.foo)?
166
+ // We don't want to lint if there is not, because the user might have
167
+ // a newtype struct and use fields from the wrapped type only.
158
168
let mut has_direct_field_access = false ;
159
169
let mut field_accesses = FxHashSet :: default ( ) ;
160
170
161
171
for_each_expr ( block, |expr| {
162
172
if let ExprKind :: Field ( target, ident) = expr. kind
163
- && let target_ty = typeck_results. expr_ty ( target) . peel_refs ( )
173
+ && let target_ty = typeck_results. expr_ty_adjusted ( target) . peel_refs ( )
164
174
&& target_ty == self_ty
165
175
{
166
- has_direct_field_access = true ;
167
176
field_accesses. insert ( ident. name ) ;
177
+ has_direct_field_access = true ;
168
178
} else if let Some ( sym) = as_field_call ( cx, typeck_results, expr) {
169
179
field_accesses. insert ( sym) ;
170
180
}
@@ -175,7 +185,8 @@ fn check_struct<'tcx>(
175
185
. fields ( )
176
186
. iter ( )
177
187
. filter_map ( |field| {
178
- if field_accesses. contains ( & field. ident . name ) {
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 ( ) {
179
190
None
180
191
} else {
181
192
Some ( ( field. span , "this field is unused" ) )
@@ -204,7 +215,7 @@ fn check_enum<'tcx>(
204
215
) {
205
216
let Some ( arms) = for_each_expr ( block, |expr| {
206
217
if let ExprKind :: Match ( val, arms, MatchSource :: Normal ) = expr. kind
207
- && let match_ty = typeck_results. expr_ty ( val) . peel_refs ( )
218
+ && let match_ty = typeck_results. expr_ty_adjusted ( val) . peel_refs ( )
208
219
&& match_ty == self_ty
209
220
{
210
221
ControlFlow :: Break ( arms)
@@ -234,20 +245,22 @@ fn check_enum<'tcx>(
234
245
} ) ;
235
246
236
247
let mut field_accesses = FxHashSet :: default ( ) ;
237
- let mut check_field_access = |sym| {
238
- arm. pat . each_binding ( |_, _, _, pat_ident| {
239
- if sym == pat_ident. name {
240
- field_accesses. insert ( pat_ident) ;
241
- }
242
- } ) ;
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
+ }
243
256
} ;
244
257
245
258
for_each_expr ( arm. body , |expr| {
246
259
if let ExprKind :: Path ( QPath :: Resolved ( _, path) ) = expr. kind && let Some ( segment) = path. segments . first ( )
247
260
{
248
- check_field_access ( segment. ident . name ) ;
261
+ check_field_access ( segment. ident . name , expr ) ;
249
262
} else if let Some ( sym) = as_field_call ( cx, typeck_results, expr) {
250
- check_field_access ( sym) ;
263
+ check_field_access ( sym, expr ) ;
251
264
}
252
265
ControlFlow :: < !, _ > :: Continue ( ( ) )
253
266
} ) ;
0 commit comments