Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 43adf23

Browse files
committedDec 27, 2024
Auto merge of #134737 - estebank:deive-lint-default-fields-base, r=<try>
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)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ``` r? `@compiler-errors` This is a simpler version of #134441, detecting the simpler case when a field with a default should have not been specified in the manual `Default::default()`, instead using `..` for it. It doesn't provide any suggestions, nor the checks for "equivalences" nor whether the value used in the imp being used would be suitable as a default field value.
2 parents 6d3db55 + 01307cf commit 43adf23

File tree

4 files changed

+526
-0
lines changed

4 files changed

+526
-0
lines changed
 
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
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 now know we have a manually written definition of a `<Type as Default>::default()`.
76+
77+
let hir = cx.tcx.hir();
78+
79+
let type_def_id = def.did();
80+
let body = hir.body(body_id);
81+
82+
// FIXME: evaluate bodies with statements and evaluate bindings to see if they would be
83+
// derivable.
84+
let hir::ExprKind::Block(hir::Block { stmts: _, expr: Some(expr), .. }, None) =
85+
body.value.kind
86+
else {
87+
return;
88+
};
89+
90+
// Keep a mapping of field name to `hir::FieldDef` for every field in the type. We'll use
91+
// these to check for things like checking whether it has a default or using its span for
92+
// suggestions.
93+
let orig_fields = match hir.get_if_local(type_def_id) {
94+
Some(hir::Node::Item(hir::Item {
95+
kind:
96+
hir::ItemKind::Struct(hir::VariantData::Struct { fields, recovered: _ }, _generics),
97+
..
98+
})) => fields.iter().map(|f| (f.ident.name, f)).collect::<FxHashMap<_, _>>(),
99+
_ => return,
100+
};
101+
102+
// We check `fn default()` body is a single ADT literal and get all the fields that are
103+
// being set.
104+
let hir::ExprKind::Struct(_qpath, fields, tail) = expr.kind else { return };
105+
106+
// We have a struct literal
107+
//
108+
// struct Foo {
109+
// field: Type,
110+
// }
111+
//
112+
// impl Default for Foo {
113+
// fn default() -> Foo {
114+
// Foo {
115+
// field: val,
116+
// }
117+
// }
118+
// }
119+
//
120+
// We would suggest `#[derive(Default)]` if `field` has a default value, regardless of what
121+
// it is; we don't want to encourage divergent behavior between `Default::default()` and
122+
// `..`.
123+
124+
if let hir::StructTailExpr::Base(_) = tail {
125+
// This is *very* niche. We'd only get here if someone wrote
126+
// impl Default for Ty {
127+
// fn default() -> Ty {
128+
// Ty { ..something() }
129+
// }
130+
// }
131+
// where `something()` would have to be a call or path.
132+
// We have nothing meaninful to do with this.
133+
return;
134+
}
135+
136+
// At least one of the fields with a default value have been overriden in
137+
// the `Default` implementation. We suggest removing it and relying on `..`
138+
// instead.
139+
let any_default_field_given =
140+
fields.iter().any(|f| orig_fields.get(&f.ident.name).and_then(|f| f.default).is_some());
141+
142+
if !any_default_field_given {
143+
// None of the default fields were actually provided explicitly, so the manual impl
144+
// doesn't override them (the user used `..`), so there's no risk of divergent behavior.
145+
return;
146+
}
147+
148+
let Some(local) = parent.as_local() else { return };
149+
let hir_id = cx.tcx.local_def_id_to_hir_id(local);
150+
let hir::Node::Item(item) = cx.tcx.hir_node(hir_id) else { return };
151+
cx.tcx.node_span_lint(DEFAULT_OVERRIDES_DEFAULT_FIELDS, hir_id, item.span, |diag| {
152+
mk_lint(diag, orig_fields, fields);
153+
});
154+
}
155+
}
156+
157+
fn mk_lint(
158+
diag: &mut Diag<'_, ()>,
159+
orig_fields: FxHashMap<Symbol, &hir::FieldDef<'_>>,
160+
fields: &[hir::ExprField<'_>],
161+
) {
162+
diag.primary_message("`Default` impl doesn't use the declared default field values");
163+
164+
// For each field in the struct expression
165+
// - if the field in the type has a default value, it should be removed
166+
// - elif the field is an expression that could be a default value, it should be used as the
167+
// field's default value (FIXME: not done).
168+
// - else, we wouldn't touch this field, it would remain in the manual impl
169+
let mut removed_all_fields = true;
170+
for field in fields {
171+
if orig_fields.get(&field.ident.name).and_then(|f| f.default).is_some() {
172+
diag.span_label(field.expr.span, "this field has a default value");
173+
} else {
174+
removed_all_fields = false;
175+
}
176+
}
177+
178+
diag.help(if removed_all_fields {
179+
"to avoid divergence in behavior between `Struct { .. }` and \
180+
`<Struct as Default>::default()`, derive the `Default`"
181+
} else {
182+
"use the default values in the `impl` with `Struct { mandatory_field, .. }` to avoid them \
183+
diverging over time"
184+
});
185+
}

‎compiler/rustc_lint/src/lib.rs

Lines changed: 3 additions & 0 deletions
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,
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
// Warn when we encounter a manual `Default` impl that could be derived.
2+
// Restricted only to types using `default_field_values`.
3+
#![feature(default_field_values)]
4+
#![allow(dead_code)]
5+
#![deny(default_overrides_default_fields)]
6+
struct S(i32);
7+
fn s() -> S { S(1) }
8+
9+
struct A {
10+
x: S,
11+
y: i32 = 1,
12+
}
13+
14+
impl Default for A { //~ ERROR default_overrides_default_fields
15+
fn default() -> Self {
16+
A {
17+
y: 0,
18+
x: s(),
19+
}
20+
}
21+
}
22+
23+
struct B {
24+
x: S = S(3),
25+
y: i32 = 1,
26+
}
27+
28+
impl Default for B { //~ ERROR default_overrides_default_fields
29+
fn default() -> Self {
30+
B {
31+
x: s(),
32+
y: 0,
33+
}
34+
}
35+
}
36+
37+
struct C {
38+
x: S,
39+
y: i32 = 1,
40+
z: i32 = 1,
41+
}
42+
43+
impl Default for C { //~ ERROR default_overrides_default_fields
44+
fn default() -> Self {
45+
C {
46+
x: s(),
47+
y: 0,
48+
..
49+
}
50+
}
51+
}
52+
53+
struct D {
54+
x: S,
55+
y: i32 = 1,
56+
z: i32 = 1,
57+
}
58+
59+
impl Default for D { //~ ERROR default_overrides_default_fields
60+
fn default() -> Self {
61+
D {
62+
y: 0,
63+
x: s(),
64+
..
65+
}
66+
}
67+
}
68+
69+
struct E {
70+
x: S,
71+
y: i32 = 1,
72+
z: i32 = 1,
73+
}
74+
75+
impl Default for E { //~ ERROR default_overrides_default_fields
76+
fn default() -> Self {
77+
E {
78+
y: 0,
79+
z: 0,
80+
x: s(),
81+
}
82+
}
83+
}
84+
85+
// Let's ensure that the span for `x` and the span for `y` don't overlap when suggesting their
86+
// removal in favor of their default field values.
87+
struct E2 {
88+
x: S,
89+
y: i32 = 1,
90+
z: i32 = 1,
91+
}
92+
93+
impl Default for E2 { //~ ERROR default_overrides_default_fields
94+
fn default() -> Self {
95+
E2 {
96+
x: s(),
97+
y: i(),
98+
z: 0,
99+
}
100+
}
101+
}
102+
103+
fn i() -> i32 {
104+
1
105+
}
106+
107+
// Account for a `const fn` being the `Default::default()` of a field's type.
108+
struct F {
109+
x: G,
110+
y: i32 = 1,
111+
}
112+
113+
impl Default for F { //~ ERROR default_overrides_default_fields
114+
fn default() -> Self {
115+
F {
116+
x: g_const(),
117+
y: 0,
118+
}
119+
}
120+
}
121+
122+
struct G;
123+
124+
impl Default for G { // ok
125+
fn default() -> Self {
126+
g_const()
127+
}
128+
}
129+
130+
const fn g_const() -> G {
131+
G
132+
}
133+
134+
// Account for a `const fn` being used in `Default::default()`, even if the type doesn't use it as
135+
// its own `Default`. We suggest setting the default field value in that case.
136+
struct H {
137+
x: I,
138+
y: i32 = 1,
139+
}
140+
141+
impl Default for H { //~ ERROR default_overrides_default_fields
142+
fn default() -> Self {
143+
H {
144+
x: i_const(),
145+
y: 0,
146+
}
147+
}
148+
}
149+
150+
struct I;
151+
152+
const fn i_const() -> I {
153+
I
154+
}
155+
156+
// Account for a `const` and struct literal being the `Default::default()` of a field's type.
157+
struct M {
158+
x: N,
159+
y: i32 = 1,
160+
z: A,
161+
}
162+
163+
impl Default for M { // ok, `y` is not specified
164+
fn default() -> Self {
165+
M {
166+
x: N_CONST,
167+
z: A {
168+
x: S(0),
169+
y: 0,
170+
},
171+
..
172+
}
173+
}
174+
}
175+
176+
struct N;
177+
178+
const N_CONST: N = N;
179+
180+
struct O {
181+
x: Option<i32>,
182+
y: i32 = 1,
183+
}
184+
185+
impl Default for O { //~ ERROR default_overrides_default_fields
186+
fn default() -> Self {
187+
O {
188+
x: None,
189+
y: 1,
190+
}
191+
}
192+
}
193+
194+
fn main() {}
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
error: `Default` impl doesn't use the declared default field values
2+
--> $DIR/manual-default-impl-could-be-derived.rs:14:1
3+
|
4+
LL | / impl Default for A {
5+
LL | | fn default() -> Self {
6+
LL | | A {
7+
LL | | y: 0,
8+
| | - this field has a default value
9+
... |
10+
LL | | }
11+
| |_^
12+
|
13+
= help: use the default values in the `impl` with `Struct { mandatory_field, .. }` to avoid them diverging over time
14+
note: the lint level is defined here
15+
--> $DIR/manual-default-impl-could-be-derived.rs:5:9
16+
|
17+
LL | #![deny(default_overrides_default_fields)]
18+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
19+
20+
error: `Default` impl doesn't use the declared default field values
21+
--> $DIR/manual-default-impl-could-be-derived.rs:28:1
22+
|
23+
LL | / impl Default for B {
24+
LL | | fn default() -> Self {
25+
LL | | B {
26+
LL | | x: s(),
27+
| | --- this field has a default value
28+
LL | | y: 0,
29+
| | - this field has a default value
30+
... |
31+
LL | | }
32+
| |_^
33+
|
34+
= help: to avoid divergence in behavior between `Struct { .. }` and `<Struct as Default>::default()`, derive the `Default`
35+
36+
error: `Default` impl doesn't use the declared default field values
37+
--> $DIR/manual-default-impl-could-be-derived.rs:43:1
38+
|
39+
LL | / impl Default for C {
40+
LL | | fn default() -> Self {
41+
LL | | C {
42+
LL | | x: s(),
43+
LL | | y: 0,
44+
| | - this field has a default value
45+
... |
46+
LL | | }
47+
| |_^
48+
|
49+
= help: use the default values in the `impl` with `Struct { mandatory_field, .. }` to avoid them diverging over time
50+
51+
error: `Default` impl doesn't use the declared default field values
52+
--> $DIR/manual-default-impl-could-be-derived.rs:59:1
53+
|
54+
LL | / impl Default for D {
55+
LL | | fn default() -> Self {
56+
LL | | D {
57+
LL | | y: 0,
58+
| | - this field has a default value
59+
... |
60+
LL | | }
61+
| |_^
62+
|
63+
= help: use the default values in the `impl` with `Struct { mandatory_field, .. }` to avoid them diverging over time
64+
65+
error: `Default` impl doesn't use the declared default field values
66+
--> $DIR/manual-default-impl-could-be-derived.rs:75:1
67+
|
68+
LL | / impl Default for E {
69+
LL | | fn default() -> Self {
70+
LL | | E {
71+
LL | | y: 0,
72+
| | - this field has a default value
73+
LL | | z: 0,
74+
| | - this field has a default value
75+
... |
76+
LL | | }
77+
| |_^
78+
|
79+
= help: use the default values in the `impl` with `Struct { mandatory_field, .. }` to avoid them diverging over time
80+
81+
error: `Default` impl doesn't use the declared default field values
82+
--> $DIR/manual-default-impl-could-be-derived.rs:93:1
83+
|
84+
LL | / impl Default for E2 {
85+
LL | | fn default() -> Self {
86+
LL | | E2 {
87+
LL | | x: s(),
88+
LL | | y: i(),
89+
| | --- this field has a default value
90+
LL | | z: 0,
91+
| | - this field has a default value
92+
... |
93+
LL | | }
94+
| |_^
95+
|
96+
= help: use the default values in the `impl` with `Struct { mandatory_field, .. }` to avoid them diverging over time
97+
98+
error: `Default` impl doesn't use the declared default field values
99+
--> $DIR/manual-default-impl-could-be-derived.rs:113:1
100+
|
101+
LL | / impl Default for F {
102+
LL | | fn default() -> Self {
103+
LL | | F {
104+
LL | | x: g_const(),
105+
LL | | y: 0,
106+
| | - this field has a default value
107+
... |
108+
LL | | }
109+
| |_^
110+
|
111+
= help: use the default values in the `impl` with `Struct { mandatory_field, .. }` to avoid them diverging over time
112+
113+
error: `Default` impl doesn't use the declared default field values
114+
--> $DIR/manual-default-impl-could-be-derived.rs:141:1
115+
|
116+
LL | / impl Default for H {
117+
LL | | fn default() -> Self {
118+
LL | | H {
119+
LL | | x: i_const(),
120+
LL | | y: 0,
121+
| | - this field has a default value
122+
... |
123+
LL | | }
124+
| |_^
125+
|
126+
= help: use the default values in the `impl` with `Struct { mandatory_field, .. }` to avoid them diverging over time
127+
128+
error: `Default` impl doesn't use the declared default field values
129+
--> $DIR/manual-default-impl-could-be-derived.rs:185:1
130+
|
131+
LL | / impl Default for O {
132+
LL | | fn default() -> Self {
133+
LL | | O {
134+
LL | | x: None,
135+
LL | | y: 1,
136+
| | - this field has a default value
137+
... |
138+
LL | | }
139+
| |_^
140+
|
141+
= help: use the default values in the `impl` with `Struct { mandatory_field, .. }` to avoid them diverging over time
142+
143+
error: aborting due to 9 previous errors
144+

0 commit comments

Comments
 (0)
Please sign in to comment.