Skip to content

Commit e43b793

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 e43b793

File tree

4 files changed

+529
-0
lines changed

4 files changed

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

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)