Skip to content

Commit a859b0e

Browse files
committed
don't lint enums, update note in lint description
1 parent f74ec6b commit a859b0e

File tree

3 files changed

+13
-258
lines changed

3 files changed

+13
-258
lines changed

clippy_lints/src/missing_fields_in_debug.rs

+12-89
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,22 @@ use std::ops::ControlFlow;
22

33
use 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
};
99
use rustc_ast::LitKind;
1010
use rustc_data_structures::fx::FxHashSet;
11+
use rustc_hir::Block;
1112
use rustc_hir::{
1213
def::{DefKind, Res},
13-
Expr, ImplItemKind, MatchSource, Node,
14+
Expr, ImplItemKind, LangItem, Node,
1415
};
15-
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;
1920
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

@@ -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-
280204
impl<'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
}

tests/ui/missing_fields_in_debug.rs

-128
Original file line numberDiff line numberDiff line change
@@ -97,140 +97,12 @@ impl fmt::Debug for MultiExprDebugImpl {
9797
}
9898
}
9999

100-
enum SingleVariantEnumUnnamed {
101-
A(u8),
102-
}
103-
104-
// ok
105-
impl fmt::Debug for SingleVariantEnumUnnamed {
106-
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
107-
match self {
108-
Self::A(n) => formatter.debug_tuple("A").field(&n).finish(),
109-
}
110-
}
111-
}
112-
113-
enum MultiVariantEnum {
114-
A(u8),
115-
B { a: u8, b: String },
116-
C,
117-
}
118-
119-
impl fmt::Debug for MultiVariantEnum {
120-
// match arm Self::B ignores `b`
121-
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
122-
match self {
123-
Self::A(n) => formatter.debug_tuple("A").field(&n).finish(),
124-
Self::B { a, b } => formatter.debug_struct("B").field("a", &a).finish(),
125-
Self::C => formatter.debug_struct("C").finish(),
126-
}
127-
}
128-
}
129-
130-
enum MultiVariantEnumOk {
131-
A(u8),
132-
B { a: u8, b: String },
133-
C,
134-
}
135-
136-
// ok
137-
impl fmt::Debug for MultiVariantEnumOk {
138-
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
139-
match self {
140-
Self::A(n) => formatter.debug_tuple("A").field(&n).finish(),
141-
Self::B { a, b } => formatter.debug_struct("B").field("a", &a).field("b", &b).finish(),
142-
Self::C => formatter.debug_struct("C").finish(),
143-
}
144-
}
145-
}
146-
147-
enum MultiVariantEnumNonExhaustive {
148-
A(u8),
149-
B { a: u8, b: String },
150-
C,
151-
}
152-
153-
// ok
154-
impl fmt::Debug for MultiVariantEnumNonExhaustive {
155-
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
156-
match self {
157-
Self::A(n) => formatter.debug_tuple("A").field(&n).finish(),
158-
Self::B { a, b } => formatter.debug_struct("B").field("b", &b).finish_non_exhaustive(),
159-
Self::C => formatter.debug_struct("C").finish(),
160-
}
161-
}
162-
}
163-
164-
enum MultiVariantRest {
165-
A(u8),
166-
B { a: u8, b: String },
167-
C,
168-
}
169-
170-
impl fmt::Debug for MultiVariantRest {
171-
// `a` field ignored due to rest pattern
172-
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
173-
match self {
174-
Self::A(n) => formatter.debug_tuple("A").field(&n).finish(),
175-
Self::B { b, .. } => formatter.debug_struct("B").field("b", &b).finish(),
176-
Self::C => formatter.debug_struct("C").finish(),
177-
}
178-
}
179-
}
180-
181-
enum MultiVariantRestNonExhaustive {
182-
A(u8),
183-
B { a: u8, b: String },
184-
C,
185-
}
186-
187-
// ok
188-
impl fmt::Debug for MultiVariantRestNonExhaustive {
189-
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
190-
match self {
191-
Self::A(n) => formatter.debug_tuple("A").field(&n).finish(),
192-
Self::B { b, .. } => formatter.debug_struct("B").field("b", &b).finish_non_exhaustive(),
193-
Self::C => formatter.debug_struct("C").finish(),
194-
}
195-
}
196-
}
197-
198-
enum Wildcard {
199-
A(u8),
200-
B(String),
201-
}
202-
203-
// ok
204-
impl fmt::Debug for Wildcard {
205-
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
206-
match self {
207-
Self::A(n) => formatter.debug_tuple("A").field(&n).finish(),
208-
_ => todo!(),
209-
}
210-
}
211-
}
212-
213-
enum Empty {}
214-
215-
// ok
216-
impl fmt::Debug for Empty {
217-
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
218-
match *self {}
219-
}
220-
}
221-
222100
#[derive(Debug)]
223101
struct DerivedStruct {
224102
a: u8,
225103
b: i32,
226104
}
227105

228-
#[derive(Debug)]
229-
enum DerivedEnum {
230-
A(i32),
231-
B { a: String },
232-
}
233-
234106
// https://github.com/rust-lang/rust-clippy/pull/10616#discussion_r1166846953
235107

236108
struct Inner {

tests/ui/missing_fields_in_debug.stderr

+1-41
Original file line numberDiff line numberDiff line change
@@ -69,45 +69,5 @@ LL | b: String,
6969
= help: consider including all fields in this `Debug` impl
7070
= help: consider calling `.finish_non_exhaustive()` if you intend to ignore fields
7171

72-
error: manual `Debug` impl does not include all fields
73-
--> $DIR/missing_fields_in_debug.rs:119:1
74-
|
75-
LL | / impl fmt::Debug for MultiVariantEnum {
76-
LL | | // match arm Self::B ignores `b`
77-
LL | | fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
78-
LL | | match self {
79-
... |
80-
LL | | }
81-
LL | | }
82-
| |_^
83-
|
84-
note: the field referenced by this binding is unused
85-
--> $DIR/missing_fields_in_debug.rs:124:26
86-
|
87-
LL | Self::B { a, b } => formatter.debug_struct("B").field("a", &a).finish(),
88-
| ^
89-
= help: consider including all fields in this `Debug` impl
90-
= help: consider calling `.finish_non_exhaustive()` if you intend to ignore fields
91-
92-
error: manual `Debug` impl does not include all fields
93-
--> $DIR/missing_fields_in_debug.rs:170:1
94-
|
95-
LL | / impl fmt::Debug for MultiVariantRest {
96-
LL | | // `a` field ignored due to rest pattern
97-
LL | | fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
98-
LL | | match self {
99-
... |
100-
LL | | }
101-
LL | | }
102-
| |_^
103-
|
104-
note: more unused fields here due to rest pattern `..`
105-
--> $DIR/missing_fields_in_debug.rs:175:13
106-
|
107-
LL | Self::B { b, .. } => formatter.debug_struct("B").field("b", &b).finish(),
108-
| ^^^^^^^^^^^^^^^^^
109-
= help: consider including all fields in this `Debug` impl
110-
= help: consider calling `.finish_non_exhaustive()` if you intend to ignore fields
111-
112-
error: aborting due to 5 previous errors
72+
error: aborting due to 3 previous errors
11373

0 commit comments

Comments
 (0)