about summary refs log tree commit diff
path: root/compiler
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2021-05-17 12:15:26 +0000
committerbors <bors@rust-lang.org>2021-05-17 12:15:26 +0000
commit9964284fed181676300aad693449f5b751e35ff2 (patch)
tree03eb105fdadb767ef2ab06ddb5c834afb80aa2ff /compiler
parent44ec846f4ea68ffa6d06e7d68f078bd3cc59d4ec (diff)
parent64acb7d92135ae722dfce89f0ca9d7cf6576de66 (diff)
downloadrust-9964284fed181676300aad693449f5b751e35ff2.tar.gz
rust-9964284fed181676300aad693449f5b751e35ff2.zip
Auto merge of #84571 - jedel1043:issue-49804-impl, r=petrochenkov
Parse unnamed fields of struct and union type

Added the `unnamed_fields` feature gate.

This is a prototype of [RFC 2102](https://github.com/rust-lang/rust/issues/49804), so any suggestions are greatly appreciated.

r? `@petrochenkov`
Diffstat (limited to 'compiler')
-rw-r--r--compiler/rustc_ast/src/ast.rs4
-rw-r--r--compiler/rustc_ast/src/mut_visit.rs3
-rw-r--r--compiler/rustc_ast/src/visit.rs3
-rw-r--r--compiler/rustc_ast_lowering/src/item.rs5
-rw-r--r--compiler/rustc_ast_lowering/src/lib.rs9
-rw-r--r--compiler/rustc_ast_passes/src/ast_validation.rs254
-rw-r--r--compiler/rustc_ast_passes/src/feature_gate.rs1
-rw-r--r--compiler/rustc_ast_pretty/src/pprust/state.rs50
-rw-r--r--compiler/rustc_feature/src/active.rs4
-rw-r--r--compiler/rustc_parse/src/parser/item.rs32
-rw-r--r--compiler/rustc_parse/src/parser/ty.rs13
-rw-r--r--compiler/rustc_span/src/symbol.rs1
12 files changed, 278 insertions, 101 deletions
diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs
index fb012d9802f..b3bac1d7ecd 100644
--- a/compiler/rustc_ast/src/ast.rs
+++ b/compiler/rustc_ast/src/ast.rs
@@ -1861,6 +1861,10 @@ pub enum TyKind {
     Never,
     /// A tuple (`(A, B, C, D,...)`).
     Tup(Vec<P<Ty>>),
+    /// An anonymous struct type i.e. `struct { foo: Type }`
+    AnonymousStruct(Vec<FieldDef>, bool),
+    /// An anonymous union type i.e. `union { bar: Type }`
+    AnonymousUnion(Vec<FieldDef>, bool),
     /// A path (`module::module::...::Type`), optionally
     /// "qualified", e.g., `<Vec<T> as SomeTrait>::SomeType`.
     ///
diff --git a/compiler/rustc_ast/src/mut_visit.rs b/compiler/rustc_ast/src/mut_visit.rs
index 374a6ec972f..071d41ea2b2 100644
--- a/compiler/rustc_ast/src/mut_visit.rs
+++ b/compiler/rustc_ast/src/mut_visit.rs
@@ -484,6 +484,9 @@ pub fn noop_visit_ty<T: MutVisitor>(ty: &mut P<Ty>, vis: &mut T) {
             visit_vec(bounds, |bound| vis.visit_param_bound(bound));
         }
         TyKind::MacCall(mac) => vis.visit_mac_call(mac),
+        TyKind::AnonymousStruct(fields, ..) | TyKind::AnonymousUnion(fields, ..) => {
+            fields.flat_map_in_place(|field| vis.flat_map_field_def(field));
+        }
     }
     vis.visit_span(span);
     visit_lazy_tts(tokens, vis);
diff --git a/compiler/rustc_ast/src/visit.rs b/compiler/rustc_ast/src/visit.rs
index c50b334d3e9..f1a99bc51c9 100644
--- a/compiler/rustc_ast/src/visit.rs
+++ b/compiler/rustc_ast/src/visit.rs
@@ -404,6 +404,9 @@ pub fn walk_ty<'a, V: Visitor<'a>>(visitor: &mut V, typ: &'a Ty) {
         TyKind::Typeof(ref expression) => visitor.visit_anon_const(expression),
         TyKind::Infer | TyKind::ImplicitSelf | TyKind::Err => {}
         TyKind::MacCall(ref mac) => visitor.visit_mac_call(mac),
+        TyKind::AnonymousStruct(ref fields, ..) | TyKind::AnonymousUnion(ref fields, ..) => {
+            walk_list!(visitor, visit_field_def, fields)
+        }
         TyKind::Never | TyKind::CVarArgs => {}
     }
 }
diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs
index aa236a690ec..292643d6d75 100644
--- a/compiler/rustc_ast_lowering/src/item.rs
+++ b/compiler/rustc_ast_lowering/src/item.rs
@@ -789,7 +789,10 @@ impl<'hir> LoweringContext<'_, 'hir> {
         }
     }
 
-    fn lower_field_def(&mut self, (index, f): (usize, &FieldDef)) -> hir::FieldDef<'hir> {
+    pub(super) fn lower_field_def(
+        &mut self,
+        (index, f): (usize, &FieldDef),
+    ) -> hir::FieldDef<'hir> {
         let ty = if let TyKind::Path(ref qself, ref path) = f.ty.kind {
             let t = self.lower_path_ty(
                 &f.ty,
diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs
index 0439de0ee7b..740dfc65df8 100644
--- a/compiler/rustc_ast_lowering/src/lib.rs
+++ b/compiler/rustc_ast_lowering/src/lib.rs
@@ -1267,6 +1267,15 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
         let kind = match t.kind {
             TyKind::Infer => hir::TyKind::Infer,
             TyKind::Err => hir::TyKind::Err,
+            // FIXME(unnamed_fields): IMPLEMENTATION IN PROGRESS
+            TyKind::AnonymousStruct(ref _fields, _recovered) => {
+                self.sess.struct_span_err(t.span, "anonymous structs are unimplemented").emit();
+                hir::TyKind::Err
+            }
+            TyKind::AnonymousUnion(ref _fields, _recovered) => {
+                self.sess.struct_span_err(t.span, "anonymous unions are unimplemented").emit();
+                hir::TyKind::Err
+            }
             TyKind::Slice(ref ty) => hir::TyKind::Slice(self.lower_ty(ty, itctx)),
             TyKind::Ptr(ref mt) => hir::TyKind::Ptr(self.lower_mt(mt, itctx)),
             TyKind::Rptr(ref region, ref mt) => {
diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs
index 6d6438920c0..ba2da769497 100644
--- a/compiler/rustc_ast_passes/src/ast_validation.rs
+++ b/compiler/rustc_ast_passes/src/ast_validation.rs
@@ -175,10 +175,30 @@ impl<'a> AstValidator<'a> {
                     }
                 }
             }
+            TyKind::AnonymousStruct(ref fields, ..) | TyKind::AnonymousUnion(ref fields, ..) => {
+                self.with_banned_assoc_ty_bound(|this| {
+                    walk_list!(this, visit_struct_field_def, fields)
+                });
+            }
             _ => visit::walk_ty(self, t),
         }
     }
 
+    fn visit_struct_field_def(&mut self, field: &'a FieldDef) {
+        if let Some(ident) = field.ident {
+            if ident.name == kw::Underscore {
+                self.check_anonymous_field(field);
+                self.visit_vis(&field.vis);
+                self.visit_ident(ident);
+                self.visit_ty_common(&field.ty);
+                self.walk_ty(&field.ty);
+                walk_list!(self, visit_attribute, &field.attrs);
+                return;
+            }
+        }
+        self.visit_field_def(field);
+    }
+
     fn err_handler(&self) -> &rustc_errors::Handler {
         &self.session.diagnostic()
     }
@@ -213,6 +233,66 @@ impl<'a> AstValidator<'a> {
         err.emit();
     }
 
+    fn check_anonymous_field(&self, field: &FieldDef) {
+        let FieldDef { ty, .. } = field;
+        match &ty.kind {
+            TyKind::AnonymousStruct(..) | TyKind::AnonymousUnion(..) => {
+                // We already checked for `kw::Underscore` before calling this function,
+                // so skip the check
+            }
+            TyKind::Path(..) => {
+                // If the anonymous field contains a Path as type, we can't determine
+                // if the path is a valid struct or union, so skip the check
+            }
+            _ => {
+                let msg = "unnamed fields can only have struct or union types";
+                let label = "not a struct or union";
+                self.err_handler()
+                    .struct_span_err(field.span, msg)
+                    .span_label(ty.span, label)
+                    .emit();
+            }
+        }
+    }
+
+    fn deny_anonymous_struct(&self, ty: &Ty) {
+        match &ty.kind {
+            TyKind::AnonymousStruct(..) => {
+                self.err_handler()
+                    .struct_span_err(
+                        ty.span,
+                        "anonymous structs are not allowed outside of unnamed struct or union fields",
+                    )
+                    .span_label(ty.span, "anonymous struct declared here")
+                    .emit();
+            }
+            TyKind::AnonymousUnion(..) => {
+                self.err_handler()
+                    .struct_span_err(
+                        ty.span,
+                        "anonymous unions are not allowed outside of unnamed struct or union fields",
+                    )
+                    .span_label(ty.span, "anonymous union declared here")
+                    .emit();
+            }
+            _ => {}
+        }
+    }
+
+    fn deny_anonymous_field(&self, field: &FieldDef) {
+        if let Some(ident) = field.ident {
+            if ident.name == kw::Underscore {
+                self.err_handler()
+                    .struct_span_err(
+                        field.span,
+                        "anonymous fields are not allowed outside of structs or unions",
+                    )
+                    .span_label(ident.span, "anonymous field declared here")
+                    .emit()
+            }
+        }
+    }
+
     fn check_decl_no_pat(decl: &FnDecl, mut report_err: impl FnMut(Span, Option<Ident>, bool)) {
         for Param { pat, .. } in &decl.inputs {
             match pat.kind {
@@ -732,6 +812,71 @@ impl<'a> AstValidator<'a> {
             )
             .emit();
     }
+
+    fn visit_ty_common(&mut self, ty: &'a Ty) {
+        match ty.kind {
+            TyKind::BareFn(ref bfty) => {
+                self.check_fn_decl(&bfty.decl, SelfSemantic::No);
+                Self::check_decl_no_pat(&bfty.decl, |span, _, _| {
+                    struct_span_err!(
+                        self.session,
+                        span,
+                        E0561,
+                        "patterns aren't allowed in function pointer types"
+                    )
+                    .emit();
+                });
+                self.check_late_bound_lifetime_defs(&bfty.generic_params);
+            }
+            TyKind::TraitObject(ref bounds, ..) => {
+                let mut any_lifetime_bounds = false;
+                for bound in bounds {
+                    if let GenericBound::Outlives(ref lifetime) = *bound {
+                        if any_lifetime_bounds {
+                            struct_span_err!(
+                                self.session,
+                                lifetime.ident.span,
+                                E0226,
+                                "only a single explicit lifetime bound is permitted"
+                            )
+                            .emit();
+                            break;
+                        }
+                        any_lifetime_bounds = true;
+                    }
+                }
+                self.no_questions_in_bounds(bounds, "trait object types", false);
+            }
+            TyKind::ImplTrait(_, ref bounds) => {
+                if self.is_impl_trait_banned {
+                    struct_span_err!(
+                        self.session,
+                        ty.span,
+                        E0667,
+                        "`impl Trait` is not allowed in path parameters"
+                    )
+                    .emit();
+                }
+
+                if let Some(outer_impl_trait_sp) = self.outer_impl_trait {
+                    struct_span_err!(
+                        self.session,
+                        ty.span,
+                        E0666,
+                        "nested `impl Trait` is not allowed"
+                    )
+                    .span_label(outer_impl_trait_sp, "outer `impl Trait`")
+                    .span_label(ty.span, "nested `impl Trait` here")
+                    .emit();
+                }
+
+                if !bounds.iter().any(|b| matches!(b, GenericBound::Trait(..))) {
+                    self.err_handler().span_err(ty.span, "at least one trait must be specified");
+                }
+            }
+            _ => {}
+        }
+    }
 }
 
 /// Checks that generic parameters are in the correct order,
@@ -850,72 +995,8 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
     }
 
     fn visit_ty(&mut self, ty: &'a Ty) {
-        match ty.kind {
-            TyKind::BareFn(ref bfty) => {
-                self.check_fn_decl(&bfty.decl, SelfSemantic::No);
-                Self::check_decl_no_pat(&bfty.decl, |span, _, _| {
-                    struct_span_err!(
-                        self.session,
-                        span,
-                        E0561,
-                        "patterns aren't allowed in function pointer types"
-                    )
-                    .emit();
-                });
-                self.check_late_bound_lifetime_defs(&bfty.generic_params);
-            }
-            TyKind::TraitObject(ref bounds, ..) => {
-                let mut any_lifetime_bounds = false;
-                for bound in bounds {
-                    if let GenericBound::Outlives(ref lifetime) = *bound {
-                        if any_lifetime_bounds {
-                            struct_span_err!(
-                                self.session,
-                                lifetime.ident.span,
-                                E0226,
-                                "only a single explicit lifetime bound is permitted"
-                            )
-                            .emit();
-                            break;
-                        }
-                        any_lifetime_bounds = true;
-                    }
-                }
-                self.no_questions_in_bounds(bounds, "trait object types", false);
-            }
-            TyKind::ImplTrait(_, ref bounds) => {
-                if self.is_impl_trait_banned {
-                    struct_span_err!(
-                        self.session,
-                        ty.span,
-                        E0667,
-                        "`impl Trait` is not allowed in path parameters"
-                    )
-                    .emit();
-                }
-
-                if let Some(outer_impl_trait_sp) = self.outer_impl_trait {
-                    struct_span_err!(
-                        self.session,
-                        ty.span,
-                        E0666,
-                        "nested `impl Trait` is not allowed"
-                    )
-                    .span_label(outer_impl_trait_sp, "outer `impl Trait`")
-                    .span_label(ty.span, "nested `impl Trait` here")
-                    .emit();
-                }
-
-                if !bounds.iter().any(|b| matches!(b, GenericBound::Trait(..))) {
-                    self.err_handler().span_err(ty.span, "at least one trait must be specified");
-                }
-
-                self.walk_ty(ty);
-                return;
-            }
-            _ => {}
-        }
-
+        self.visit_ty_common(ty);
+        self.deny_anonymous_struct(ty);
         self.walk_ty(ty)
     }
 
@@ -929,6 +1010,11 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
         visit::walk_lifetime(self, lifetime);
     }
 
+    fn visit_field_def(&mut self, s: &'a FieldDef) {
+        self.deny_anonymous_field(s);
+        visit::walk_field_def(self, s)
+    }
+
     fn visit_item(&mut self, item: &'a Item) {
         if item.attrs.iter().any(|attr| self.session.is_proc_macro_attr(attr)) {
             self.has_proc_macro_decls = true;
@@ -1084,14 +1170,42 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
                     self.check_mod_file_item_asciionly(item.ident);
                 }
             }
-            ItemKind::Union(ref vdata, _) => {
-                if let VariantData::Tuple(..) | VariantData::Unit(..) = vdata {
-                    self.err_handler()
-                        .span_err(item.span, "tuple and unit unions are not permitted");
+            ItemKind::Struct(ref vdata, ref generics) => match vdata {
+                // Duplicating the `Visitor` logic allows catching all cases
+                // of `Anonymous(Struct, Union)` outside of a field struct or union.
+                //
+                // Inside `visit_ty` the validator catches every `Anonymous(Struct, Union)` it
+                // encounters, and only on `ItemKind::Struct` and `ItemKind::Union`
+                // it uses `visit_ty_common`, which doesn't contain that specific check.
+                VariantData::Struct(ref fields, ..) => {
+                    self.visit_vis(&item.vis);
+                    self.visit_ident(item.ident);
+                    self.visit_generics(generics);
+                    self.with_banned_assoc_ty_bound(|this| {
+                        walk_list!(this, visit_struct_field_def, fields);
+                    });
+                    walk_list!(self, visit_attribute, &item.attrs);
+                    return;
                 }
+                _ => {}
+            },
+            ItemKind::Union(ref vdata, ref generics) => {
                 if vdata.fields().is_empty() {
                     self.err_handler().span_err(item.span, "unions cannot have zero fields");
                 }
+                match vdata {
+                    VariantData::Struct(ref fields, ..) => {
+                        self.visit_vis(&item.vis);
+                        self.visit_ident(item.ident);
+                        self.visit_generics(generics);
+                        self.with_banned_assoc_ty_bound(|this| {
+                            walk_list!(this, visit_struct_field_def, fields);
+                        });
+                        walk_list!(self, visit_attribute, &item.attrs);
+                        return;
+                    }
+                    _ => {}
+                }
             }
             ItemKind::Const(def, .., None) => {
                 self.check_defaultness(item.span, def);
diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs
index dc3383dae84..4215d5c55a0 100644
--- a/compiler/rustc_ast_passes/src/feature_gate.rs
+++ b/compiler/rustc_ast_passes/src/feature_gate.rs
@@ -725,6 +725,7 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session) {
         // involved, so we only emit errors where there are no other parsing errors.
         gate_all!(destructuring_assignment, "destructuring assignments are unstable");
     }
+    gate_all!(unnamed_fields, "unnamed fields are not yet fully implemented");
 
     // All uses of `gate_all!` below this point were added in #65742,
     // and subsequently disabled (with the non-early gating readded).
diff --git a/compiler/rustc_ast_pretty/src/pprust/state.rs b/compiler/rustc_ast_pretty/src/pprust/state.rs
index a7908f70060..da9d89745a8 100644
--- a/compiler/rustc_ast_pretty/src/pprust/state.rs
+++ b/compiler/rustc_ast_pretty/src/pprust/state.rs
@@ -954,6 +954,14 @@ impl<'a> State<'a> {
                 }
                 self.pclose();
             }
+            ast::TyKind::AnonymousStruct(ref fields, ..) => {
+                self.s.word("struct");
+                self.print_record_struct_body(fields, ty.span);
+            }
+            ast::TyKind::AnonymousUnion(ref fields, ..) => {
+                self.s.word("union");
+                self.print_record_struct_body(fields, ty.span);
+            }
             ast::TyKind::Paren(ref typ) => {
                 self.popen();
                 self.print_type(typ);
@@ -1389,6 +1397,29 @@ impl<'a> State<'a> {
         }
     }
 
+    crate fn print_record_struct_body(
+        &mut self,
+        fields: &Vec<ast::FieldDef>,
+        span: rustc_span::Span,
+    ) {
+        self.nbsp();
+        self.bopen();
+        self.hardbreak_if_not_bol();
+
+        for field in fields {
+            self.hardbreak_if_not_bol();
+            self.maybe_print_comment(field.span.lo());
+            self.print_outer_attributes(&field.attrs);
+            self.print_visibility(&field.vis);
+            self.print_ident(field.ident.unwrap());
+            self.word_nbsp(":");
+            self.print_type(&field.ty);
+            self.s.word(",");
+        }
+
+        self.bclose(span)
+    }
+
     crate fn print_struct(
         &mut self,
         struct_def: &ast::VariantData,
@@ -1418,24 +1449,9 @@ impl<'a> State<'a> {
                 self.end();
                 self.end(); // Close the outer-box.
             }
-            ast::VariantData::Struct(..) => {
+            ast::VariantData::Struct(ref fields, ..) => {
                 self.print_where_clause(&generics.where_clause);
-                self.nbsp();
-                self.bopen();
-                self.hardbreak_if_not_bol();
-
-                for field in struct_def.fields() {
-                    self.hardbreak_if_not_bol();
-                    self.maybe_print_comment(field.span.lo());
-                    self.print_outer_attributes(&field.attrs);
-                    self.print_visibility(&field.vis);
-                    self.print_ident(field.ident.unwrap());
-                    self.word_nbsp(":");
-                    self.print_type(&field.ty);
-                    self.s.word(",");
-                }
-
-                self.bclose(span)
+                self.print_record_struct_body(fields, span);
             }
         }
     }
diff --git a/compiler/rustc_feature/src/active.rs b/compiler/rustc_feature/src/active.rs
index f3f9f0cd763..80fe5bafc6e 100644
--- a/compiler/rustc_feature/src/active.rs
+++ b/compiler/rustc_feature/src/active.rs
@@ -671,6 +671,9 @@ declare_features! (
     /// Allows specifying the as-needed link modifier
     (active, native_link_modifiers_as_needed, "1.53.0", Some(81490), None),
 
+    /// Allows unnamed fields of struct and union type
+    (active, unnamed_fields, "1.53.0", Some(49804), None),
+
     // -------------------------------------------------------------------------
     // feature-group-end: actual feature gates
     // -------------------------------------------------------------------------
@@ -704,6 +707,7 @@ pub const INCOMPLETE_FEATURES: &[Symbol] = &[
     sym::native_link_modifiers_whole_archive,
     sym::native_link_modifiers_as_needed,
     sym::rustc_insignificant_dtor,
+    sym::unnamed_fields,
 ];
 
 /// Some features are not allowed to be used together at the same time, if
diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs
index b2b578f1ed4..c64fab0507c 100644
--- a/compiler/rustc_parse/src/parser/item.rs
+++ b/compiler/rustc_parse/src/parser/item.rs
@@ -1236,7 +1236,7 @@ impl<'a> Parser<'a> {
         Ok((class_name, ItemKind::Union(vdata, generics)))
     }
 
-    fn parse_record_struct_body(
+    pub(super) fn parse_record_struct_body(
         &mut self,
         adt_ty: &str,
     ) -> PResult<'a, (Vec<FieldDef>, /* recovered */ bool)> {
@@ -1470,19 +1470,25 @@ impl<'a> Parser<'a> {
     fn parse_field_ident(&mut self, adt_ty: &str, lo: Span) -> PResult<'a, Ident> {
         let (ident, is_raw) = self.ident_or_err()?;
         if !is_raw && ident.is_reserved() {
-            let err = if self.check_fn_front_matter(false) {
-                let _ = self.parse_fn(&mut Vec::new(), |_| true, lo);
-                let mut err = self.struct_span_err(
-                    lo.to(self.prev_token.span),
-                    &format!("functions are not allowed in {} definitions", adt_ty),
-                );
-                err.help("unlike in C++, Java, and C#, functions are declared in `impl` blocks");
-                err.help("see https://doc.rust-lang.org/book/ch05-03-method-syntax.html for more information");
-                err
+            if ident.name == kw::Underscore {
+                self.sess.gated_spans.gate(sym::unnamed_fields, lo);
             } else {
-                self.expected_ident_found()
-            };
-            return Err(err);
+                let err = if self.check_fn_front_matter(false) {
+                    let _ = self.parse_fn(&mut Vec::new(), |_| true, lo);
+                    let mut err = self.struct_span_err(
+                        lo.to(self.prev_token.span),
+                        &format!("functions are not allowed in {} definitions", adt_ty),
+                    );
+                    err.help(
+                        "unlike in C++, Java, and C#, functions are declared in `impl` blocks",
+                    );
+                    err.help("see https://doc.rust-lang.org/book/ch05-03-method-syntax.html for more information");
+                    err
+                } else {
+                    self.expected_ident_found()
+                };
+                return Err(err);
+            }
         }
         self.bump();
         Ok(ident)
diff --git a/compiler/rustc_parse/src/parser/ty.rs b/compiler/rustc_parse/src/parser/ty.rs
index d537741c749..89cf2d7876e 100644
--- a/compiler/rustc_parse/src/parser/ty.rs
+++ b/compiler/rustc_parse/src/parser/ty.rs
@@ -226,6 +226,19 @@ impl<'a> Parser<'a> {
             }
         } else if self.eat_keyword(kw::Impl) {
             self.parse_impl_ty(&mut impl_dyn_multi)?
+        } else if self.token.is_keyword(kw::Union)
+            && self.look_ahead(1, |t| t == &token::OpenDelim(token::Brace))
+        {
+            self.bump();
+            let (fields, recovered) = self.parse_record_struct_body("union")?;
+            let span = lo.to(self.prev_token.span);
+            self.sess.gated_spans.gate(sym::unnamed_fields, span);
+            TyKind::AnonymousUnion(fields, recovered)
+        } else if self.eat_keyword(kw::Struct) {
+            let (fields, recovered) = self.parse_record_struct_body("struct")?;
+            let span = lo.to(self.prev_token.span);
+            self.sess.gated_spans.gate(sym::unnamed_fields, span);
+            TyKind::AnonymousStruct(fields, recovered)
         } else if self.is_explicit_dyn_type() {
             self.parse_dyn_ty(&mut impl_dyn_multi)?
         } else if self.eat_lt() {
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index 502d4cd3272..03a8f2be156 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -1270,6 +1270,7 @@ symbols! {
         unix,
         unlikely,
         unmarked_api,
+        unnamed_fields,
         unpin,
         unreachable,
         unreachable_code,