Rollup merge of #59266 - estebank:struct-parse-recovery, r=petrochenkov

Do not complain about non-existing fields after parse recovery

When failing to parse struct-like enum variants, the ADT gets recorded
as having no fields. Record that we have actually recovered during
parsing of this variant to avoid complaing about non-existing fields
when actually using it.

Fix #57361.
diff --git a/src/librustc/hir/lowering.rs b/src/librustc/hir/lowering.rs
index 949fdd2..73c3b30 100644
--- a/src/librustc/hir/lowering.rs
+++ b/src/librustc/hir/lowering.rs
@@ -2672,7 +2672,7 @@
 
     fn lower_variant_data(&mut self, vdata: &VariantData) -> hir::VariantData {
         match *vdata {
-            VariantData::Struct(ref fields, id) => {
+            VariantData::Struct(ref fields, id, recovered) => {
                 let LoweredNodeId { node_id: _, hir_id } = self.lower_node_id(id);
 
                 hir::VariantData::Struct(
@@ -2682,6 +2682,7 @@
                         .map(|f| self.lower_struct_field(f))
                         .collect(),
                     hir_id,
+                    recovered,
                 )
             },
             VariantData::Tuple(ref fields, id) => {
diff --git a/src/librustc/hir/mod.rs b/src/librustc/hir/mod.rs
index 88ab58d..51d91cc 100644
--- a/src/librustc/hir/mod.rs
+++ b/src/librustc/hir/mod.rs
@@ -2173,7 +2173,7 @@
 /// Id of the whole struct lives in `Item`.
 #[derive(Clone, RustcEncodable, RustcDecodable, Debug, HashStable)]
 pub enum VariantData {
-    Struct(HirVec<StructField>, HirId),
+    Struct(HirVec<StructField>, HirId, /* recovered */ bool),
     Tuple(HirVec<StructField>, HirId),
     Unit(HirId),
 }
@@ -2187,7 +2187,7 @@
     }
     pub fn hir_id(&self) -> HirId {
         match *self {
-            VariantData::Struct(_, hir_id)
+            VariantData::Struct(_, hir_id, _)
             | VariantData::Tuple(_, hir_id)
             | VariantData::Unit(hir_id) => hir_id,
         }
diff --git a/src/librustc/ty/mod.rs b/src/librustc/ty/mod.rs
index 298af1d..91b84b6 100644
--- a/src/librustc/ty/mod.rs
+++ b/src/librustc/ty/mod.rs
@@ -1811,6 +1811,7 @@
     pub fields: Vec<FieldDef>,
     pub ctor_kind: CtorKind,
     flags: VariantFlags,
+    pub recovered: bool,
 }
 
 impl<'a, 'gcx, 'tcx> VariantDef {
@@ -1829,16 +1830,17 @@
     ///
     /// If someone speeds up attribute loading to not be a performance concern, they can
     /// remove this hack and use the constructor `DefId` everywhere.
-    pub fn new(tcx: TyCtxt<'a, 'gcx, 'tcx>,
-               did: DefId,
-               ident: Ident,
-               discr: VariantDiscr,
-               fields: Vec<FieldDef>,
-               adt_kind: AdtKind,
-               ctor_kind: CtorKind,
-               attribute_def_id: DefId)
-               -> Self
-    {
+    pub fn new(
+        tcx: TyCtxt<'a, 'gcx, 'tcx>,
+        did: DefId,
+        ident: Ident,
+        discr: VariantDiscr,
+        fields: Vec<FieldDef>,
+        adt_kind: AdtKind,
+        ctor_kind: CtorKind,
+        attribute_def_id: DefId,
+        recovered: bool,
+    ) -> Self {
         debug!("VariantDef::new({:?}, {:?}, {:?}, {:?}, {:?}, {:?}, {:?})", did, ident, discr,
                fields, adt_kind, ctor_kind, attribute_def_id);
         let mut flags = VariantFlags::NO_VARIANT_FLAGS;
@@ -1852,7 +1854,8 @@
             discr,
             fields,
             ctor_kind,
-            flags
+            flags,
+            recovered,
         }
     }
 
@@ -1868,7 +1871,8 @@
     discr,
     fields,
     ctor_kind,
-    flags
+    flags,
+    recovered
 });
 
 #[derive(Copy, Clone, Debug, PartialEq, Eq, RustcEncodable, RustcDecodable, HashStable)]
diff --git a/src/librustc_metadata/decoder.rs b/src/librustc_metadata/decoder.rs
index 6fe00a4..c608c03 100644
--- a/src/librustc_metadata/decoder.rs
+++ b/src/librustc_metadata/decoder.rs
@@ -576,7 +576,8 @@
             }).collect(),
             adt_kind,
             data.ctor_kind,
-            attribute_def_id
+            attribute_def_id,
+            false,
         )
     }
 
diff --git a/src/librustc_save_analysis/dump_visitor.rs b/src/librustc_save_analysis/dump_visitor.rs
index 3fea515..01bb643 100644
--- a/src/librustc_save_analysis/dump_visitor.rs
+++ b/src/librustc_save_analysis/dump_visitor.rs
@@ -481,8 +481,8 @@
         };
 
         let (value, fields) = match item.node {
-            ast::ItemKind::Struct(ast::VariantData::Struct(ref fields, _), _) |
-            ast::ItemKind::Union(ast::VariantData::Struct(ref fields, _), _) => {
+            ast::ItemKind::Struct(ast::VariantData::Struct(ref fields, ..), _) |
+            ast::ItemKind::Union(ast::VariantData::Struct(ref fields, ..), _) => {
                 let include_priv_fields = !self.save_ctxt.config.pub_only;
                 let fields_str = fields
                     .iter()
@@ -560,7 +560,7 @@
             let name_span = variant.node.ident.span;
 
             match variant.node.data {
-                ast::VariantData::Struct(ref fields, _) => {
+                ast::VariantData::Struct(ref fields, ..) => {
                     let fields_str = fields
                         .iter()
                         .enumerate()
diff --git a/src/librustc_save_analysis/sig.rs b/src/librustc_save_analysis/sig.rs
index 64a2c92..6e47ae6 100644
--- a/src/librustc_save_analysis/sig.rs
+++ b/src/librustc_save_analysis/sig.rs
@@ -703,7 +703,7 @@
     fn make(&self, offset: usize, _parent_id: Option<NodeId>, scx: &SaveContext<'_, '_>) -> Result {
         let mut text = self.ident.to_string();
         match self.data {
-            ast::VariantData::Struct(ref fields, id) => {
+            ast::VariantData::Struct(ref fields, id, r) => {
                 let name_def = SigElement {
                     id: id_from_node_id(id, scx),
                     start: offset,
@@ -712,12 +712,16 @@
                 text.push_str(" { ");
                 let mut defs = vec![name_def];
                 let mut refs = vec![];
-                for f in fields {
-                    let field_sig = f.make(offset + text.len(), Some(id), scx)?;
-                    text.push_str(&field_sig.text);
-                    text.push_str(", ");
-                    defs.extend(field_sig.defs.into_iter());
-                    refs.extend(field_sig.refs.into_iter());
+                if r {
+                    text.push_str("/* parse error */ ");
+                } else {
+                    for f in fields {
+                        let field_sig = f.make(offset + text.len(), Some(id), scx)?;
+                        text.push_str(&field_sig.text);
+                        text.push_str(", ");
+                        defs.extend(field_sig.defs.into_iter());
+                        refs.extend(field_sig.refs.into_iter());
+                    }
                 }
                 text.push('}');
                 Ok(Signature { text, defs, refs })
diff --git a/src/librustc_typeck/check/_match.rs b/src/librustc_typeck/check/_match.rs
index c6b6639..c30b9d6 100644
--- a/src/librustc_typeck/check/_match.rs
+++ b/src/librustc_typeck/check/_match.rs
@@ -918,14 +918,16 @@
         pat_ty
     }
 
-    fn check_struct_pat_fields(&self,
-                               adt_ty: Ty<'tcx>,
-                               pat_id: hir::HirId,
-                               span: Span,
-                               variant: &'tcx ty::VariantDef,
-                               fields: &'gcx [Spanned<hir::FieldPat>],
-                               etc: bool,
-                               def_bm: ty::BindingMode) -> bool {
+    fn check_struct_pat_fields(
+        &self,
+        adt_ty: Ty<'tcx>,
+        pat_id: hir::HirId,
+        span: Span,
+        variant: &'tcx ty::VariantDef,
+        fields: &'gcx [Spanned<hir::FieldPat>],
+        etc: bool,
+        def_bm: ty::BindingMode,
+    ) -> bool {
         let tcx = self.tcx;
 
         let (substs, adt) = match adt_ty.sty {
@@ -985,7 +987,7 @@
                 .map(|field| field.ident.modern())
                 .filter(|ident| !used_fields.contains_key(&ident))
                 .collect::<Vec<_>>();
-        if inexistent_fields.len() > 0 {
+        if inexistent_fields.len() > 0 && !variant.recovered {
             let (field_names, t, plural) = if inexistent_fields.len() == 1 {
                 (format!("a field named `{}`", inexistent_fields[0].1), "this", "")
             } else {
diff --git a/src/librustc_typeck/check/mod.rs b/src/librustc_typeck/check/mod.rs
index 899bb6f..ab0e4b0 100644
--- a/src/librustc_typeck/check/mod.rs
+++ b/src/librustc_typeck/check/mod.rs
@@ -3734,12 +3734,17 @@
                            field, expr_t)
     }
 
-    fn report_unknown_field(&self,
-                            ty: Ty<'tcx>,
-                            variant: &'tcx ty::VariantDef,
-                            field: &hir::Field,
-                            skip_fields: &[hir::Field],
-                            kind_name: &str) {
+    fn report_unknown_field(
+        &self,
+        ty: Ty<'tcx>,
+        variant: &'tcx ty::VariantDef,
+        field: &hir::Field,
+        skip_fields: &[hir::Field],
+        kind_name: &str,
+    ) {
+        if variant.recovered {
+            return;
+        }
         let mut err = self.type_error_struct_with_diag(
             field.ident.span,
             |actual| match ty.sty {
diff --git a/src/librustc_typeck/collect.rs b/src/librustc_typeck/collect.rs
index 10e9613..c0739db 100644
--- a/src/librustc_typeck/collect.rs
+++ b/src/librustc_typeck/collect.rs
@@ -598,6 +598,10 @@
             }
         })
         .collect();
+    let recovered = match def {
+        hir::VariantData::Struct(_, _, r) => *r,
+        _ => false,
+    };
     ty::VariantDef::new(tcx,
         did,
         ident,
@@ -605,7 +609,8 @@
         fields,
         adt_kind,
         CtorKind::from_hir(def),
-        attribute_def_id
+        attribute_def_id,
+        recovered,
     )
 }
 
diff --git a/src/libsyntax/ast.rs b/src/libsyntax/ast.rs
index 1a0da73..2cbd2df 100644
--- a/src/libsyntax/ast.rs
+++ b/src/libsyntax/ast.rs
@@ -620,7 +620,7 @@
 
     /// A struct or struct variant pattern (e.g., `Variant {x, y, ..}`).
     /// The `bool` is `true` in the presence of a `..`.
-    Struct(Path, Vec<Spanned<FieldPat>>, bool),
+    Struct(Path, Vec<Spanned<FieldPat>>, /* recovered */ bool),
 
     /// A tuple struct/variant pattern (`Variant(x, y, .., z)`).
     /// If the `..` pattern fragment is present, then `Option<usize>` denotes its position.
@@ -2133,7 +2133,7 @@
     /// Struct variant.
     ///
     /// E.g., `Bar { .. }` as in `enum Foo { Bar { .. } }`.
-    Struct(Vec<StructField>, NodeId),
+    Struct(Vec<StructField>, NodeId, bool),
     /// Tuple variant.
     ///
     /// E.g., `Bar(..)` as in `enum Foo { Bar(..) }`.
@@ -2147,13 +2147,13 @@
 impl VariantData {
     pub fn fields(&self) -> &[StructField] {
         match *self {
-            VariantData::Struct(ref fields, _) | VariantData::Tuple(ref fields, _) => fields,
+            VariantData::Struct(ref fields, ..) | VariantData::Tuple(ref fields, _) => fields,
             _ => &[],
         }
     }
     pub fn id(&self) -> NodeId {
         match *self {
-            VariantData::Struct(_, id) | VariantData::Tuple(_, id) | VariantData::Unit(id) => id,
+            VariantData::Struct(_, id, _) | VariantData::Tuple(_, id) | VariantData::Unit(id) => id,
         }
     }
     pub fn is_struct(&self) -> bool {
diff --git a/src/libsyntax/config.rs b/src/libsyntax/config.rs
index c300ffc..7159c94 100644
--- a/src/libsyntax/config.rs
+++ b/src/libsyntax/config.rs
@@ -225,7 +225,7 @@
 
     fn configure_variant_data(&mut self, vdata: &mut ast::VariantData) {
         match vdata {
-            ast::VariantData::Struct(fields, _id) |
+            ast::VariantData::Struct(fields, _id, _) |
             ast::VariantData::Tuple(fields, _id) =>
                 fields.flat_map_in_place(|field| self.configure(field)),
             ast::VariantData::Unit(_id) => {}
diff --git a/src/libsyntax/mut_visit.rs b/src/libsyntax/mut_visit.rs
index 462346d..5bb1d8a 100644
--- a/src/libsyntax/mut_visit.rs
+++ b/src/libsyntax/mut_visit.rs
@@ -765,7 +765,7 @@
 
 pub fn noop_visit_variant_data<T: MutVisitor>(vdata: &mut VariantData, vis: &mut T) {
     match vdata {
-        VariantData::Struct(fields, id) |
+        VariantData::Struct(fields, id, _) |
         VariantData::Tuple(fields, id) => {
             visit_vec(fields, |field| vis.visit_struct_field(field));
             vis.visit_id(id);
diff --git a/src/libsyntax/parse/parser.rs b/src/libsyntax/parse/parser.rs
index 7a4a687..648ee77 100644
--- a/src/libsyntax/parse/parser.rs
+++ b/src/libsyntax/parse/parser.rs
@@ -6837,14 +6837,16 @@
                 VariantData::Unit(ast::DUMMY_NODE_ID)
             } else {
                 // If we see: `struct Foo<T> where T: Copy { ... }`
-                VariantData::Struct(self.parse_record_struct_body()?, ast::DUMMY_NODE_ID)
+                let (fields, recovered) = self.parse_record_struct_body()?;
+                VariantData::Struct(fields, ast::DUMMY_NODE_ID, recovered)
             }
         // No `where` so: `struct Foo<T>;`
         } else if self.eat(&token::Semi) {
             VariantData::Unit(ast::DUMMY_NODE_ID)
         // Record-style struct definition
         } else if self.token == token::OpenDelim(token::Brace) {
-            VariantData::Struct(self.parse_record_struct_body()?, ast::DUMMY_NODE_ID)
+            let (fields, recovered) = self.parse_record_struct_body()?;
+            VariantData::Struct(fields, ast::DUMMY_NODE_ID, recovered)
         // Tuple-style struct definition with optional where-clause.
         } else if self.token == token::OpenDelim(token::Paren) {
             let body = VariantData::Tuple(self.parse_tuple_struct_body()?, ast::DUMMY_NODE_ID);
@@ -6872,9 +6874,11 @@
 
         let vdata = if self.token.is_keyword(keywords::Where) {
             generics.where_clause = self.parse_where_clause()?;
-            VariantData::Struct(self.parse_record_struct_body()?, ast::DUMMY_NODE_ID)
+            let (fields, recovered) = self.parse_record_struct_body()?;
+            VariantData::Struct(fields, ast::DUMMY_NODE_ID, recovered)
         } else if self.token == token::OpenDelim(token::Brace) {
-            VariantData::Struct(self.parse_record_struct_body()?, ast::DUMMY_NODE_ID)
+            let (fields, recovered) = self.parse_record_struct_body()?;
+            VariantData::Struct(fields, ast::DUMMY_NODE_ID, recovered)
         } else {
             let token_str = self.this_token_descr();
             let mut err = self.fatal(&format!(
@@ -6906,12 +6910,16 @@
         }
     }
 
-    fn parse_record_struct_body(&mut self) -> PResult<'a, Vec<StructField>> {
+    fn parse_record_struct_body(
+        &mut self,
+    ) -> PResult<'a, (Vec<StructField>, /* recovered */ bool)> {
         let mut fields = Vec::new();
+        let mut recovered = false;
         if self.eat(&token::OpenDelim(token::Brace)) {
             while self.token != token::CloseDelim(token::Brace) {
                 let field = self.parse_struct_decl_field().map_err(|e| {
                     self.recover_stmt();
+                    recovered = true;
                     e
                 });
                 match field {
@@ -6930,7 +6938,7 @@
             return Err(err);
         }
 
-        Ok(fields)
+        Ok((fields, recovered))
     }
 
     fn parse_tuple_struct_body(&mut self) -> PResult<'a, Vec<StructField>> {
@@ -7693,12 +7701,14 @@
             if self.check(&token::OpenDelim(token::Brace)) {
                 // Parse a struct variant.
                 all_nullary = false;
-                struct_def = VariantData::Struct(self.parse_record_struct_body()?,
-                                                 ast::DUMMY_NODE_ID);
+                let (fields, recovered) = self.parse_record_struct_body()?;
+                struct_def = VariantData::Struct(fields, ast::DUMMY_NODE_ID, recovered);
             } else if self.check(&token::OpenDelim(token::Paren)) {
                 all_nullary = false;
-                struct_def = VariantData::Tuple(self.parse_tuple_struct_body()?,
-                                                ast::DUMMY_NODE_ID);
+                struct_def = VariantData::Tuple(
+                    self.parse_tuple_struct_body()?,
+                    ast::DUMMY_NODE_ID,
+                );
             } else if self.eat(&token::Eq) {
                 disr_expr = Some(AnonConst {
                     id: ast::DUMMY_NODE_ID,
diff --git a/src/test/ui/parser/recovered-struct-variant.rs b/src/test/ui/parser/recovered-struct-variant.rs
new file mode 100644
index 0000000..5b195dc
--- /dev/null
+++ b/src/test/ui/parser/recovered-struct-variant.rs
@@ -0,0 +1,13 @@
+enum Foo {
+    A { a, b: usize }
+    //~^ ERROR expected `:`, found `,`
+}
+
+fn main() {
+    // no complaints about non-existing fields
+    let f = Foo::A { a:3, b: 4};
+    match f {
+        // no complaints about non-existing fields
+        Foo::A {a, b} => {}
+    }
+}
diff --git a/src/test/ui/parser/recovered-struct-variant.stderr b/src/test/ui/parser/recovered-struct-variant.stderr
new file mode 100644
index 0000000..51aaf8b
--- /dev/null
+++ b/src/test/ui/parser/recovered-struct-variant.stderr
@@ -0,0 +1,8 @@
+error: expected `:`, found `,`
+  --> $DIR/recovered-struct-variant.rs:2:10
+   |
+LL |     A { a, b: usize }
+   |          ^ expected `:`
+
+error: aborting due to previous error
+