Skip to content

Commit 4505b65

Browse files
committed
if_chain -> let chains, fix markdown, allow newtype pattern
1 parent 8e5f70a commit 4505b65

File tree

3 files changed

+126
-129
lines changed

3 files changed

+126
-129
lines changed

clippy_lints/src/missing_fields_in_debug.rs

Lines changed: 82 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use clippy_utils::{
66
ty::match_type,
77
visitors::{for_each_expr, Visitable},
88
};
9-
use if_chain::if_chain;
109
use rustc_data_structures::fx::FxHashSet;
1110
use rustc_hir::{
1211
def::{DefKind, Res},
@@ -23,25 +22,24 @@ use rustc_span::{sym, Span};
2322

2423
declare_clippy_lint! {
2524
/// ### What it does
26-
/// Checks for manual [`core::fmt::Debug`] implementations that do not use all fields.
25+
/// Checks for manual [`core::fmt::Debug`](https://doc.rust-lang.org/core/fmt/trait.Debug.html) implementations that do not use all fields.
2726
///
2827
/// ### Why is this bad?
2928
/// A common mistake is to forget to update manual `Debug` implementations when adding a new field
3029
/// to a struct or a new variant to an enum.
3130
///
32-
/// At the same time, it also acts as a style lint to suggest using [`core::fmt::DebugStruct::finish_non_exhaustive`]
31+
/// At the same time, it also acts as a style lint to suggest using [`core::fmt::DebugStruct::finish_non_exhaustive`](https://doc.rust-lang.org/core/fmt/struct.DebugStruct.html#method.finish_non_exhaustive)
3332
/// for the times when the user intentionally wants to leave out certain fields (e.g. to hide implementation details).
3433
///
3534
/// ### Known problems
36-
/// This lint works based on the [`DebugStruct`] and [`DebugTuple`] helper types provided by
37-
/// the [`Formatter`], so this won't detect `Debug` impls that use the `write!` macro.
38-
/// Oftentimes there is more logic to a `Debug` impl if it uses `write!` macro, so it tries
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.
37+
/// Oftentimes there is more logic to a `Debug` impl if it uses `write!` macro, so it tries
3938
/// to be on the conservative side and not lint in those cases in an attempt to prevent false positives.
4039
///
4140
/// This lint also does not look through function calls, so calling `.field(self.as_slice())` for example
4241
/// does not consider fields used inside of `as_slice()` as used by the `Debug` impl.
4342
///
44-
///
4543
/// ### Example
4644
/// ```rust
4745
/// use std::fmt;
@@ -83,21 +81,20 @@ declare_clippy_lint! {
8381
}
8482
declare_lint_pass!(MissingFieldInDebug => [MISSING_FIELDS_IN_DEBUG]);
8583

86-
const WARN: &str = "manual `Debug` impl does not include all fields";
87-
const HELP_WILDCARD: &str = "unused field here due to wildcard `_`";
88-
const HELP_UNUSED_REST: &str = "more unused fields here due to rest pattern `..`";
89-
const HELP_FIELD_UNUSED: &str = "this field is unused";
90-
const HELP_FIELD_REFERENCE: &str = "the field referenced by this binding is unused";
91-
const HELP_INCLUDE_ALL_FIELDS: &str = "consider including all fields in this `Debug` impl";
92-
const HELP_FINISH_NON_EXHAUSTIVE: &str = "consider calling `.finish_non_exhaustive()` if you intend to ignore fields";
93-
9484
fn report_lints(cx: &LateContext<'_>, span: Span, span_notes: Vec<(Span, &'static str)>) {
95-
span_lint_and_then(cx, MISSING_FIELDS_IN_DEBUG, span, WARN, |diag| {
96-
for (span, note) in span_notes {
97-
diag.span_note(span, note);
98-
}
99-
diag.help(HELP_INCLUDE_ALL_FIELDS).help(HELP_FINISH_NON_EXHAUSTIVE);
100-
});
85+
span_lint_and_then(
86+
cx,
87+
MISSING_FIELDS_IN_DEBUG,
88+
span,
89+
"manual `Debug` impl does not include all fields",
90+
|diag| {
91+
for (span, note) in span_notes {
92+
diag.span_note(span, note);
93+
}
94+
diag.help("consider including all fields in this `Debug` impl")
95+
.help("consider calling `.finish_non_exhaustive()` if you intend to ignore fields");
96+
},
97+
);
10198
}
10299

103100
/// Checks if we should lint in a block of code
@@ -119,20 +116,14 @@ fn should_lint<'tcx>(
119116
};
120117
let recv_ty = typeck_results.expr_ty(recv).peel_refs();
121118

122-
if_chain! {
123-
if [sym::debug_struct, sym::debug_tuple].contains(&path.ident.name);
124-
if match_type(cx, recv_ty, &paths::FORMATTER);
125-
then {
126-
has_debug_struct_tuple = true;
127-
}
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;
128123
}
129124

130-
if_chain! {
131-
if path.ident.name.as_str() == "finish_non_exhaustive";
132-
if match_type(cx, recv_ty, &paths::DEBUG_STRUCT);
133-
then {
134-
has_finish_non_exhaustive = true;
135-
}
125+
if path.ident.name.as_str() == "finish_non_exhaustive" && match_type(cx, recv_ty, &paths::DEBUG_STRUCT) {
126+
has_finish_non_exhaustive = true;
136127
}
137128
ControlFlow::Continue(())
138129
});
@@ -152,13 +143,11 @@ fn check_struct<'tcx>(
152143
let mut field_accesses = FxHashSet::default();
153144

154145
let _: Option<!> = for_each_expr(block, |expr| {
155-
if_chain! {
156-
if let ExprKind::Field(target, ident) = expr.kind;
157-
let target_ty = typeck_results.expr_ty(target).peel_refs();
158-
if target_ty == self_ty;
159-
then {
160-
field_accesses.insert(ident);
161-
}
146+
if let ExprKind::Field(target, ident) = expr.kind
147+
&& let target_ty = typeck_results.expr_ty(target).peel_refs()
148+
&& target_ty == self_ty
149+
{
150+
field_accesses.insert(ident);
162151
}
163152
ControlFlow::Continue(())
164153
});
@@ -170,12 +159,14 @@ fn check_struct<'tcx>(
170159
if field_accesses.contains(&field.ident) {
171160
None
172161
} else {
173-
Some((field.span, HELP_FIELD_UNUSED))
162+
Some((field.span, "this field is unused"))
174163
}
175164
})
176165
.collect::<Vec<_>>();
177166

178-
if !span_notes.is_empty() {
167+
// only lint if there's also at least one field access to allow patterns
168+
// where one might have a newtype struct and uses fields from the wrapped type
169+
if !span_notes.is_empty() && !field_accesses.is_empty() {
179170
report_lints(cx, item.span, span_notes);
180171
}
181172
}
@@ -194,56 +185,54 @@ fn check_enum<'tcx>(
194185
item: &'tcx Item<'tcx>,
195186
) {
196187
let Some(arms) = for_each_expr(block, |expr| {
197-
if_chain! {
198-
if let ExprKind::Match(val, arms, MatchSource::Normal) = &expr.kind;
199-
if let match_ty = typeck_results.expr_ty(val).peel_refs();
200-
if match_ty == self_ty;
201-
then {
202-
ControlFlow::Break(arms)
203-
} else {
204-
ControlFlow::Continue(())
205-
}
188+
if let ExprKind::Match(val, arms, MatchSource::Normal) = expr.kind
189+
&& let match_ty = typeck_results.expr_ty(val).peel_refs()
190+
&& match_ty == self_ty
191+
{
192+
ControlFlow::Break(arms)
193+
} else {
194+
ControlFlow::Continue(())
206195
}
207196
}) else {
208197
return;
209198
};
210199

211200
let mut span_notes = Vec::new();
212201

213-
for arm in *arms {
202+
for arm in arms {
214203
if !should_lint(cx, typeck_results, arm.body) {
215204
continue;
216205
}
217206

218-
arm.pat.walk_always(|pat| match &pat.kind {
219-
PatKind::Wild => span_notes.push((pat.span, HELP_WILDCARD)),
207+
arm.pat.walk_always(|pat| match pat.kind {
208+
PatKind::Wild => span_notes.push((pat.span, "unused field here due to wildcard `_`")),
220209
PatKind::Tuple(_, rest) | PatKind::TupleStruct(.., rest) if rest.as_opt_usize().is_some() => {
221-
span_notes.push((pat.span, HELP_UNUSED_REST));
210+
span_notes.push((pat.span, "more unused fields here due to rest pattern `..`"));
211+
},
212+
PatKind::Struct(.., true) => {
213+
span_notes.push((pat.span, "more unused fields here due to rest pattern `..`"))
222214
},
223-
PatKind::Struct(.., true) => span_notes.push((pat.span, HELP_UNUSED_REST)),
224215
_ => {},
225216
});
226217

227218
let mut field_accesses = FxHashSet::default();
228219

229220
let _: Option<!> = for_each_expr(arm.body, |expr| {
230-
if_chain! {
231-
if let ExprKind::Path(QPath::Resolved(_, path)) = &expr.kind;
232-
if let Some(segment) = path.segments.first();
233-
then {
234-
arm.pat.each_binding(|_, _, _, pat_ident| {
235-
if segment.ident == pat_ident {
236-
field_accesses.insert(pat_ident);
237-
}
238-
});
239-
}
221+
if let ExprKind::Path(QPath::Resolved(_, path)) = expr.kind
222+
&& let Some(segment) = path.segments.first()
223+
{
224+
arm.pat.each_binding(|_, _, _, pat_ident| {
225+
if segment.ident == pat_ident {
226+
field_accesses.insert(pat_ident);
227+
}
228+
});
240229
}
241230
ControlFlow::Continue(())
242231
});
243232

244233
arm.pat.each_binding(|_, _, span, pat_ident| {
245234
if !field_accesses.contains(&pat_ident) {
246-
span_notes.push((span, HELP_FIELD_REFERENCE));
235+
span_notes.push((span, "the field referenced by this binding is unused"));
247236
}
248237
});
249238
}
@@ -255,38 +244,36 @@ fn check_enum<'tcx>(
255244

256245
impl<'tcx> LateLintPass<'tcx> for MissingFieldInDebug {
257246
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx rustc_hir::Item<'tcx>) {
258-
if_chain! {
259-
// is this an `impl Debug for X` block?
260-
if let ItemKind::Impl(Impl { of_trait: Some(trait_ref), self_ty, items, .. }) = item.kind;
261-
if let Res::Def(DefKind::Trait, trait_def_id) = trait_ref.path.res;
262-
if let TyKind::Path(QPath::Resolved(_, self_path)) = &self_ty.kind;
263-
if cx.match_def_path(trait_def_id, &[sym::core, sym::fmt, sym::Debug]);
264-
247+
// is this an `impl Debug for X` block?
248+
if let ItemKind::Impl(Impl { of_trait: Some(trait_ref), self_ty, items, .. }) = item.kind
249+
&& let Res::Def(DefKind::Trait, trait_def_id) = trait_ref.path.res
250+
&& let TyKind::Path(QPath::Resolved(_, self_path)) = &self_ty.kind
251+
&& cx.match_def_path(trait_def_id, &[sym::core, sym::fmt, sym::Debug])
252+
265253
// don't trigger if this impl was derived
266-
if !cx.tcx.has_attr(item.owner_id, sym::automatically_derived);
267-
if !item.span.from_expansion();
268-
254+
&& !cx.tcx.has_attr(item.owner_id, sym::automatically_derived)
255+
&& !item.span.from_expansion()
256+
269257
// find `Debug::fmt` function
270-
if let Some(fmt_item) = items.iter().find(|i| i.ident.name == sym::fmt);
271-
if let ImplItem { kind: ImplItemKind::Fn(_, body_id), .. } = cx.tcx.hir().impl_item(fmt_item.id);
272-
let body = cx.tcx.hir().body(*body_id);
273-
if let ExprKind::Block(block, _) = body.value.kind;
274-
258+
&& let Some(fmt_item) = items.iter().find(|i| i.ident.name == sym::fmt)
259+
&& let ImplItem { kind: ImplItemKind::Fn(_, body_id), .. } = cx.tcx.hir().impl_item(fmt_item.id)
260+
&& let body = cx.tcx.hir().body(*body_id)
261+
&& let ExprKind::Block(block, _) = body.value.kind
262+
275263
// inspect `self`
276-
let self_ty = cx.tcx.type_of(self_path.res.def_id()).0.peel_refs();
277-
if let Some(self_adt) = self_ty.ty_adt_def();
278-
if let Some(self_def_id) = self_adt.did().as_local();
279-
if let Some(Node::Item(self_item)) = cx.tcx.hir().find_by_def_id(self_def_id);
280-
264+
&& let self_ty = cx.tcx.type_of(self_path.res.def_id()).0.peel_refs()
265+
&& let Some(self_adt) = self_ty.ty_adt_def()
266+
&& let Some(self_def_id) = self_adt.did().as_local()
267+
&& let Some(Node::Item(self_item)) = cx.tcx.hir().find_by_def_id(self_def_id)
268+
281269
// NB: can't call cx.typeck_results() as we are not in a body
282-
let typeck_results = cx.tcx.typeck_body(*body_id);
283-
if should_lint(cx, typeck_results, block);
284-
then {
285-
match &self_item.kind {
286-
ItemKind::Struct(data, _) => check_struct(cx, typeck_results, block, self_ty, item, data),
287-
ItemKind::Enum(..) => check_enum(cx, typeck_results, block, self_ty, item),
288-
_ => {}
289-
}
270+
&& let typeck_results = cx.tcx.typeck_body(*body_id)
271+
&& should_lint(cx, typeck_results, block)
272+
{
273+
match &self_item.kind {
274+
ItemKind::Struct(data, _) => check_struct(cx, typeck_results, block, self_ty, item, data),
275+
ItemKind::Enum(..) => check_enum(cx, typeck_results, block, self_ty, item),
276+
_ => {}
290277
}
291278
}
292279
}

tests/ui/missing_field_in_debug.rs renamed to tests/ui/missing_fields_in_debug.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,4 +229,32 @@ enum DerivedEnum {
229229
B { a: String },
230230
}
231231

232+
// https://github.com/rust-lang/rust-clippy/pull/10616#discussion_r1166846953
233+
234+
struct Inner {
235+
a: usize,
236+
b: usize,
237+
}
238+
239+
struct HasInner {
240+
inner: Inner,
241+
}
242+
243+
impl HasInner {
244+
fn get(&self) -> &Inner {
245+
&self.inner
246+
}
247+
}
248+
249+
impl fmt::Debug for HasInner {
250+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
251+
let inner = self.get();
252+
253+
f.debug_struct("HasInner")
254+
.field("a", &inner.a)
255+
.field("b", &inner.b)
256+
.finish()
257+
}
258+
}
259+
232260
fn main() {}

0 commit comments

Comments
 (0)