Skip to content

Commit 9f14773

Browse files
committed
Detect when a field default is not using that field's type's default values
Given ```rust pub struct Foo { pub something: i32 = 2, } pub struct Bar { pub foo: Foo = Foo { something: 10 }, } ``` The value for `Bar { .. }.foo.something` is different to the value in `Foo { .. }.something`. This can cause confusion so we lint against it. We don't check that the values actually differ. At just the mere presence of the default value we suggest `..` to avoid the possibility of divergence. This is just a lint to allow for APIs where that divergence is intentional.
1 parent 7b26197 commit 9f14773

File tree

4 files changed

+204
-5
lines changed

4 files changed

+204
-5
lines changed

compiler/rustc_lint/src/default_could_be_derived.rs

Lines changed: 128 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
use rustc_data_structures::fx::FxHashMap;
2-
use rustc_errors::{Applicability, Diag};
1+
use hir::def::{CtorOf, DefKind, Res};
2+
use rustc_ast::Recovered;
3+
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
4+
use rustc_errors::{Applicability, Diag, MultiSpan};
35
use rustc_hir as hir;
46
use rustc_middle::ty;
57
use rustc_middle::ty::TyCtxt;
@@ -50,7 +52,6 @@ declare_lint! {
5052
@feature_gate = default_field_values;
5153
}
5254

53-
#[derive(Default)]
5455
pub(crate) struct DefaultCouldBeDerived;
5556

5657
impl_lint_pass!(DefaultCouldBeDerived => [DEFAULT_OVERRIDES_DEFAULT_FIELDS]);
@@ -201,3 +202,127 @@ fn mk_lint(
201202
diag.help(msg);
202203
}
203204
}
205+
206+
declare_lint! {
207+
/// The `default_field_overrides_default_field` lint checks for struct literals in field default
208+
/// values with fields that have in turn default values. These should instead be skipped and
209+
/// rely on `..` for them.
210+
///
211+
/// ### Example
212+
///
213+
/// ```rust,compile_fail
214+
/// #![feature(default_field_values)]
215+
/// #![deny(default_field_overrides_default_field)]
216+
///
217+
/// struct Foo {
218+
/// x: Bar = Bar { x: 0 }, // `Foo { .. }.x.x` != `Bar { .. }.x`
219+
/// }
220+
///
221+
/// struct Bar {
222+
/// x: i32 = 101,
223+
/// }
224+
/// ```
225+
///
226+
/// {{produces}}
227+
///
228+
/// ### Explanation
229+
///
230+
/// Defaulting a field to a value different to that field's type already defined default can
231+
/// easily lead to confusion due to diverging behavior. Acknowleding that there can be reasons
232+
/// for one to write an API that does this, this is not outright rejected by the compiler,
233+
/// merely linted against.
234+
pub DEFAULT_FIELD_OVERRIDES_DEFAULT_FIELD,
235+
Deny,
236+
"detect default field value that should use the type's default field values",
237+
@feature_gate = default_field_values;
238+
}
239+
240+
pub(crate) struct DefaultFieldOverride;
241+
242+
impl_lint_pass!(DefaultFieldOverride => [DEFAULT_FIELD_OVERRIDES_DEFAULT_FIELD]);
243+
244+
impl DefaultFieldOverride {
245+
fn lint_variant(&mut self, cx: &LateContext<'_>, data: &hir::VariantData<'_>) {
246+
if !cx.tcx.features().default_field_values() {
247+
return;
248+
}
249+
let hir::VariantData::Struct { fields, recovered: Recovered::No } = data else {
250+
return;
251+
};
252+
253+
for default in fields.iter().filter_map(|f| f.default) {
254+
let body = cx.tcx.hir().body(default.body);
255+
let hir::ExprKind::Struct(hir::QPath::Resolved(_, path), fields, _) = body.value.kind
256+
else {
257+
continue;
258+
};
259+
let Res::Def(
260+
DefKind::Variant
261+
| DefKind::Struct
262+
| DefKind::Ctor(CtorOf::Variant | CtorOf::Struct, ..),
263+
def_id,
264+
) = path.res
265+
else {
266+
continue;
267+
};
268+
let fields_set: FxHashSet<_> = fields.iter().map(|f| f.ident.name).collect();
269+
let variant = cx.tcx.expect_variant_res(path.res);
270+
let mut to_lint = vec![];
271+
let mut defs = vec![];
272+
273+
for field in &variant.fields {
274+
if fields_set.contains(&field.name) {
275+
for f in fields {
276+
if f.ident.name == field.name
277+
&& let Some(default) = field.value
278+
{
279+
to_lint.push((f.expr.span, f.ident.name));
280+
defs.push(cx.tcx.def_span(default));
281+
}
282+
}
283+
}
284+
}
285+
286+
if to_lint.is_empty() {
287+
continue;
288+
}
289+
cx.tcx.node_span_lint(
290+
DEFAULT_FIELD_OVERRIDES_DEFAULT_FIELD,
291+
body.value.hir_id,
292+
to_lint.iter().map(|&(span, _)| span).collect::<Vec<_>>(),
293+
|diag| {
294+
diag.primary_message("default field overrides that field's type's default");
295+
diag.span_label(path.span, "when constructing this value");
296+
let type_name = cx.tcx.item_name(def_id);
297+
for (span, name) in to_lint {
298+
diag.span_label(
299+
span,
300+
format!(
301+
"this overrides the default of field `{name}` in `{type_name}`"
302+
),
303+
);
304+
}
305+
let mut overriden_spans: MultiSpan = defs.clone().into();
306+
overriden_spans.push_span_label(cx.tcx.def_span(def_id), "");
307+
diag.span_note(
308+
overriden_spans,
309+
format!(
310+
"{this} field's default value in `{type_name}` is overriden",
311+
this = if defs.len() == 1 { "this" } else { "these" }
312+
),
313+
);
314+
},
315+
);
316+
}
317+
}
318+
}
319+
320+
impl<'tcx> LateLintPass<'tcx> for DefaultFieldOverride {
321+
fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) {
322+
let hir::ItemKind::Struct(data, _) = item.kind else { return };
323+
self.lint_variant(cx, &data);
324+
}
325+
fn check_variant(&mut self, cx: &LateContext<'_>, variant: &hir::Variant<'_>) {
326+
self.lint_variant(cx, &variant.data);
327+
}
328+
}

compiler/rustc_lint/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ use async_closures::AsyncClosureUsage;
8686
use async_fn_in_trait::AsyncFnInTrait;
8787
use builtin::*;
8888
use dangling::*;
89-
use default_could_be_derived::DefaultCouldBeDerived;
89+
use default_could_be_derived::{DefaultCouldBeDerived, DefaultFieldOverride};
9090
use deref_into_dyn_supertrait::*;
9191
use drop_forget_useless::*;
9292
use enum_intrinsics_non_enums::EnumIntrinsicsNonEnums;
@@ -191,7 +191,8 @@ late_lint_methods!(
191191
BuiltinCombinedModuleLateLintPass,
192192
[
193193
ForLoopsOverFallibles: ForLoopsOverFallibles,
194-
DefaultCouldBeDerived: DefaultCouldBeDerived::default(),
194+
DefaultCouldBeDerived: DefaultCouldBeDerived,
195+
DefaultFieldOverride: DefaultFieldOverride,
195196
DerefIntoDynSupertrait: DerefIntoDynSupertrait,
196197
DropForgetUseless: DropForgetUseless,
197198
ImproperCTypesDeclarations: ImproperCTypesDeclarations,
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#![feature(default_field_values)]
2+
#![allow(dead_code)]
3+
4+
mod a {
5+
pub struct Foo {
6+
pub something: i32 = 2,
7+
}
8+
9+
pub struct Bar {
10+
pub foo: Foo = Foo { something: 10 }, //~ ERROR default field overrides that field's type's default
11+
}
12+
}
13+
14+
mod b {
15+
pub enum Foo {
16+
X {
17+
something: i32 = 2,
18+
}
19+
}
20+
21+
pub enum Bar {
22+
Y {
23+
foo: Foo = Foo::X { something: 10 }, //~ ERROR default field overrides that field's type's default
24+
}
25+
}
26+
}
27+
28+
fn main() {
29+
let x = a::Bar { .. };
30+
let y = a::Foo { .. };
31+
assert_eq!(x.foo.something, y.something);
32+
let x = b::Bar::Y { .. };
33+
let y = b::Foo::X { .. };
34+
match (x, y) {
35+
(b::Bar::Y { foo: b::Foo::X { something: a} }, b::Foo::X { something:b }) if a == b=> {}
36+
_ => panic!(),
37+
}
38+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
error: default field overrides that field's type's default
2+
--> $DIR/diverging-default-field-values-in-field.rs:10:41
3+
|
4+
LL | pub foo: Foo = Foo { something: 10 },
5+
| --- ^^ this overrides the default of field `something` in `Foo`
6+
| |
7+
| when constructing this value
8+
|
9+
note: this field's default value in `Foo` is overriden
10+
--> $DIR/diverging-default-field-values-in-field.rs:6:30
11+
|
12+
LL | pub struct Foo {
13+
| --------------
14+
LL | pub something: i32 = 2,
15+
| ^
16+
= note: `#[deny(default_field_overrides_default_field)]` on by default
17+
18+
error: default field overrides that field's type's default
19+
--> $DIR/diverging-default-field-values-in-field.rs:23:44
20+
|
21+
LL | foo: Foo = Foo::X { something: 10 },
22+
| ------ ^^ this overrides the default of field `something` in `X`
23+
| |
24+
| when constructing this value
25+
|
26+
note: this field's default value in `X` is overriden
27+
--> $DIR/diverging-default-field-values-in-field.rs:17:30
28+
|
29+
LL | X {
30+
| -
31+
LL | something: i32 = 2,
32+
| ^
33+
34+
error: aborting due to 2 previous errors
35+

0 commit comments

Comments
 (0)