Skip to content

Commit f9f1925

Browse files
committed
Implement default_overrides_default_fields lint
Detect when a manual `Default` implementation isn't using the existing default field values and suggest using `..` instead: ``` error: `Default` impl doesn't use the declared default field values --> $DIR/manual-default-impl-could-be-derived.rs:14:1 | LL | / impl Default for A { LL | | fn default() -> Self { LL | | A { LL | | y: 0, | | - this field has a default value ... | LL | | } | |_^ | = help: use the default values in the `impl` with `Struct { mandatory_field, .. }` to avoid them diverging over time note: the lint level is defined here --> $DIR/manual-default-impl-could-be-derived.rs:5:9 | LL | #![deny(default_overrides_default_fields)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ```
1 parent e108481 commit f9f1925

File tree

4 files changed

+547
-0
lines changed

4 files changed

+547
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
use rustc_data_structures::fx::FxHashMap;
2+
use rustc_errors::Diag;
3+
use rustc_hir as hir;
4+
use rustc_hir::def_id::DefId;
5+
use rustc_middle::ty;
6+
use rustc_session::{declare_lint, impl_lint_pass};
7+
use rustc_span::Symbol;
8+
use rustc_span::symbol::sym;
9+
10+
use crate::{LateContext, LateLintPass};
11+
12+
declare_lint! {
13+
/// The `default_overrides_default_fields` lint checks for manual `impl` blocks of the
14+
/// `Default` trait of types with default field values.
15+
///
16+
/// ### Example
17+
///
18+
/// ```rust,compile_fail
19+
/// #![feature(default_field_values)]
20+
/// struct Foo {
21+
/// x: i32 = 101,
22+
/// y: NonDefault,
23+
/// }
24+
///
25+
/// struct NonDefault;
26+
///
27+
/// #[deny(default_overrides_default_fields)]
28+
/// impl Default for Foo {
29+
/// fn default() -> Foo {
30+
/// Foo { x: 100, y: NonDefault }
31+
/// }
32+
/// }
33+
/// ```
34+
///
35+
/// {{produces}}
36+
///
37+
/// ### Explanation
38+
///
39+
/// Manually writing a `Default` implementation for a type that has
40+
/// default field values runs the risk of diverging behavior between
41+
/// `Type { .. }` and `<Type as Default>::default()`, which would be a
42+
/// foot-gun for users of that type that would expect these to be
43+
/// equivalent. If `Default` can't be derived due to some fields not
44+
/// having a `Default` implementation, we encourage the use of `..` for
45+
/// the fields that do have a default field value.
46+
pub DEFAULT_OVERRIDES_DEFAULT_FIELDS,
47+
Deny,
48+
"detect `Default` impl that should use the type's default field values",
49+
@feature_gate = default_field_values;
50+
}
51+
52+
#[derive(Default)]
53+
pub(crate) struct DefaultCouldBeDerived {
54+
data: Option<Data>,
55+
}
56+
57+
struct Data {
58+
type_def_id: DefId,
59+
parent: DefId,
60+
}
61+
62+
impl_lint_pass!(DefaultCouldBeDerived => [DEFAULT_OVERRIDES_DEFAULT_FIELDS]);
63+
64+
impl<'tcx> LateLintPass<'tcx> for DefaultCouldBeDerived {
65+
fn check_impl_item(&mut self, cx: &LateContext<'_>, impl_item: &hir::ImplItem<'_>) {
66+
// Look for manual implementations of `Default`.
67+
let Some(default_def_id) = cx.tcx.get_diagnostic_item(sym::Default) else { return };
68+
let assoc = cx.tcx.associated_item(impl_item.owner_id);
69+
let parent = assoc.container_id(cx.tcx);
70+
if cx.tcx.has_attr(parent, sym::automatically_derived) {
71+
// We don't care about what `#[derive(Default)]` produces in this lint.
72+
return;
73+
}
74+
let Some(trait_ref) = cx.tcx.impl_trait_ref(parent) else { return };
75+
let trait_ref = trait_ref.instantiate_identity();
76+
if trait_ref.def_id != default_def_id {
77+
return;
78+
}
79+
let ty = trait_ref.self_ty();
80+
let ty::Adt(def, _) = ty.kind() else { return };
81+
82+
// We have a manually written definition of a `<Type as Default>::default()`. We store the
83+
// necessary metadata for further analysis of its body in `check_body`.
84+
self.data = Some(Data { type_def_id: def.did(), parent });
85+
}
86+
87+
fn check_impl_item_post(&mut self, _cx: &LateContext<'_>, _impl_item: &hir::ImplItem<'_>) {
88+
// Clean up.
89+
self.data = None;
90+
}
91+
92+
fn check_body(&mut self, cx: &LateContext<'tcx>, body: &hir::Body<'tcx>) {
93+
// We perform this logic here instead of in `check_impl_item` so that we have unconditional
94+
// permission to call `cx.typeck_results()` (because we're within a body).
95+
let Some(Data { type_def_id, parent }) = self.data else {
96+
// We're not in a `<Type as Default>::default()` body.
97+
return;
98+
};
99+
// FIXME: evaluate bodies with statements and evaluate bindings to see if they would be
100+
// derivable.
101+
let hir::ExprKind::Block(hir::Block { stmts: _, expr: Some(expr), .. }, None) =
102+
body.value.kind
103+
else {
104+
return;
105+
};
106+
107+
let hir = cx.tcx.hir();
108+
109+
// Keep a mapping of field name to `hir::FieldDef` for every field in the type. We'll use
110+
// these to check for things like checking whether it has a default or using its span for
111+
// suggestions.
112+
let orig_fields = match hir.get_if_local(type_def_id) {
113+
Some(hir::Node::Item(hir::Item {
114+
kind:
115+
hir::ItemKind::Struct(hir::VariantData::Struct { fields, recovered: _ }, _generics),
116+
..
117+
})) => fields.iter().map(|f| (f.ident.name, f)).collect::<FxHashMap<_, _>>(),
118+
_ => return,
119+
};
120+
121+
// We check `fn default()` body is a single ADT literal and all the fields are being
122+
// set to something equivalent to the corresponding types' `Default::default()`.
123+
let hir::ExprKind::Struct(_qpath, fields, tail) = expr.kind else { return };
124+
125+
// We have a struct literal
126+
//
127+
// struct Foo {
128+
// field: Type,
129+
// }
130+
//
131+
// impl Default for Foo {
132+
// fn default() -> Foo {
133+
// Foo {
134+
// field: val,
135+
// }
136+
// }
137+
// }
138+
//
139+
// We would suggest `#[derive(Default)]` if
140+
// - `field` has a default value, regardless of what it is; we don't want to
141+
// encourage divergent behavior between `Default::default()` and `..`
142+
// - `val` is `Default::default()`
143+
// - `val` matches the `Default::default()` body for that type
144+
// - `val` is `0`
145+
// - `val` is `false`
146+
147+
if let hir::StructTailExpr::Base(_) = tail {
148+
// This is *very* niche. We'd only get here if someone wrote
149+
// impl Default for Ty {
150+
// fn default() -> Ty {
151+
// Ty { ..something() }
152+
// }
153+
// }
154+
// where `something()` would have to be a call or path.
155+
// We have nothing meaninful to do with this.
156+
return;
157+
}
158+
159+
// At least one of the fields with a default value have been overriden in
160+
// the `Default` implementation. We suggest removing it and relying on `..`
161+
// instead.
162+
let any_default_field_given =
163+
fields.iter().any(|f| orig_fields.get(&f.ident.name).and_then(|f| f.default).is_some());
164+
165+
if !any_default_field_given {
166+
return;
167+
}
168+
169+
let Some(local) = parent.as_local() else { return };
170+
let hir_id = cx.tcx.local_def_id_to_hir_id(local);
171+
let hir::Node::Item(item) = cx.tcx.hir_node(hir_id) else { return };
172+
cx.tcx.node_span_lint(DEFAULT_OVERRIDES_DEFAULT_FIELDS, hir_id, item.span, |diag| {
173+
mk_lint(diag, orig_fields, fields);
174+
});
175+
}
176+
}
177+
178+
fn mk_lint(
179+
diag: &mut Diag<'_, ()>,
180+
orig_fields: FxHashMap<Symbol, &hir::FieldDef<'_>>,
181+
fields: &[hir::ExprField<'_>],
182+
) {
183+
diag.primary_message("`Default` impl doesn't use the declared default field values");
184+
185+
// For each field in the struct expression
186+
// - if the field in the type has a default value, it should be removed
187+
// - elif the field is an expression that could be a default value, it should be used as the
188+
// field's default value.
189+
// - else, we wouldn't touch this field, it would remain in the manual impl
190+
let mut removed_all_fields = true;
191+
for field in fields {
192+
if orig_fields.get(&field.ident.name).and_then(|f| f.default).is_some() {
193+
diag.span_label(field.expr.span, "this field has a default value");
194+
} else {
195+
removed_all_fields = false;
196+
}
197+
}
198+
199+
diag.help(if removed_all_fields {
200+
"to avoid divergence in behavior between `Struct { .. }` and \
201+
`<Struct as Default>::default()`, derive the `Default`"
202+
} else {
203+
"use the default values in the `impl` with `Struct { mandatory_field, .. }` to avoid them \
204+
diverging over time"
205+
});
206+
}

compiler/rustc_lint/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ mod async_fn_in_trait;
4141
pub mod builtin;
4242
mod context;
4343
mod dangling;
44+
mod default_could_be_derived;
4445
mod deref_into_dyn_supertrait;
4546
mod drop_forget_useless;
4647
mod early;
@@ -85,6 +86,7 @@ use async_closures::AsyncClosureUsage;
8586
use async_fn_in_trait::AsyncFnInTrait;
8687
use builtin::*;
8788
use dangling::*;
89+
use default_could_be_derived::DefaultCouldBeDerived;
8890
use deref_into_dyn_supertrait::*;
8991
use drop_forget_useless::*;
9092
use enum_intrinsics_non_enums::EnumIntrinsicsNonEnums;
@@ -189,6 +191,7 @@ late_lint_methods!(
189191
BuiltinCombinedModuleLateLintPass,
190192
[
191193
ForLoopsOverFallibles: ForLoopsOverFallibles,
194+
DefaultCouldBeDerived: DefaultCouldBeDerived::default(),
192195
DerefIntoDynSupertrait: DerefIntoDynSupertrait,
193196
DropForgetUseless: DropForgetUseless,
194197
ImproperCTypesDeclarations: ImproperCTypesDeclarations,

0 commit comments

Comments
 (0)