Rollup merge of #153227 - kpreid:struct-missing-field, r=estebank

Don’t report missing fields in struct exprs with syntax errors.

@Noratrieb [told me](https://internals.rust-lang.org/t/custom-cargo-command-to-show-only-errors-avoid-setting-rustflags-every-time/24032/7?u=kpreid) that “it is a bug if this recovery causes follow-up errors that would not be there if the user fixed the first error.” So, here’s a contribution to hide a follow-up error that annoyed me recently.

Specifically, if the user writes a struct literal with a syntax error, such as

```rust
StructName { foo: 1 bar: 2 }
```

the compiler will no longer report that the field `bar` is missing in addition to the syntax error.

This is my first time attempting any change to the parser or AST; please let me know if there is a better way to do what I’ve done here. ~~The part I’m least happy with is the blast radius of adding another field to `hir::ExprKind::Struct`, but this seems to be in line with the style of the rest of the code. (If this were my own code, I would consider changing `hir::ExprKind::Struct` to a nested struct, the same way it is in `ast::ExprKind`.)~~ The additional information is now stored as an additional variant of `ast::StructRest` / `hir::StructTailExpr`.

**Note to reviewers:** I recommend reviewing each commit separately, and in the case of the first one with indentation changes ignored.
This commit is contained in:
Jonathan Brouwer
2026-03-03 07:14:12 +01:00
committed by GitHub
28 changed files with 404 additions and 269 deletions
+6
View File
@@ -1724,6 +1724,12 @@ pub enum StructRest {
Rest(Span),
/// No trailing `..` or expression.
None,
/// No trailing `..` or expression, and also, a parse error occurred inside the struct braces.
///
/// This struct should be treated similarly to as if it had an `..` in it,
/// in particular rather than reporting missing fields, because the parse error
/// makes which fields the struct was intended to have not fully known.
NoneWithError(ErrorGuaranteed),
}
#[derive(Clone, Encodable, Decodable, Debug, Walkable)]
+5 -4
View File
@@ -340,12 +340,13 @@ pub(super) fn lower_expr_mut(&mut self, e: &Expr) -> hir::Expr<'hir> {
self.arena.alloc_from_iter(fields.iter().map(|&ident| self.lower_ident(ident))),
),
ExprKind::Struct(se) => {
let rest = match &se.rest {
StructRest::Base(e) => hir::StructTailExpr::Base(self.lower_expr(e)),
let rest = match se.rest {
StructRest::Base(ref e) => hir::StructTailExpr::Base(self.lower_expr(e)),
StructRest::Rest(sp) => {
hir::StructTailExpr::DefaultFields(self.lower_span(*sp))
hir::StructTailExpr::DefaultFields(self.lower_span(sp))
}
StructRest::None => hir::StructTailExpr::None,
StructRest::NoneWithError(guar) => hir::StructTailExpr::NoneWithError(guar),
};
hir::ExprKind::Struct(
self.arena.alloc(self.lower_qpath(
@@ -1435,7 +1436,7 @@ fn destructure_assign_mut(
Some(self.lower_span(e.span))
}
StructRest::Rest(span) => Some(self.lower_span(*span)),
StructRest::None => None,
StructRest::None | StructRest::NoneWithError(_) => None,
};
let struct_pat = hir::PatKind::Struct(qpath, field_pats, fields_omitted);
return self.pat_without_dbm(lhs.span, struct_pat);
@@ -162,7 +162,7 @@ fn print_expr_struct(
self.word("{");
let has_rest = match rest {
ast::StructRest::Base(_) | ast::StructRest::Rest(_) => true,
ast::StructRest::None => false,
ast::StructRest::None | ast::StructRest::NoneWithError(_) => false,
};
if fields.is_empty() && !has_rest {
self.word("}");
+9 -1
View File
@@ -2658,7 +2658,9 @@ pub fn can_have_side_effects(&self) -> bool {
ExprKind::Struct(_, fields, init) => {
let init_side_effects = match init {
StructTailExpr::Base(init) => init.can_have_side_effects(),
StructTailExpr::DefaultFields(_) | StructTailExpr::None => false,
StructTailExpr::DefaultFields(_)
| StructTailExpr::None
| StructTailExpr::NoneWithError(_) => false,
};
fields.iter().map(|field| field.expr).any(|e| e.can_have_side_effects())
|| init_side_effects
@@ -2950,6 +2952,12 @@ pub enum StructTailExpr<'hir> {
/// fields' default values will be used to populate any fields not explicitly mentioned:
/// `Foo { .. }`.
DefaultFields(Span),
/// No trailing `..` was written, and also, a parse error occurred inside the struct braces.
///
/// This struct should be treated similarly to as if it had an `..` in it,
/// in particular rather than reporting missing fields, because the parse error
/// makes which fields the struct was intended to have not fully known.
NoneWithError(ErrorGuaranteed),
}
/// Represents an optionally `Self`-qualified value/type path or associated extension.
+3 -1
View File
@@ -830,7 +830,9 @@ pub fn walk_expr<'v, V: Visitor<'v>>(visitor: &mut V, expression: &'v Expr<'v>)
walk_list!(visitor, visit_expr_field, fields);
match optional_base {
StructTailExpr::Base(base) => try_visit!(visitor.visit_expr(base)),
StructTailExpr::None | StructTailExpr::DefaultFields(_) => {}
StructTailExpr::None
| StructTailExpr::NoneWithError(_)
| StructTailExpr::DefaultFields(_) => {}
}
}
ExprKind::Tup(subexpressions) => {
+1
View File
@@ -1314,6 +1314,7 @@ fn print_expr_struct(
self.end(ib);
}
hir::StructTailExpr::None => {}
hir::StructTailExpr::NoneWithError(_) => {}
}
self.space();
self.word("}");
+232 -205
View File
@@ -2000,229 +2000,256 @@ fn check_expr_struct_fields(
return;
}
if let hir::StructTailExpr::DefaultFields(span) = *base_expr {
let mut missing_mandatory_fields = Vec::new();
let mut missing_optional_fields = Vec::new();
for f in &variant.fields {
let ident = self.tcx.adjust_ident(f.ident(self.tcx), variant.def_id);
if let Some(_) = remaining_fields.remove(&ident) {
if f.value.is_none() {
missing_mandatory_fields.push(ident);
} else {
missing_optional_fields.push(ident);
match *base_expr {
hir::StructTailExpr::DefaultFields(span) => {
let mut missing_mandatory_fields = Vec::new();
let mut missing_optional_fields = Vec::new();
for f in &variant.fields {
let ident = self.tcx.adjust_ident(f.ident(self.tcx), variant.def_id);
if let Some(_) = remaining_fields.remove(&ident) {
if f.value.is_none() {
missing_mandatory_fields.push(ident);
} else {
missing_optional_fields.push(ident);
}
}
}
}
if !self.tcx.features().default_field_values() {
let sugg = self.tcx.crate_level_attribute_injection_span();
self.dcx().emit_err(BaseExpressionDoubleDot {
span: span.shrink_to_hi(),
// We only mention enabling the feature if this is a nightly rustc *and* the
// expression would make sense with the feature enabled.
default_field_values_suggestion: if self.tcx.sess.is_nightly_build()
&& missing_mandatory_fields.is_empty()
&& !missing_optional_fields.is_empty()
{
Some(sugg)
} else {
None
},
add_expr: if !missing_mandatory_fields.is_empty()
|| !missing_optional_fields.is_empty()
{
Some(BaseExpressionDoubleDotAddExpr { span: span.shrink_to_hi() })
} else {
None
},
remove_dots: if missing_mandatory_fields.is_empty()
&& missing_optional_fields.is_empty()
{
Some(BaseExpressionDoubleDotRemove { span })
} else {
None
},
});
return;
}
if variant.fields.is_empty() {
let mut err = self.dcx().struct_span_err(
span,
format!(
"`{adt_ty}` has no fields, `..` needs at least one default field in the \
struct definition",
),
);
err.span_label(path_span, "this type has no fields");
err.emit();
}
if !missing_mandatory_fields.is_empty() {
let s = pluralize!(missing_mandatory_fields.len());
let fields = listify(&missing_mandatory_fields, |f| format!("`{f}`")).unwrap();
self.dcx()
.struct_span_err(
span.shrink_to_lo(),
format!("missing field{s} {fields} in initializer"),
)
.with_span_label(
span.shrink_to_lo(),
"fields that do not have a defaulted value must be provided explicitly",
)
.emit();
return;
}
let fru_tys = match adt_ty.kind() {
ty::Adt(adt, args) if adt.is_struct() => variant
.fields
.iter()
.map(|f| self.normalize(span, f.ty(self.tcx, args)))
.collect(),
ty::Adt(adt, args) if adt.is_enum() => variant
.fields
.iter()
.map(|f| self.normalize(span, f.ty(self.tcx, args)))
.collect(),
_ => {
self.dcx().emit_err(FunctionalRecordUpdateOnNonStruct { span });
if !self.tcx.features().default_field_values() {
let sugg = self.tcx.crate_level_attribute_injection_span();
self.dcx().emit_err(BaseExpressionDoubleDot {
span: span.shrink_to_hi(),
// We only mention enabling the feature if this is a nightly rustc *and* the
// expression would make sense with the feature enabled.
default_field_values_suggestion: if self.tcx.sess.is_nightly_build()
&& missing_mandatory_fields.is_empty()
&& !missing_optional_fields.is_empty()
{
Some(sugg)
} else {
None
},
add_expr: if !missing_mandatory_fields.is_empty()
|| !missing_optional_fields.is_empty()
{
Some(BaseExpressionDoubleDotAddExpr { span: span.shrink_to_hi() })
} else {
None
},
remove_dots: if missing_mandatory_fields.is_empty()
&& missing_optional_fields.is_empty()
{
Some(BaseExpressionDoubleDotRemove { span })
} else {
None
},
});
return;
}
};
self.typeck_results.borrow_mut().fru_field_types_mut().insert(expr.hir_id, fru_tys);
} else if let hir::StructTailExpr::Base(base_expr) = base_expr {
// FIXME: We are currently creating two branches here in order to maintain
// consistency. But they should be merged as much as possible.
let fru_tys = if self.tcx.features().type_changing_struct_update() {
if adt.is_struct() {
// Make some fresh generic parameters for our ADT type.
let fresh_args = self.fresh_args_for_item(base_expr.span, adt.did());
// We do subtyping on the FRU fields first, so we can
// learn exactly what types we expect the base expr
// needs constrained to be compatible with the struct
// type we expect from the expectation value.
let fru_tys = variant
.fields
.iter()
.map(|f| {
let fru_ty = self
.normalize(expr.span, self.field_ty(base_expr.span, f, fresh_args));
let ident = self.tcx.adjust_ident(f.ident(self.tcx), variant.def_id);
if let Some(_) = remaining_fields.remove(&ident) {
let target_ty = self.field_ty(base_expr.span, f, args);
let cause = self.misc(base_expr.span);
match self.at(&cause, self.param_env).sup(
// We're already using inference variables for any params, and don't allow converting
// between different structs, so there is no way this ever actually defines an opaque type.
// Thus choosing `Yes` is fine.
DefineOpaqueTypes::Yes,
target_ty,
fru_ty,
) {
Ok(InferOk { obligations, value: () }) => {
self.register_predicates(obligations)
}
Err(_) => {
span_bug!(
cause.span,
"subtyping remaining fields of type changing FRU failed: {target_ty} != {fru_ty}: {}::{}",
variant.name,
ident.name,
);
}
}
}
self.resolve_vars_if_possible(fru_ty)
})
.collect();
// The use of fresh args that we have subtyped against
// our base ADT type's fields allows us to guide inference
// along so that, e.g.
// ```
// MyStruct<'a, F1, F2, const C: usize> {
// f: F1,
// // Other fields that reference `'a`, `F2`, and `C`
// }
//
// let x = MyStruct {
// f: 1usize,
// ..other_struct
// };
// ```
// will have the `other_struct` expression constrained to
// `MyStruct<'a, _, F2, C>`, as opposed to just `_`...
// This is important to allow coercions to happen in
// `other_struct` itself. See `coerce-in-base-expr.rs`.
let fresh_base_ty = Ty::new_adt(self.tcx, *adt, fresh_args);
self.check_expr_has_type_or_error(
base_expr,
self.resolve_vars_if_possible(fresh_base_ty),
|_| {},
if variant.fields.is_empty() {
let mut err = self.dcx().struct_span_err(
span,
format!(
"`{adt_ty}` has no fields, `..` needs at least one default field in \
the struct definition",
),
);
fru_tys
} else {
// Check the base_expr, regardless of a bad expected adt_ty, so we can get
// type errors on that expression, too.
self.check_expr(base_expr);
self.dcx().emit_err(FunctionalRecordUpdateOnNonStruct { span: base_expr.span });
return;
err.span_label(path_span, "this type has no fields");
err.emit();
}
} else {
self.check_expr_has_type_or_error(base_expr, adt_ty, |_| {
let base_ty = self.typeck_results.borrow().expr_ty(*base_expr);
let same_adt = matches!((adt_ty.kind(), base_ty.kind()),
(ty::Adt(adt, _), ty::Adt(base_adt, _)) if adt == base_adt);
if self.tcx.sess.is_nightly_build() && same_adt {
feature_err(
&self.tcx.sess,
sym::type_changing_struct_update,
base_expr.span,
"type changing struct updating is experimental",
if !missing_mandatory_fields.is_empty() {
let s = pluralize!(missing_mandatory_fields.len());
let fields = listify(&missing_mandatory_fields, |f| format!("`{f}`")).unwrap();
self.dcx()
.struct_span_err(
span.shrink_to_lo(),
format!("missing field{s} {fields} in initializer"),
)
.with_span_label(
span.shrink_to_lo(),
"fields that do not have a defaulted value must be provided explicitly",
)
.emit();
}
});
match adt_ty.kind() {
return;
}
let fru_tys = match adt_ty.kind() {
ty::Adt(adt, args) if adt.is_struct() => variant
.fields
.iter()
.map(|f| self.normalize(expr.span, f.ty(self.tcx, args)))
.map(|f| self.normalize(span, f.ty(self.tcx, args)))
.collect(),
ty::Adt(adt, args) if adt.is_enum() => variant
.fields
.iter()
.map(|f| self.normalize(span, f.ty(self.tcx, args)))
.collect(),
_ => {
self.dcx().emit_err(FunctionalRecordUpdateOnNonStruct { span });
return;
}
};
self.typeck_results.borrow_mut().fru_field_types_mut().insert(expr.hir_id, fru_tys);
}
hir::StructTailExpr::Base(base_expr) => {
// FIXME: We are currently creating two branches here in order to maintain
// consistency. But they should be merged as much as possible.
let fru_tys = if self.tcx.features().type_changing_struct_update() {
if adt.is_struct() {
// Make some fresh generic parameters for our ADT type.
let fresh_args = self.fresh_args_for_item(base_expr.span, adt.did());
// We do subtyping on the FRU fields first, so we can
// learn exactly what types we expect the base expr
// needs constrained to be compatible with the struct
// type we expect from the expectation value.
let fru_tys = variant
.fields
.iter()
.map(|f| {
let fru_ty = self.normalize(
expr.span,
self.field_ty(base_expr.span, f, fresh_args),
);
let ident =
self.tcx.adjust_ident(f.ident(self.tcx), variant.def_id);
if let Some(_) = remaining_fields.remove(&ident) {
let target_ty = self.field_ty(base_expr.span, f, args);
let cause = self.misc(base_expr.span);
match self.at(&cause, self.param_env).sup(
// We're already using inference variables for any params,
// and don't allow converting between different structs,
// so there is no way this ever actually defines an opaque
// type. Thus choosing `Yes` is fine.
DefineOpaqueTypes::Yes,
target_ty,
fru_ty,
) {
Ok(InferOk { obligations, value: () }) => {
self.register_predicates(obligations)
}
Err(_) => {
span_bug!(
cause.span,
"subtyping remaining fields of type changing FRU \
failed: {target_ty} != {fru_ty}: {}::{}",
variant.name,
ident.name,
);
}
}
}
self.resolve_vars_if_possible(fru_ty)
})
.collect();
// The use of fresh args that we have subtyped against
// our base ADT type's fields allows us to guide inference
// along so that, e.g.
// ```
// MyStruct<'a, F1, F2, const C: usize> {
// f: F1,
// // Other fields that reference `'a`, `F2`, and `C`
// }
//
// let x = MyStruct {
// f: 1usize,
// ..other_struct
// };
// ```
// will have the `other_struct` expression constrained to
// `MyStruct<'a, _, F2, C>`, as opposed to just `_`...
// This is important to allow coercions to happen in
// `other_struct` itself. See `coerce-in-base-expr.rs`.
let fresh_base_ty = Ty::new_adt(self.tcx, *adt, fresh_args);
self.check_expr_has_type_or_error(
base_expr,
self.resolve_vars_if_possible(fresh_base_ty),
|_| {},
);
fru_tys
} else {
// Check the base_expr, regardless of a bad expected adt_ty, so we can get
// type errors on that expression, too.
self.check_expr(base_expr);
self.dcx()
.emit_err(FunctionalRecordUpdateOnNonStruct { span: base_expr.span });
return;
}
}
};
self.typeck_results.borrow_mut().fru_field_types_mut().insert(expr.hir_id, fru_tys);
} else if adt_kind != AdtKind::Union
&& !remaining_fields.is_empty()
//~ non_exhaustive already reported, which will only happen for extern modules
&& !variant.field_list_has_applicable_non_exhaustive()
{
debug!(?remaining_fields);
let private_fields: Vec<&ty::FieldDef> = variant
.fields
.iter()
.filter(|field| !field.vis.is_accessible_from(tcx.parent_module(expr.hir_id), tcx))
.collect();
} else {
self.check_expr_has_type_or_error(base_expr, adt_ty, |_| {
let base_ty = self.typeck_results.borrow().expr_ty(base_expr);
let same_adt = matches!((adt_ty.kind(), base_ty.kind()),
(ty::Adt(adt, _), ty::Adt(base_adt, _)) if adt == base_adt);
if self.tcx.sess.is_nightly_build() && same_adt {
feature_err(
&self.tcx.sess,
sym::type_changing_struct_update,
base_expr.span,
"type changing struct updating is experimental",
)
.emit();
}
});
match adt_ty.kind() {
ty::Adt(adt, args) if adt.is_struct() => variant
.fields
.iter()
.map(|f| self.normalize(expr.span, f.ty(self.tcx, args)))
.collect(),
_ => {
self.dcx().emit_err(FunctionalRecordUpdateOnNonStruct {
span: base_expr.span,
});
return;
}
}
};
self.typeck_results.borrow_mut().fru_field_types_mut().insert(expr.hir_id, fru_tys);
}
rustc_hir::StructTailExpr::NoneWithError(ErrorGuaranteed { .. }) => {
// If parsing the struct recovered from a syntax error, do not report missing
// fields. This prevents spurious errors when a field is intended to be present
// but a preceding syntax error caused it not to be parsed. For example, if a
// struct type `StructName` has fields `foo` and `bar`, then
// StructName { foo(), bar: 2 }
// will not successfully parse a field `foo`, but we will not mention that,
// since the syntax error has already been reported.
}
rustc_hir::StructTailExpr::None => {
if adt_kind != AdtKind::Union
&& !remaining_fields.is_empty()
//~ non_exhaustive already reported, which will only happen for extern modules
&& !variant.field_list_has_applicable_non_exhaustive()
{
debug!(?remaining_fields);
if !private_fields.is_empty() {
self.report_private_fields(
adt_ty,
path_span,
expr.span,
private_fields,
hir_fields,
);
} else {
self.report_missing_fields(
adt_ty,
path_span,
expr.span,
remaining_fields,
variant,
hir_fields,
args,
);
// Report missing fields.
let private_fields: Vec<&ty::FieldDef> = variant
.fields
.iter()
.filter(|field| {
!field.vis.is_accessible_from(tcx.parent_module(expr.hir_id), tcx)
})
.collect();
if !private_fields.is_empty() {
self.report_private_fields(
adt_ty,
path_span,
expr.span,
private_fields,
hir_fields,
);
} else {
self.report_missing_fields(
adt_ty,
path_span,
expr.span,
remaining_fields,
variant,
hir_fields,
args,
);
}
}
}
}
}
@@ -673,7 +673,9 @@ fn walk_struct_expr<'hir>(
let with_expr = match *opt_with {
hir::StructTailExpr::Base(w) => &*w,
hir::StructTailExpr::DefaultFields(_) | hir::StructTailExpr::None => {
hir::StructTailExpr::DefaultFields(_)
| hir::StructTailExpr::None
| hir::StructTailExpr::NoneWithError(_) => {
return Ok(());
}
};
+6 -2
View File
@@ -632,7 +632,8 @@ fn make_mirror_unadjusted(&mut self, expr: &'tcx hir::Expr<'tcx>) -> Expr<'tcx>
.collect(),
)
}
hir::StructTailExpr::None => AdtExprBase::None,
hir::StructTailExpr::None
| hir::StructTailExpr::NoneWithError(_) => AdtExprBase::None,
},
}))
}
@@ -669,7 +670,10 @@ fn make_mirror_unadjusted(&mut self, expr: &'tcx hir::Expr<'tcx>) -> Expr<'tcx>
hir::StructTailExpr::Base(base) => {
span_bug!(base.span, "unexpected res: {:?}", res);
}
hir::StructTailExpr::None => AdtExprBase::None,
hir::StructTailExpr::None
| hir::StructTailExpr::NoneWithError(_) => {
AdtExprBase::None
}
},
}))
}
+13
View File
@@ -3842,6 +3842,15 @@ pub(super) fn parse_struct_fields(
recovered_async = Some(guar);
}
// If we encountered an error which we are recovering from, treat the struct
// as if it has a `..` in it, because we dont know what fields the user
// might have *intended* it to have.
//
// This assignment will be overwritten if we actually parse a `..` later.
//
// (Note that this code is duplicated between here and below in comma parsing.
base = ast::StructRest::NoneWithError(guar);
// If the next token is a comma, then try to parse
// what comes next as additional fields, rather than
// bailing out until next `}`.
@@ -3892,6 +3901,10 @@ pub(super) fn parse_struct_fields(
} else if let Some(f) = field_ident(self, guar) {
fields.push(f);
}
// See comment above on this same assignment inside of field parsing.
base = ast::StructRest::NoneWithError(guar);
self.recover_stmt_(SemiColonMode::Comma, BlockMode::Ignore);
let _ = self.eat(exp!(Comma));
}
+1 -1
View File
@@ -1100,7 +1100,7 @@ fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
qpath.span(),
);
}
hir::StructTailExpr::None => {
hir::StructTailExpr::None | hir::StructTailExpr::NoneWithError(_) => {
let mut failed_fields = vec![];
for field in fields {
let (hir_id, use_ctxt) = (field.hir_id, field.ident.span);
+1 -1
View File
@@ -5066,7 +5066,7 @@ fn resolve_expr(&mut self, expr: &'ast Expr, parent: Option<&'ast Expr>) {
match &se.rest {
StructRest::Base(expr) => self.visit_expr(expr),
StructRest::Rest(_span) => {}
StructRest::None => {}
StructRest::None | StructRest::NoneWithError(_) => {}
}
}
@@ -242,7 +242,7 @@ fn has_no_effect(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
!expr_ty_has_significant_drop(cx, expr)
&& fields.iter().all(|field| has_no_effect(cx, field.expr))
&& match &base {
StructTailExpr::None | StructTailExpr::DefaultFields(_) => true,
StructTailExpr::None | StructTailExpr::NoneWithError(_) | StructTailExpr::DefaultFields(_) => true,
StructTailExpr::Base(base) => has_no_effect(cx, base),
}
},
@@ -353,7 +353,7 @@ fn reduce_expression<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<Vec
} else {
let base = match base {
StructTailExpr::Base(base) => Some(base),
StructTailExpr::None | StructTailExpr::DefaultFields(_) => None,
StructTailExpr::None | StructTailExpr::NoneWithError(_) | StructTailExpr::DefaultFields(_) => None,
};
Some(fields.iter().map(|f| &f.expr).chain(base).map(Deref::deref).collect())
}
@@ -673,7 +673,7 @@ macro_rules! kind {
bind!(self, qpath, fields);
let base = OptionPat::new(match base {
StructTailExpr::Base(base) => Some(self.bind("base", base)),
StructTailExpr::None | StructTailExpr::DefaultFields(_) => None,
StructTailExpr::None | StructTailExpr::NoneWithError(_) | StructTailExpr::DefaultFields(_) => None,
});
kind!("Struct({qpath}, {fields}, {base})");
self.qpath(qpath, &expr.name, expr.value.hir_id);
+1 -1
View File
@@ -1809,7 +1809,7 @@ enum StructLitField<'a> {
match struct_rest {
ast::StructRest::Base(expr) => Some(StructLitField::Base(&**expr)),
ast::StructRest::Rest(span) => Some(StructLitField::Rest(*span)),
ast::StructRest::None => None,
ast::StructRest::None | ast::StructRest::NoneWithError(_) => None,
}
.into_iter(),
);
-1
View File
@@ -16,7 +16,6 @@ fn pz(v: V3) {
let _ = V3 { z: 0.0, ... };
//~^ ERROR expected identifier
//~| ERROR missing fields `x` and `y` in initializer of `V3`
let V3 { z: val, ... } = v;
//~^ ERROR expected field pattern
+2 -9
View File
@@ -31,7 +31,7 @@ LL | let _ = V3 { z: 0.0, ... };
| while parsing this struct
error: expected field pattern, found `...`
--> $DIR/issue-102806.rs:21:22
--> $DIR/issue-102806.rs:20:22
|
LL | let V3 { z: val, ... } = v;
| ^^^
@@ -42,12 +42,5 @@ LL - let V3 { z: val, ... } = v;
LL + let V3 { z: val, .. } = v;
|
error[E0063]: missing fields `x` and `y` in initializer of `V3`
--> $DIR/issue-102806.rs:17:13
|
LL | let _ = V3 { z: 0.0, ... };
| ^^ missing `x` and `y`
error: aborting due to 4 previous errors
error: aborting due to 5 previous errors
For more information about this error, try `rustc --explain E0063`.
-1
View File
@@ -7,6 +7,5 @@ fn main() {
let bar = 1.5f32;
let _ = Foo { bar.into(), bat: -1, . };
//~^ ERROR expected one of
//~| ERROR missing fields `bar` and `baz` in initializer of `Foo`
//~| ERROR expected identifier, found `.`
}
+1 -7
View File
@@ -37,12 +37,6 @@ error[E0063]: missing field `bat` in initializer of `Foo`
LL | let _ = Foo { bar: .5, baz: 42 };
| ^^^ missing `bat`
error[E0063]: missing fields `bar` and `baz` in initializer of `Foo`
--> $DIR/issue-52496.rs:8:13
|
LL | let _ = Foo { bar.into(), bat: -1, . };
| ^^^ missing `bar` and `baz`
error: aborting due to 5 previous errors
error: aborting due to 4 previous errors
For more information about this error, try `rustc --explain E0063`.
@@ -7,5 +7,4 @@ struct S {
let a = S { foo: (), bar: () };
let b = S { foo: (), with a };
//~^ ERROR expected one of `,`, `:`, or `}`, found `a`
//~| ERROR missing field `bar` in initializer of `S`
}
@@ -7,12 +7,5 @@ LL | let b = S { foo: (), with a };
| | while parsing this struct field
| while parsing this struct
error[E0063]: missing field `bar` in initializer of `S`
--> $DIR/removed-syntax-with-2.rs:8:13
|
LL | let b = S { foo: (), with a };
| ^ missing `bar`
error: aborting due to 1 previous error
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0063`.
@@ -5,5 +5,4 @@ fn main() {
//~^ ERROR expected identifier, found `0`
//~| ERROR expected identifier, found `1`
//~| ERROR expected identifier, found `2`
//~| ERROR missing fields `0`, `1` and `2` in initializer of `Rgb`
}
@@ -22,12 +22,5 @@ LL | let _ = Rgb { 0, 1, 2 };
| |
| while parsing this struct
error[E0063]: missing fields `0`, `1` and `2` in initializer of `Rgb`
--> $DIR/struct-field-numeric-shorthand.rs:4:13
|
LL | let _ = Rgb { 0, 1, 2 };
| ^^^ missing `0`, `1` and `2`
error: aborting due to 3 previous errors
error: aborting due to 4 previous errors
For more information about this error, try `rustc --explain E0063`.
@@ -0,0 +1,38 @@
// Check that a syntax error inside a struct literal does not also report missing fields,
// because the field might be present but hidden by the syntax error.
//
// The stderr for this test should contain ONLY one syntax error per struct literal,
// and not any errors about missing fields.
struct Foo { a: isize, b: isize }
fn make_a() -> isize { 1234 }
fn expr_wrong_separator() {
let f = Foo { a: make_a(); b: 2 }; //~ ERROR found `;`
}
fn expr_missing_separator() {
let f = Foo { a: make_a() b: 2 }; //~ ERROR found `b`
}
fn expr_rest_trailing_comma() {
let f = Foo { a: make_a(), ..todo!(), }; //~ ERROR cannot use a comma
}
fn expr_missing_field_name() {
let f = Foo { make_a(), b: 2, }; //~ ERROR found `(`
}
fn pat_wrong_separator(Foo { a; b }: Foo) { //~ ERROR expected `,`
let _ = (a, b);
}
fn pat_missing_separator(Foo { a b }: Foo) { //~ ERROR expected `,`
let _ = (a, b);
}
fn pat_rest_trailing_comma(Foo { a, .., }: Foo) { //~ ERROR expected `}`, found `,`
}
fn main() {}
@@ -0,0 +1,74 @@
error: expected one of `,`, `.`, `?`, `}`, or an operator, found `;`
--> $DIR/syntax-error-not-missing-field.rs:12:30
|
LL | let f = Foo { a: make_a(); b: 2 };
| --- ^
| | |
| | expected one of `,`, `.`, `?`, `}`, or an operator
| | help: try adding a comma: `,`
| while parsing this struct
error: expected one of `,`, `.`, `?`, `}`, or an operator, found `b`
--> $DIR/syntax-error-not-missing-field.rs:16:31
|
LL | let f = Foo { a: make_a() b: 2 };
| --- -^ expected one of `,`, `.`, `?`, `}`, or an operator
| | |
| | help: try adding a comma: `,`
| while parsing this struct
error: cannot use a comma after the base struct
--> $DIR/syntax-error-not-missing-field.rs:20:32
|
LL | let f = Foo { a: make_a(), ..todo!(), };
| ^^^^^^^^^
|
= note: the base struct must always be the last field
help: remove this comma
|
LL - let f = Foo { a: make_a(), ..todo!(), };
LL + let f = Foo { a: make_a(), ..todo!() };
|
error: expected one of `,`, `:`, or `}`, found `(`
--> $DIR/syntax-error-not-missing-field.rs:24:25
|
LL | let f = Foo { make_a(), b: 2, };
| --- ------^ expected one of `,`, `:`, or `}`
| | |
| | while parsing this struct field
| while parsing this struct
|
help: try naming a field
|
LL | let f = Foo { make_a: make_a(), b: 2, };
| +++++++
error: expected `,`
--> $DIR/syntax-error-not-missing-field.rs:27:31
|
LL | fn pat_wrong_separator(Foo { a; b }: Foo) {
| --- ^
| |
| while parsing the fields for this pattern
error: expected `,`
--> $DIR/syntax-error-not-missing-field.rs:31:34
|
LL | fn pat_missing_separator(Foo { a b }: Foo) {
| --- ^
| |
| while parsing the fields for this pattern
error: expected `}`, found `,`
--> $DIR/syntax-error-not-missing-field.rs:35:39
|
LL | fn pat_rest_trailing_comma(Foo { a, .., }: Foo) {
| --^
| | |
| | expected `}`
| | help: remove this comma
| `..` must be at the end and cannot have a trailing comma
error: aborting due to 7 previous errors
@@ -7,7 +7,6 @@ pub struct Foo {
fn main() {
let _ = Foo {
//~^ ERROR missing field
first: true,
second: 25
//~^ ERROR expected one of
@@ -7,7 +7,6 @@ pub struct Foo {
fn main() {
let _ = Foo {
//~^ ERROR missing field
first: true
second: 25
//~^ ERROR expected one of
@@ -1,9 +1,8 @@
error: expected one of `,`, `.`, `?`, `}`, or an operator, found `second`
--> $DIR/struct-initializer-comma.rs:12:9
--> $DIR/struct-initializer-comma.rs:11:9
|
LL | let _ = Foo {
| --- while parsing this struct
LL |
LL | first: true
| -
| |
@@ -12,12 +11,5 @@ LL | first: true
LL | second: 25
| ^^^^^^ unexpected token
error[E0063]: missing field `second` in initializer of `Foo`
--> $DIR/struct-initializer-comma.rs:9:13
|
LL | let _ = Foo {
| ^^^ missing `second`
error: aborting due to 1 previous error
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0063`.