mirror of
https://github.com/rust-lang/rust.git
synced 2026-04-27 18:57:42 +03:00
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:
@@ -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)]
|
||||
|
||||
@@ -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("}");
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -1314,6 +1314,7 @@ fn print_expr_struct(
|
||||
self.end(ib);
|
||||
}
|
||||
hir::StructTailExpr::None => {}
|
||||
hir::StructTailExpr::NoneWithError(_) => {}
|
||||
}
|
||||
self.space();
|
||||
self.word("}");
|
||||
|
||||
@@ -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(());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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
|
||||
}
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -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 don’t 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));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(),
|
||||
);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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 `.`
|
||||
}
|
||||
|
||||
@@ -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`.
|
||||
|
||||
Reference in New Issue
Block a user