Skip to content

Commit 003ffb3

Browse files
committed
special-case PD, account for field access through Deref
1 parent 8da0770 commit 003ffb3

File tree

3 files changed

+85
-25
lines changed

3 files changed

+85
-25
lines changed

clippy_lints/src/missing_fields_in_debug.rs

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ use rustc_hir::{Block, PatKind};
1616
use rustc_hir::{ExprKind, Impl, ItemKind, QPath, TyKind};
1717
use rustc_hir::{ImplItem, Item, VariantData};
1818
use rustc_lint::{LateContext, LateLintPass};
19-
use rustc_middle::ty::Ty;
2019
use rustc_middle::ty::TypeckResults;
20+
use rustc_middle::ty::{EarlyBinder, Ty};
2121
use rustc_session::{declare_lint_pass, declare_tool_lint};
2222
use rustc_span::{sym, Span, Symbol};
2323

@@ -41,6 +41,9 @@ declare_clippy_lint! {
4141
/// This lint also does not look through function calls, so calling `.field(self.as_slice())` for example
4242
/// does not consider fields used inside of `as_slice()` as used by the `Debug` impl.
4343
///
44+
/// Lastly, it also ignores tuple structs as their `DebugTuple` formatter does not have a `finish_non_exhaustive`
45+
/// method.
46+
///
4447
/// ### Example
4548
/// ```rust
4649
/// use std::fmt;
@@ -107,7 +110,9 @@ fn should_lint<'tcx>(
107110
typeck_results: &TypeckResults<'tcx>,
108111
block: impl Visitable<'tcx>,
109112
) -> bool {
113+
// Is there a call to `DebugStruct::finish_non_exhaustive`? Don't lint if there is.
110114
let mut has_finish_non_exhaustive = false;
115+
// Is there a call to `DebugStruct::debug_struct`? Do lint if there is.
111116
let mut has_debug_struct = false;
112117

113118
for_each_expr(block, |expr| {
@@ -128,6 +133,8 @@ fn should_lint<'tcx>(
128133

129134
/// Checks if the given expression is a call to `DebugStruct::field`
130135
/// and the first argument to it is a string literal and if so, returns it
136+
///
137+
/// Example: `.field("foo", ....)` returns `Some("foo")`
131138
fn as_field_call<'tcx>(
132139
cx: &LateContext<'tcx>,
133140
typeck_results: &TypeckResults<'tcx>,
@@ -155,16 +162,19 @@ fn check_struct<'tcx>(
155162
item: &'tcx Item<'tcx>,
156163
data: &VariantData<'_>,
157164
) {
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.
158168
let mut has_direct_field_access = false;
159169
let mut field_accesses = FxHashSet::default();
160170

161171
for_each_expr(block, |expr| {
162172
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()
164174
&& target_ty == self_ty
165175
{
166-
has_direct_field_access = true;
167176
field_accesses.insert(ident.name);
177+
has_direct_field_access = true;
168178
} else if let Some(sym) = as_field_call(cx, typeck_results, expr) {
169179
field_accesses.insert(sym);
170180
}
@@ -175,7 +185,8 @@ fn check_struct<'tcx>(
175185
.fields()
176186
.iter()
177187
.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() {
179190
None
180191
} else {
181192
Some((field.span, "this field is unused"))
@@ -204,7 +215,7 @@ fn check_enum<'tcx>(
204215
) {
205216
let Some(arms) = for_each_expr(block, |expr| {
206217
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()
208219
&& match_ty == self_ty
209220
{
210221
ControlFlow::Break(arms)
@@ -234,20 +245,22 @@ fn check_enum<'tcx>(
234245
});
235246

236247
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+
}
243256
};
244257

245258
for_each_expr(arm.body, |expr| {
246259
if let ExprKind::Path(QPath::Resolved(_, path)) = expr.kind && let Some(segment) = path.segments.first()
247260
{
248-
check_field_access(segment.ident.name);
261+
check_field_access(segment.ident.name, expr);
249262
} else if let Some(sym) = as_field_call(cx, typeck_results, expr) {
250-
check_field_access(sym);
263+
check_field_access(sym, expr);
251264
}
252265
ControlFlow::<!, _>::Continue(())
253266
});

tests/ui/missing_fields_in_debug.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
#![warn(clippy::missing_fields_in_debug)]
33

44
use std::fmt;
5+
use std::marker::PhantomData;
6+
use std::ops::Deref;
57

68
struct NamedStruct1Ignored {
79
data: u8,
@@ -269,4 +271,49 @@ impl fmt::Debug for Foo {
269271
}
270272
}
271273

274+
// https://github.com/rust-lang/rust-clippy/pull/10616#discussion_r1175473620
275+
mod comment1175473620 {
276+
use super::*;
277+
278+
struct Inner {
279+
a: usize,
280+
b: usize,
281+
}
282+
struct Wrapper(Inner);
283+
284+
impl Deref for Wrapper {
285+
type Target = Inner;
286+
287+
fn deref(&self) -> &Self::Target {
288+
&self.0
289+
}
290+
}
291+
292+
impl fmt::Debug for Wrapper {
293+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
294+
f.debug_struct("Wrapper")
295+
.field("a", &self.a)
296+
.field("b", &self.b)
297+
.finish()
298+
}
299+
}
300+
}
301+
302+
// https://github.com/rust-lang/rust-clippy/pull/10616#discussion_r1175488757
303+
// PhantomData is an exception and does not need to be included
304+
struct WithPD {
305+
a: u8,
306+
b: u8,
307+
c: PhantomData<String>,
308+
}
309+
310+
impl fmt::Debug for WithPD {
311+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
312+
f.debug_struct("WithPD")
313+
.field("a", &self.a)
314+
.field("b", &self.b)
315+
.finish()
316+
}
317+
}
318+
272319
fn main() {}

tests/ui/missing_fields_in_debug.stderr

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: manual `Debug` impl does not include all fields
2-
--> $DIR/missing_fields_in_debug.rs:11:1
2+
--> $DIR/missing_fields_in_debug.rs:13:1
33
|
44
LL | / impl fmt::Debug for NamedStruct1Ignored {
55
LL | | // unused field: hidden
@@ -11,7 +11,7 @@ LL | | }
1111
| |_^
1212
|
1313
note: this field is unused
14-
--> $DIR/missing_fields_in_debug.rs:8:5
14+
--> $DIR/missing_fields_in_debug.rs:10:5
1515
|
1616
LL | hidden: u32,
1717
| ^^^^^^^^^^^
@@ -20,7 +20,7 @@ LL | hidden: u32,
2020
= note: `-D clippy::missing-fields-in-debug` implied by `-D warnings`
2121

2222
error: manual `Debug` impl does not include all fields
23-
--> $DIR/missing_fields_in_debug.rs:29:1
23+
--> $DIR/missing_fields_in_debug.rs:31:1
2424
|
2525
LL | / impl fmt::Debug for NamedStructMultipleIgnored {
2626
LL | | // unused fields: hidden, hidden2, hidden4
@@ -32,25 +32,25 @@ LL | | }
3232
| |_^
3333
|
3434
note: this field is unused
35-
--> $DIR/missing_fields_in_debug.rs:23:5
35+
--> $DIR/missing_fields_in_debug.rs:25:5
3636
|
3737
LL | hidden: u32,
3838
| ^^^^^^^^^^^
3939
note: this field is unused
40-
--> $DIR/missing_fields_in_debug.rs:24:5
40+
--> $DIR/missing_fields_in_debug.rs:26:5
4141
|
4242
LL | hidden2: String,
4343
| ^^^^^^^^^^^^^^^
4444
note: this field is unused
45-
--> $DIR/missing_fields_in_debug.rs:26:5
45+
--> $DIR/missing_fields_in_debug.rs:28:5
4646
|
4747
LL | hidden4: ((((u8), u16), u32), u64),
4848
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4949
= help: consider including all fields in this `Debug` impl
5050
= help: consider calling `.finish_non_exhaustive()` if you intend to ignore fields
5151

5252
error: manual `Debug` impl does not include all fields
53-
--> $DIR/missing_fields_in_debug.rs:90:1
53+
--> $DIR/missing_fields_in_debug.rs:92:1
5454
|
5555
LL | / impl fmt::Debug for MultiExprDebugImpl {
5656
LL | | fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
@@ -62,15 +62,15 @@ LL | | }
6262
| |_^
6363
|
6464
note: this field is unused
65-
--> $DIR/missing_fields_in_debug.rs:86:5
65+
--> $DIR/missing_fields_in_debug.rs:88:5
6666
|
6767
LL | b: String,
6868
| ^^^^^^^^^
6969
= help: consider including all fields in this `Debug` impl
7070
= help: consider calling `.finish_non_exhaustive()` if you intend to ignore fields
7171

7272
error: manual `Debug` impl does not include all fields
73-
--> $DIR/missing_fields_in_debug.rs:117:1
73+
--> $DIR/missing_fields_in_debug.rs:119:1
7474
|
7575
LL | / impl fmt::Debug for MultiVariantEnum {
7676
LL | | // match arm Self::B ignores `b`
@@ -82,15 +82,15 @@ LL | | }
8282
| |_^
8383
|
8484
note: the field referenced by this binding is unused
85-
--> $DIR/missing_fields_in_debug.rs:122:26
85+
--> $DIR/missing_fields_in_debug.rs:124:26
8686
|
8787
LL | Self::B { a, b } => formatter.debug_struct("B").field("a", &a).finish(),
8888
| ^
8989
= help: consider including all fields in this `Debug` impl
9090
= help: consider calling `.finish_non_exhaustive()` if you intend to ignore fields
9191

9292
error: manual `Debug` impl does not include all fields
93-
--> $DIR/missing_fields_in_debug.rs:168:1
93+
--> $DIR/missing_fields_in_debug.rs:170:1
9494
|
9595
LL | / impl fmt::Debug for MultiVariantRest {
9696
LL | | // `a` field ignored due to rest pattern
@@ -102,7 +102,7 @@ LL | | }
102102
| |_^
103103
|
104104
note: more unused fields here due to rest pattern `..`
105-
--> $DIR/missing_fields_in_debug.rs:173:13
105+
--> $DIR/missing_fields_in_debug.rs:175:13
106106
|
107107
LL | Self::B { b, .. } => formatter.debug_struct("B").field("b", &b).finish(),
108108
| ^^^^^^^^^^^^^^^^^

0 commit comments

Comments
 (0)