diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 026758958f04f..2367ecad880b6 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -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)] diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index 5b31c421bf346..4a2992038003c 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -340,12 +340,13 @@ impl<'hir> LoweringContext<'_, '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 @@ impl<'hir> LoweringContext<'_, 'hir> { 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); diff --git a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs index 7dce7f6d41954..9b4ff2b63bd45 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs @@ -162,7 +162,7 @@ impl<'a> State<'a> { 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("}"); diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index 373d61978439e..3f8a3f2c34476 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -2652,7 +2652,9 @@ impl Expr<'_> { 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 @@ -2944,6 +2946,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. diff --git a/compiler/rustc_hir/src/intravisit.rs b/compiler/rustc_hir/src/intravisit.rs index 21bcf53b5619f..98c7cf274e801 100644 --- a/compiler/rustc_hir/src/intravisit.rs +++ b/compiler/rustc_hir/src/intravisit.rs @@ -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) => { diff --git a/compiler/rustc_hir_pretty/src/lib.rs b/compiler/rustc_hir_pretty/src/lib.rs index fc6cc4e672517..2ca1cd21ee3bf 100644 --- a/compiler/rustc_hir_pretty/src/lib.rs +++ b/compiler/rustc_hir_pretty/src/lib.rs @@ -1303,6 +1303,7 @@ impl<'a> State<'a> { self.end(ib); } hir::StructTailExpr::None => {} + hir::StructTailExpr::NoneWithError(_) => {} } self.space(); self.word("}"); diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index f3ca04c6bbe27..bfbe93176a25c 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -1999,229 +1999,256 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { 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; } + } 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); + + // 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, + ); + } } - }; - 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(); - - 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, - ); } } } diff --git a/compiler/rustc_hir_typeck/src/expr_use_visitor.rs b/compiler/rustc_hir_typeck/src/expr_use_visitor.rs index 77ecfc2fb0bee..ca60129ac5d6a 100644 --- a/compiler/rustc_hir_typeck/src/expr_use_visitor.rs +++ b/compiler/rustc_hir_typeck/src/expr_use_visitor.rs @@ -673,7 +673,9 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx 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(()); } }; diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs index 3d94ee701f564..bd937f1ad9689 100644 --- a/compiler/rustc_mir_build/src/thir/cx/expr.rs +++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs @@ -632,7 +632,8 @@ impl<'tcx> ThirBuildCx<'tcx> { .collect(), ) } - hir::StructTailExpr::None => AdtExprBase::None, + hir::StructTailExpr::None + | hir::StructTailExpr::NoneWithError(_) => AdtExprBase::None, }, })) } @@ -669,7 +670,10 @@ impl<'tcx> ThirBuildCx<'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 + } }, })) } diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 44e8f13dd2364..c8027dc541b4d 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -3841,6 +3841,15 @@ impl<'a> Parser<'a> { 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 `}`. @@ -3891,6 +3900,10 @@ impl<'a> Parser<'a> { } 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)); } diff --git a/compiler/rustc_privacy/src/lib.rs b/compiler/rustc_privacy/src/lib.rs index 59fd908756b02..1bceb20d6fc78 100644 --- a/compiler/rustc_privacy/src/lib.rs +++ b/compiler/rustc_privacy/src/lib.rs @@ -1097,7 +1097,7 @@ impl<'tcx> Visitor<'tcx> for NamePrivacyVisitor<'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); diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index f72abaa37201e..0c15cf916761c 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -5063,7 +5063,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { match &se.rest { StructRest::Base(expr) => self.visit_expr(expr), StructRest::Rest(_span) => {} - StructRest::None => {} + StructRest::None | StructRest::NoneWithError(_) => {} } } diff --git a/src/tools/clippy/clippy_lints/src/no_effect.rs b/src/tools/clippy/clippy_lints/src/no_effect.rs index fdb8e1b475c1d..3620e5425a947 100644 --- a/src/tools/clippy/clippy_lints/src/no_effect.rs +++ b/src/tools/clippy/clippy_lints/src/no_effect.rs @@ -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 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()) } diff --git a/src/tools/clippy/clippy_lints/src/utils/author.rs b/src/tools/clippy/clippy_lints/src/utils/author.rs index d5191794b6b06..7ba28f95a719f 100644 --- a/src/tools/clippy/clippy_lints/src/utils/author.rs +++ b/src/tools/clippy/clippy_lints/src/utils/author.rs @@ -673,7 +673,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> { 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); diff --git a/src/tools/rustfmt/src/expr.rs b/src/tools/rustfmt/src/expr.rs index b79137c44427e..51b73351fe6a5 100644 --- a/src/tools/rustfmt/src/expr.rs +++ b/src/tools/rustfmt/src/expr.rs @@ -1809,7 +1809,7 @@ fn rewrite_struct_lit<'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(), ); diff --git a/tests/ui/parser/issues/issue-102806.rs b/tests/ui/parser/issues/issue-102806.rs index 5ee8c5c1e3de0..804e727272d4c 100644 --- a/tests/ui/parser/issues/issue-102806.rs +++ b/tests/ui/parser/issues/issue-102806.rs @@ -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 diff --git a/tests/ui/parser/issues/issue-102806.stderr b/tests/ui/parser/issues/issue-102806.stderr index cd447c6dec0d2..d4c899ed58114 100644 --- a/tests/ui/parser/issues/issue-102806.stderr +++ b/tests/ui/parser/issues/issue-102806.stderr @@ -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 5 previous errors +error: aborting due to 4 previous errors -For more information about this error, try `rustc --explain E0063`. diff --git a/tests/ui/parser/issues/issue-52496.rs b/tests/ui/parser/issues/issue-52496.rs index 05461f8b8c413..3e88a92aae9bf 100644 --- a/tests/ui/parser/issues/issue-52496.rs +++ b/tests/ui/parser/issues/issue-52496.rs @@ -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 `.` } diff --git a/tests/ui/parser/issues/issue-52496.stderr b/tests/ui/parser/issues/issue-52496.stderr index a97effb4e0cd8..da22f5ce13562 100644 --- a/tests/ui/parser/issues/issue-52496.stderr +++ b/tests/ui/parser/issues/issue-52496.stderr @@ -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`. diff --git a/tests/ui/parser/removed-syntax/removed-syntax-with-2.rs b/tests/ui/parser/removed-syntax/removed-syntax-with-2.rs index 451057c66a127..119afdcc75e36 100644 --- a/tests/ui/parser/removed-syntax/removed-syntax-with-2.rs +++ b/tests/ui/parser/removed-syntax/removed-syntax-with-2.rs @@ -7,5 +7,4 @@ fn main() { 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` } diff --git a/tests/ui/parser/removed-syntax/removed-syntax-with-2.stderr b/tests/ui/parser/removed-syntax/removed-syntax-with-2.stderr index e75c5bcd64319..7768aeccfad99 100644 --- a/tests/ui/parser/removed-syntax/removed-syntax-with-2.stderr +++ b/tests/ui/parser/removed-syntax/removed-syntax-with-2.stderr @@ -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 2 previous errors +error: aborting due to 1 previous error -For more information about this error, try `rustc --explain E0063`. diff --git a/tests/ui/parser/struct-field-numeric-shorthand.rs b/tests/ui/parser/struct-field-numeric-shorthand.rs index 645abd9c7192d..aa342eb02a916 100644 --- a/tests/ui/parser/struct-field-numeric-shorthand.rs +++ b/tests/ui/parser/struct-field-numeric-shorthand.rs @@ -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` } diff --git a/tests/ui/parser/struct-field-numeric-shorthand.stderr b/tests/ui/parser/struct-field-numeric-shorthand.stderr index bfb8a931b6406..9878cfbbdceb8 100644 --- a/tests/ui/parser/struct-field-numeric-shorthand.stderr +++ b/tests/ui/parser/struct-field-numeric-shorthand.stderr @@ -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 4 previous errors +error: aborting due to 3 previous errors -For more information about this error, try `rustc --explain E0063`. diff --git a/tests/ui/structs/syntax-error-not-missing-field.rs b/tests/ui/structs/syntax-error-not-missing-field.rs new file mode 100644 index 0000000000000..f1080d3908cf4 --- /dev/null +++ b/tests/ui/structs/syntax-error-not-missing-field.rs @@ -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() {} diff --git a/tests/ui/structs/syntax-error-not-missing-field.stderr b/tests/ui/structs/syntax-error-not-missing-field.stderr new file mode 100644 index 0000000000000..d781a41300f36 --- /dev/null +++ b/tests/ui/structs/syntax-error-not-missing-field.stderr @@ -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 + diff --git a/tests/ui/suggestions/struct-initializer-comma.fixed b/tests/ui/suggestions/struct-initializer-comma.fixed index 556bfbca58df4..9bd9434a3b59d 100644 --- a/tests/ui/suggestions/struct-initializer-comma.fixed +++ b/tests/ui/suggestions/struct-initializer-comma.fixed @@ -7,7 +7,6 @@ pub struct Foo { fn main() { let _ = Foo { - //~^ ERROR missing field first: true, second: 25 //~^ ERROR expected one of diff --git a/tests/ui/suggestions/struct-initializer-comma.rs b/tests/ui/suggestions/struct-initializer-comma.rs index 7b93bf187b870..4b225df08df0a 100644 --- a/tests/ui/suggestions/struct-initializer-comma.rs +++ b/tests/ui/suggestions/struct-initializer-comma.rs @@ -7,7 +7,6 @@ pub struct Foo { fn main() { let _ = Foo { - //~^ ERROR missing field first: true second: 25 //~^ ERROR expected one of diff --git a/tests/ui/suggestions/struct-initializer-comma.stderr b/tests/ui/suggestions/struct-initializer-comma.stderr index 5eff43f32cda1..5fdc95a30cc44 100644 --- a/tests/ui/suggestions/struct-initializer-comma.stderr +++ b/tests/ui/suggestions/struct-initializer-comma.stderr @@ -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 2 previous errors +error: aborting due to 1 previous error -For more information about this error, try `rustc --explain E0063`.