Skip to content

Commit

Permalink
Detect missing fields with default values and suggest ..
Browse files Browse the repository at this point in the history
When a struct definition has default field values, and the use struct ctor has missing field, if all those missing fields have defaults suggest `..`:

```
error[E0063]: missing fields `field1` and `field2` in initializer of `S`
  --> $DIR/non-exhaustive-ctor.rs:16:13
   |
LL |     let _ = S { field: () };
   |             ^ missing `field1` and `field2`
   |
help: all remaining fields have defaults, use `..`
   |
LL |     let _ = S { field: (), .. };
   |                          ++++
```
  • Loading branch information
estebank committed Jan 20, 2025
1 parent 6a64e3b commit 86e6877
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 0 deletions.
31 changes: 31 additions & 0 deletions compiler/rustc_hir_typeck/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2349,6 +2349,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
self.report_missing_fields(
adt_ty,
path_span,
expr.span,
remaining_fields,
variant,
hir_fields,
Expand Down Expand Up @@ -2386,6 +2387,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
&self,
adt_ty: Ty<'tcx>,
span: Span,
full_span: Span,
remaining_fields: UnordMap<Ident, (FieldIdx, &ty::FieldDef)>,
variant: &'tcx ty::VariantDef,
hir_fields: &'tcx [hir::ExprField<'tcx>],
Expand Down Expand Up @@ -2425,6 +2427,35 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
);
err.span_label(span, format!("missing {remaining_fields_names}{truncated_fields_error}"));

if remaining_fields.items().all(|(_, (_, field))| field.value.is_some()) {
let msg = format!(
"all remaining fields have default values, {you_can} use those values with `..`",
you_can = if self.tcx.features().default_field_values() {
"you can"
} else if self.tcx.sess.is_nightly_build() {
"if you added `#![feature(default_field_values)]` to your crate you could"
} else {
"if your crate were nightly-only, you could add \
`#![feature(default_field_values)]` to your crate and"
},
);
if let Some(hir_field) = hir_fields.last() {
err.span_suggestion_verbose(
hir_field.span.shrink_to_hi(),
msg,
", ..".to_string(),
Applicability::MachineApplicable,
);
} else if hir_fields.is_empty() {
err.span_suggestion_verbose(
span.shrink_to_hi().with_hi(full_span.hi()),
msg,
" { .. }".to_string(),
Applicability::MachineApplicable,
);
}
}

if let Some(hir_field) = hir_fields.last() {
self.suggest_fru_from_range_and_emit(hir_field, variant, args, err);
} else {
Expand Down
26 changes: 26 additions & 0 deletions tests/ui/structs/default-field-values/non-exhaustive-ctor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#![feature(default_field_values)]
use m::S;

mod m {
pub struct S {
pub field: () = (),
pub field1: Priv = Priv,
pub field2: Priv = Priv,
}
struct Priv;
}

fn main() {
let _ = S { .. }; // ok
let _ = S { field: (), .. }; // ok
let _ = S { };
//~^ ERROR missing fields `field`, `field1` and `field2`
let _ = S { field: () };
//~^ ERROR missing fields `field1` and `field2`
let _ = S { field: (), field1: m::Priv };
//~^ ERROR missing field `field2`
//~| ERROR unit struct `Priv` is private
let _ = S { field: (), field1: m::Priv, field2: m::Priv };
//~^ ERROR unit struct `Priv` is private
//~| ERROR unit struct `Priv` is private
}
73 changes: 73 additions & 0 deletions tests/ui/structs/default-field-values/non-exhaustive-ctor.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
error[E0603]: unit struct `Priv` is private
--> $DIR/non-exhaustive-ctor.rs:20:39
|
LL | let _ = S { field: (), field1: m::Priv };
| ^^^^ private unit struct
|
note: the unit struct `Priv` is defined here
--> $DIR/non-exhaustive-ctor.rs:10:5
|
LL | struct Priv;
| ^^^^^^^^^^^^

error[E0603]: unit struct `Priv` is private
--> $DIR/non-exhaustive-ctor.rs:23:39
|
LL | let _ = S { field: (), field1: m::Priv, field2: m::Priv };
| ^^^^ private unit struct
|
note: the unit struct `Priv` is defined here
--> $DIR/non-exhaustive-ctor.rs:10:5
|
LL | struct Priv;
| ^^^^^^^^^^^^

error[E0603]: unit struct `Priv` is private
--> $DIR/non-exhaustive-ctor.rs:23:56
|
LL | let _ = S { field: (), field1: m::Priv, field2: m::Priv };
| ^^^^ private unit struct
|
note: the unit struct `Priv` is defined here
--> $DIR/non-exhaustive-ctor.rs:10:5
|
LL | struct Priv;
| ^^^^^^^^^^^^

error[E0063]: missing fields `field`, `field1` and `field2` in initializer of `S`
--> $DIR/non-exhaustive-ctor.rs:16:13
|
LL | let _ = S { };
| ^ missing `field`, `field1` and `field2`
|
help: all remaining fields have default values, you can use those values with `..`
|
LL | let _ = S { .. };
| ~~~~~~

error[E0063]: missing fields `field1` and `field2` in initializer of `S`
--> $DIR/non-exhaustive-ctor.rs:18:13
|
LL | let _ = S { field: () };
| ^ missing `field1` and `field2`
|
help: all remaining fields have default values, you can use those values with `..`
|
LL | let _ = S { field: (), .. };
| ++++

error[E0063]: missing field `field2` in initializer of `S`
--> $DIR/non-exhaustive-ctor.rs:20:13
|
LL | let _ = S { field: (), field1: m::Priv };
| ^ missing `field2`
|
help: all remaining fields have default values, you can use those values with `..`
|
LL | let _ = S { field: (), field1: m::Priv, .. };
| ++++

error: aborting due to 6 previous errors

Some errors have detailed explanations: E0063, E0603.
For more information about an error, try `rustc --explain E0063`.

0 comments on commit 86e6877

Please sign in to comment.