Skip to content

Commit 18be272

Browse files
committed
Auto merge of rust-lang#115131 - frank-king:feature/unnamed-fields-lite, r=petrochenkov
Parse unnamed fields and anonymous structs or unions (no-recovery) It is part of rust-lang#114782 which implements rust-lang#49804. Only parse anonymous structs or unions in struct field definition positions. r? `@petrochenkov`
2 parents 8a6b67f + 868706d commit 18be272

File tree

25 files changed

+650
-5
lines changed

25 files changed

+650
-5
lines changed

compiler/rustc_ast/src/ast.rs

+4
Original file line numberDiff line numberDiff line change
@@ -2092,6 +2092,10 @@ pub enum TyKind {
20922092
Never,
20932093
/// A tuple (`(A, B, C, D,...)`).
20942094
Tup(ThinVec<P<Ty>>),
2095+
/// An anonymous struct type i.e. `struct { foo: Type }`
2096+
AnonStruct(ThinVec<FieldDef>),
2097+
/// An anonymous union type i.e. `union { bar: Type }`
2098+
AnonUnion(ThinVec<FieldDef>),
20952099
/// A path (`module::module::...::Type`), optionally
20962100
/// "qualified", e.g., `<Vec<T> as SomeTrait>::SomeType`.
20972101
///

compiler/rustc_ast/src/mut_visit.rs

+3
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,9 @@ pub fn noop_visit_ty<T: MutVisitor>(ty: &mut P<Ty>, vis: &mut T) {
510510
visit_vec(bounds, |bound| vis.visit_param_bound(bound));
511511
}
512512
TyKind::MacCall(mac) => vis.visit_mac_call(mac),
513+
TyKind::AnonStruct(fields) | TyKind::AnonUnion(fields) => {
514+
fields.flat_map_in_place(|field| vis.flat_map_field_def(field));
515+
}
513516
}
514517
vis.visit_span(span);
515518
visit_lazy_tts(tokens, vis);

compiler/rustc_ast/src/token.rs

+2
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,8 @@ impl Token {
486486
Lt | BinOp(Shl) | // associated path
487487
ModSep => true, // global path
488488
Interpolated(ref nt) => matches!(**nt, NtTy(..) | NtPath(..)),
489+
// For anonymous structs or unions, which only appear in specific positions
490+
// (type of struct fields or union fields), we don't consider them as regular types
489491
_ => false,
490492
}
491493
}

compiler/rustc_ast/src/visit.rs

+3
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,9 @@ pub fn walk_ty<'a, V: Visitor<'a>>(visitor: &mut V, typ: &'a Ty) {
438438
TyKind::Infer | TyKind::ImplicitSelf | TyKind::Err => {}
439439
TyKind::MacCall(mac) => visitor.visit_mac_call(mac),
440440
TyKind::Never | TyKind::CVarArgs => {}
441+
TyKind::AnonStruct(ref fields, ..) | TyKind::AnonUnion(ref fields, ..) => {
442+
walk_list!(visitor, visit_field_def, fields)
443+
}
441444
}
442445
}
443446

compiler/rustc_ast_lowering/src/lib.rs

+12
Original file line numberDiff line numberDiff line change
@@ -1293,6 +1293,18 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
12931293
TyKind::Err => {
12941294
hir::TyKind::Err(self.tcx.sess.delay_span_bug(t.span, "TyKind::Err lowered"))
12951295
}
1296+
// FIXME(unnamed_fields): IMPLEMENTATION IN PROGRESS
1297+
#[allow(rustc::untranslatable_diagnostic)]
1298+
#[allow(rustc::diagnostic_outside_of_impl)]
1299+
TyKind::AnonStruct(ref _fields) => hir::TyKind::Err(
1300+
self.tcx.sess.span_err(t.span, "anonymous structs are unimplemented"),
1301+
),
1302+
// FIXME(unnamed_fields): IMPLEMENTATION IN PROGRESS
1303+
#[allow(rustc::untranslatable_diagnostic)]
1304+
#[allow(rustc::diagnostic_outside_of_impl)]
1305+
TyKind::AnonUnion(ref _fields) => hir::TyKind::Err(
1306+
self.tcx.sess.span_err(t.span, "anonymous unions are unimplemented"),
1307+
),
12961308
TyKind::Slice(ty) => hir::TyKind::Slice(self.lower_ty(ty, itctx)),
12971309
TyKind::Ptr(mt) => hir::TyKind::Ptr(self.lower_mt(mt, itctx)),
12981310
TyKind::Ref(region, mt) => {

compiler/rustc_ast_passes/messages.ftl

+12
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
ast_passes_anon_struct_or_union_not_allowed =
2+
anonymous {$struct_or_union}s are not allowed outside of unnamed struct or union fields
3+
.label = anonymous {$struct_or_union} declared here
4+
15
ast_passes_assoc_const_without_body =
26
associated constant in `impl` without body
37
.suggestion = provide a definition for the constant
@@ -162,6 +166,14 @@ ast_passes_inherent_cannot_be = inherent impls cannot be {$annotation}
162166
ast_passes_invalid_label =
163167
invalid label name `{$name}`
164168
169+
ast_passes_invalid_unnamed_field =
170+
unnamed fields are not allowed outside of structs or unions
171+
.label = unnamed field declared here
172+
173+
ast_passes_invalid_unnamed_field_ty =
174+
unnamed fields can only have struct or union types
175+
.label = not a struct or union
176+
165177
ast_passes_item_underscore = `{$kind}` items in this context need a name
166178
.label = `_` is not a valid name for this `{$kind}` item
167179

compiler/rustc_ast_passes/src/ast_validation.rs

+84-1
Original file line numberDiff line numberDiff line change
@@ -223,10 +223,27 @@ impl<'a> AstValidator<'a> {
223223
}
224224
}
225225
}
226+
TyKind::AnonStruct(ref fields, ..) | TyKind::AnonUnion(ref fields, ..) => {
227+
walk_list!(self, visit_field_def, fields)
228+
}
226229
_ => visit::walk_ty(self, t),
227230
}
228231
}
229232

233+
fn visit_struct_field_def(&mut self, field: &'a FieldDef) {
234+
if let Some(ident) = field.ident &&
235+
ident.name == kw::Underscore {
236+
self.check_unnamed_field_ty(&field.ty, ident.span);
237+
self.visit_vis(&field.vis);
238+
self.visit_ident(ident);
239+
self.visit_ty_common(&field.ty);
240+
self.walk_ty(&field.ty);
241+
walk_list!(self, visit_attribute, &field.attrs);
242+
} else {
243+
self.visit_field_def(field);
244+
}
245+
}
246+
230247
fn err_handler(&self) -> &rustc_errors::Handler {
231248
&self.session.diagnostic()
232249
}
@@ -264,6 +281,42 @@ impl<'a> AstValidator<'a> {
264281
}
265282
}
266283

284+
fn check_unnamed_field_ty(&self, ty: &Ty, span: Span) {
285+
if matches!(
286+
&ty.kind,
287+
// We already checked for `kw::Underscore` before calling this function,
288+
// so skip the check
289+
TyKind::AnonStruct(..) | TyKind::AnonUnion(..)
290+
// If the anonymous field contains a Path as type, we can't determine
291+
// if the path is a valid struct or union, so skip the check
292+
| TyKind::Path(..)
293+
) {
294+
return;
295+
}
296+
self.err_handler().emit_err(errors::InvalidUnnamedFieldTy { span, ty_span: ty.span });
297+
}
298+
299+
fn deny_anon_struct_or_union(&self, ty: &Ty) {
300+
let struct_or_union = match &ty.kind {
301+
TyKind::AnonStruct(..) => "struct",
302+
TyKind::AnonUnion(..) => "union",
303+
_ => return,
304+
};
305+
self.err_handler()
306+
.emit_err(errors::AnonStructOrUnionNotAllowed { struct_or_union, span: ty.span });
307+
}
308+
309+
fn deny_unnamed_field(&self, field: &FieldDef) {
310+
if let Some(ident) = field.ident &&
311+
ident.name == kw::Underscore {
312+
self.err_handler()
313+
.emit_err(errors::InvalidUnnamedField {
314+
span: field.span,
315+
ident_span: ident.span
316+
});
317+
}
318+
}
319+
267320
fn check_trait_fn_not_const(&self, constness: Const) {
268321
if let Const::Yes(span) = constness {
269322
self.session.emit_err(errors::TraitFnConst { span });
@@ -789,6 +842,7 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
789842

790843
fn visit_ty(&mut self, ty: &'a Ty) {
791844
self.visit_ty_common(ty);
845+
self.deny_anon_struct_or_union(ty);
792846
self.walk_ty(ty)
793847
}
794848

@@ -803,6 +857,7 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
803857
}
804858

805859
fn visit_field_def(&mut self, field: &'a FieldDef) {
860+
self.deny_unnamed_field(field);
806861
visit::walk_field_def(self, field)
807862
}
808863

@@ -995,10 +1050,38 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
9951050
self.check_mod_file_item_asciionly(item.ident);
9961051
}
9971052
}
998-
ItemKind::Union(vdata, ..) => {
1053+
ItemKind::Struct(vdata, generics) => match vdata {
1054+
// Duplicating the `Visitor` logic allows catching all cases
1055+
// of `Anonymous(Struct, Union)` outside of a field struct or union.
1056+
//
1057+
// Inside `visit_ty` the validator catches every `Anonymous(Struct, Union)` it
1058+
// encounters, and only on `ItemKind::Struct` and `ItemKind::Union`
1059+
// it uses `visit_ty_common`, which doesn't contain that specific check.
1060+
VariantData::Struct(fields, ..) => {
1061+
self.visit_vis(&item.vis);
1062+
self.visit_ident(item.ident);
1063+
self.visit_generics(generics);
1064+
walk_list!(self, visit_struct_field_def, fields);
1065+
walk_list!(self, visit_attribute, &item.attrs);
1066+
return;
1067+
}
1068+
_ => {}
1069+
},
1070+
ItemKind::Union(vdata, generics) => {
9991071
if vdata.fields().is_empty() {
10001072
self.err_handler().emit_err(errors::FieldlessUnion { span: item.span });
10011073
}
1074+
match vdata {
1075+
VariantData::Struct(fields, ..) => {
1076+
self.visit_vis(&item.vis);
1077+
self.visit_ident(item.ident);
1078+
self.visit_generics(generics);
1079+
walk_list!(self, visit_struct_field_def, fields);
1080+
walk_list!(self, visit_attribute, &item.attrs);
1081+
return;
1082+
}
1083+
_ => {}
1084+
}
10021085
}
10031086
ItemKind::Const(box ConstItem { defaultness, expr: None, .. }) => {
10041087
self.check_defaultness(item.span, *defaultness);

compiler/rustc_ast_passes/src/errors.rs

+27
Original file line numberDiff line numberDiff line change
@@ -727,3 +727,30 @@ pub struct ConstraintOnNegativeBound {
727727
#[primary_span]
728728
pub span: Span,
729729
}
730+
731+
#[derive(Diagnostic)]
732+
#[diag(ast_passes_invalid_unnamed_field_ty)]
733+
pub struct InvalidUnnamedFieldTy {
734+
#[primary_span]
735+
pub span: Span,
736+
#[label]
737+
pub ty_span: Span,
738+
}
739+
740+
#[derive(Diagnostic)]
741+
#[diag(ast_passes_invalid_unnamed_field)]
742+
pub struct InvalidUnnamedField {
743+
#[primary_span]
744+
pub span: Span,
745+
#[label]
746+
pub ident_span: Span,
747+
}
748+
749+
#[derive(Diagnostic)]
750+
#[diag(ast_passes_anon_struct_or_union_not_allowed)]
751+
pub struct AnonStructOrUnionNotAllowed {
752+
#[primary_span]
753+
#[label]
754+
pub span: Span,
755+
pub struct_or_union: &'static str,
756+
}

compiler/rustc_ast_passes/src/feature_gate.rs

+1
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,7 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session, features: &Features) {
570570
gate_all!(builtin_syntax, "`builtin #` syntax is unstable");
571571
gate_all!(explicit_tail_calls, "`become` expression is experimental");
572572
gate_all!(generic_const_items, "generic const items are experimental");
573+
gate_all!(unnamed_fields, "unnamed fields are not yet fully implemented");
573574

574575
if !visitor.features.negative_bounds {
575576
for &span in spans.get(&sym::negative_bounds).iter().copied().flatten() {

compiler/rustc_ast_pretty/src/pprust/state.rs

+8
Original file line numberDiff line numberDiff line change
@@ -1064,6 +1064,14 @@ impl<'a> State<'a> {
10641064
}
10651065
self.pclose();
10661066
}
1067+
ast::TyKind::AnonStruct(fields) => {
1068+
self.head("struct");
1069+
self.print_record_struct_body(&fields, ty.span);
1070+
}
1071+
ast::TyKind::AnonUnion(fields) => {
1072+
self.head("union");
1073+
self.print_record_struct_body(&fields, ty.span);
1074+
}
10671075
ast::TyKind::Paren(typ) => {
10681076
self.popen();
10691077
self.print_type(typ);

compiler/rustc_ast_pretty/src/pprust/state/item.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,11 @@ impl<'a> State<'a> {
443443
}
444444
}
445445

446-
fn print_record_struct_body(&mut self, fields: &[ast::FieldDef], span: rustc_span::Span) {
446+
pub(crate) fn print_record_struct_body(
447+
&mut self,
448+
fields: &[ast::FieldDef],
449+
span: rustc_span::Span,
450+
) {
447451
self.nbsp();
448452
self.bopen();
449453

compiler/rustc_feature/src/active.rs

+2
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,8 @@ declare_features! (
584584
(active, type_privacy_lints, "1.72.0", Some(48054), None),
585585
/// Enables rustc to generate code that instructs libstd to NOT ignore SIGPIPE.
586586
(active, unix_sigpipe, "1.65.0", Some(97889), None),
587+
/// Allows unnamed fields of struct and union type
588+
(incomplete, unnamed_fields, "CURRENT_RUSTC_VERSION", Some(49804), None),
587589
/// Allows unsized fn parameters.
588590
(active, unsized_fn_params, "1.49.0", Some(48055), None),
589591
/// Allows unsized rvalues at arguments and parameters.

compiler/rustc_parse/src/parser/item.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -1594,7 +1594,7 @@ impl<'a> Parser<'a> {
15941594
Ok((class_name, ItemKind::Union(vdata, generics)))
15951595
}
15961596

1597-
fn parse_record_struct_body(
1597+
pub(crate) fn parse_record_struct_body(
15981598
&mut self,
15991599
adt_ty: &str,
16001600
ident_span: Span,
@@ -1869,7 +1869,7 @@ impl<'a> Parser<'a> {
18691869
}
18701870
}
18711871
self.expect_field_ty_separator()?;
1872-
let ty = self.parse_ty()?;
1872+
let ty = self.parse_ty_for_field_def()?;
18731873
if self.token.kind == token::Colon && self.look_ahead(1, |tok| tok.kind != token::Colon) {
18741874
self.sess.emit_err(errors::SingleColonStructType { span: self.token.span });
18751875
}
@@ -1894,7 +1894,9 @@ impl<'a> Parser<'a> {
18941894
/// for better diagnostics and suggestions.
18951895
fn parse_field_ident(&mut self, adt_ty: &str, lo: Span) -> PResult<'a, Ident> {
18961896
let (ident, is_raw) = self.ident_or_err(true)?;
1897-
if !is_raw && ident.is_reserved() {
1897+
if ident.name == kw::Underscore {
1898+
self.sess.gated_spans.gate(sym::unnamed_fields, lo);
1899+
} else if !is_raw && ident.is_reserved() {
18981900
let snapshot = self.create_snapshot_for_diagnostic();
18991901
let err = if self.check_fn_front_matter(false, Case::Sensitive) {
19001902
let inherited_vis = Visibility {

compiler/rustc_parse/src/parser/ty.rs

+46
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,17 @@ impl<'a> Parser<'a> {
136136
)
137137
}
138138

139+
/// Parse a type suitable for a field defintion.
140+
/// The difference from `parse_ty` is that this version
141+
/// allows anonymous structs and unions.
142+
pub fn parse_ty_for_field_def(&mut self) -> PResult<'a, P<Ty>> {
143+
if self.can_begin_anon_struct_or_union() {
144+
self.parse_anon_struct_or_union()
145+
} else {
146+
self.parse_ty()
147+
}
148+
}
149+
139150
/// Parse a type suitable for a function or function pointer parameter.
140151
/// The difference from `parse_ty` is that this version allows `...`
141152
/// (`CVarArgs`) at the top level of the type.
@@ -336,6 +347,36 @@ impl<'a> Parser<'a> {
336347
if allow_qpath_recovery { self.maybe_recover_from_bad_qpath(ty) } else { Ok(ty) }
337348
}
338349

350+
/// Parse an anonymous struct or union (only for field definitions):
351+
/// ```ignore (feature-not-ready)
352+
/// #[repr(C)]
353+
/// struct Foo {
354+
/// _: struct { // anonymous struct
355+
/// x: u32,
356+
/// y: f64,
357+
/// }
358+
/// _: union { // anonymous union
359+
/// z: u32,
360+
/// w: f64,
361+
/// }
362+
/// }
363+
/// ```
364+
fn parse_anon_struct_or_union(&mut self) -> PResult<'a, P<Ty>> {
365+
assert!(self.token.is_keyword(kw::Union) || self.token.is_keyword(kw::Struct));
366+
let is_union = self.token.is_keyword(kw::Union);
367+
368+
let lo = self.token.span;
369+
self.bump();
370+
371+
let (fields, _recovered) =
372+
self.parse_record_struct_body(if is_union { "union" } else { "struct" }, lo, false)?;
373+
let span = lo.to(self.prev_token.span);
374+
self.sess.gated_spans.gate(sym::unnamed_fields, span);
375+
// These can be rejected during AST validation in `deny_anon_struct_or_union`.
376+
let kind = if is_union { TyKind::AnonUnion(fields) } else { TyKind::AnonStruct(fields) };
377+
Ok(self.mk_ty(span, kind))
378+
}
379+
339380
/// Parses either:
340381
/// - `(TYPE)`, a parenthesized type.
341382
/// - `(TYPE,)`, a tuple with a single field of type TYPE.
@@ -696,6 +737,11 @@ impl<'a> Parser<'a> {
696737
Ok(bounds)
697738
}
698739

740+
pub(super) fn can_begin_anon_struct_or_union(&mut self) -> bool {
741+
(self.token.is_keyword(kw::Struct) || self.token.is_keyword(kw::Union))
742+
&& self.look_ahead(1, |t| t == &token::OpenDelim(Delimiter::Brace))
743+
}
744+
699745
/// Can the current token begin a bound?
700746
fn can_begin_bound(&mut self) -> bool {
701747
// This needs to be synchronized with `TokenKind::can_begin_bound`.

compiler/rustc_passes/src/hir_stats.rs

+2
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,8 @@ impl<'v> ast_visit::Visitor<'v> for StatCollector<'v> {
587587
BareFn,
588588
Never,
589589
Tup,
590+
AnonStruct,
591+
AnonUnion,
590592
Path,
591593
TraitObject,
592594
ImplTrait,

compiler/rustc_span/src/symbol.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1618,6 +1618,7 @@ symbols! {
16181618
unix_sigpipe,
16191619
unlikely,
16201620
unmarked_api,
1621+
unnamed_fields,
16211622
unpin,
16221623
unreachable,
16231624
unreachable_2015,

0 commit comments

Comments
 (0)