Skip to content

Commit 36d7e7f

Browse files
committed
check uniqueness of nested fields
1 parent 879a1e5 commit 36d7e7f

12 files changed

+1840
-95
lines changed

compiler/rustc_ast_passes/src/ast_validation.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ impl<'a> AstValidator<'a> {
220220
}
221221
}
222222
TyKind::AnonStruct(_, ref fields) | TyKind::AnonUnion(_, ref fields) => {
223-
walk_list!(self, visit_field_def, fields)
223+
walk_list!(self, visit_struct_field_def, fields)
224224
}
225225
_ => visit::walk_ty(self, t),
226226
}

compiler/rustc_hir_analysis/messages.ftl

+19
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,25 @@ hir_analysis_field_already_declared =
123123
.label = field already declared
124124
.previous_decl_label = `{$field_name}` first declared here
125125
126+
hir_analysis_field_already_declared_both_nested =
127+
field `{$field_name}` is already declared
128+
.label = field `{$field_name}` declared in this unnamed field
129+
.nested_field_decl_note = field `{$field_name}` declared here
130+
.previous_decl_label = `{$field_name}` first declared here in this unnamed field
131+
.previous_nested_field_decl_note = field `{$field_name}` first declared here
132+
133+
hir_analysis_field_already_declared_current_nested =
134+
field `{$field_name}` is already declared
135+
.label = field `{$field_name}` declared in this unnamed field
136+
.nested_field_decl_note = field `{$field_name}` declared here
137+
.previous_decl_label = `{$field_name}` first declared here
138+
139+
hir_analysis_field_already_declared_previous_nested =
140+
field `{$field_name}` is already declared
141+
.label = field already declared
142+
.previous_decl_label = `{$field_name}` first declared here in this unnamed field
143+
.previous_nested_field_decl_note = field `{$field_name}` first declared here
144+
126145
hir_analysis_function_not_found_in_trait = function not found in this trait
127146
128147
hir_analysis_function_not_have_default_implementation = function doesn't have a default implementation

compiler/rustc_hir_analysis/src/collect.rs

+137-65
Original file line numberDiff line numberDiff line change
@@ -789,64 +789,100 @@ fn convert_enum_variant_types(tcx: TyCtxt<'_>, def_id: DefId) {
789789
}
790790
}
791791

792-
/*
793-
/// In a type definition, we check that unnamed field names are distinct.
794-
fn check_unnamed_fields_defn<'tcx>(tcx: TyCtxt<'tcx>, item: &hir::Item<'tcx>) {
795-
let mut seen_fields: FxHashMap<Ident, Option<Span>> = Default::default();
796-
fn check_fields_anon_adt_defn<'tcx>(tcx: TyCtxt<'tcx>, item: &hir::Item<'tcx>, seen_fields: &mut FxHashMap<Ident, Option<Span>>) {
797-
let fields = match &item.kind {
798-
hir::ItemKind::Struct(fields, _) | hir::ItemKind::Union(fields, _) => fields,
799-
_ => return,
800-
};
801-
for field in fields.fields() {
802-
if field.ident.name == kw::Underscore {
803-
if let hir::TyKind::AnonAdt(item_id) = field.ty.kind() {
804-
let item = tcx.hir().item(item_id);
805-
check_fields_anon_adt_defn(tcx, item, &mut *seen_fields);
806-
} else {
807-
let field_ty = match tcx.type_of(field.def_id).instantiate_identity().ty_adt_def() {
808-
Some(adt_ty) => adt_ty,
809-
None => {
810-
tcx.sess.emit_err(err);
811-
return;
812-
}
813-
};
814-
if let Some(def_id) = field_ty.did().as_local() {
815-
let item = tcx.hir().item(hir::ItemId { owner_id: hir::OwnerId { def_id }});
816-
check_fields_anon_adt_defn(tcx, item, &mut *seen_fields);
817-
}
792+
#[derive(Clone, Copy)]
793+
struct NestedSpan {
794+
span: Span,
795+
nested_field_span: Span,
796+
}
797+
798+
#[derive(Clone, Copy)]
799+
enum FieldDeclSpan {
800+
NotNested(Span),
801+
Nested(NestedSpan),
802+
}
803+
804+
impl From<Span> for FieldDeclSpan {
805+
fn from(span: Span) -> Self {
806+
Self::NotNested(span)
807+
}
808+
}
809+
810+
impl From<NestedSpan> for FieldDeclSpan {
811+
fn from(span: NestedSpan) -> Self {
812+
Self::Nested(span)
813+
}
814+
}
815+
816+
/// Check the uniqueness of fields across adt where there are
817+
/// nested fields imported from an unnamed field.
818+
fn check_field_uniqueness_in_nested_adt(
819+
tcx: TyCtxt<'_>,
820+
adt_def: ty::AdtDef<'_>,
821+
check: &mut impl FnMut(Ident, /* nested_field_span */ Span),
822+
) {
823+
for field in adt_def.all_fields() {
824+
if field.is_unnamed() {
825+
// Here we don't care about the generic parameters, so `instantiate_identity` is enough.
826+
match tcx.type_of(field.did).instantiate_identity().kind() {
827+
ty::Adt(adt_def, _) => {
828+
check_field_uniqueness_in_nested_adt(tcx, *adt_def, &mut *check);
818829
}
819-
field_ty.flags()
820-
let inner_adt_def = field_ty.ty_adt_def().expect("expect an adt");
821-
check_fields_anon_adt_defn(tcx, adt_def, &mut *seen_fields);
822-
} else {
823-
let span = field.did.as_local().map(|did| {
824-
let hir_id = tcx.hir().local_def_id_to_hir_id(did);
825-
tcx.hir().span(hir_id)
826-
});
827-
match seen_fields.get(&ident.normalize_to_macros_2_0()).cloned() {
828-
Some(Some(prev_span)) => {
829-
tcx.sess.emit_err(errors::FieldAlreadyDeclared {
830-
field_name: ident,
831-
span: f.span,
832-
prev_span,
833-
});
834-
}
835-
Some(None) => {
836-
tcx.sess.emit_err(errors::FieldAlreadyDeclared {
837-
field_name: f.ident,
838-
span: f.span,
839-
prev_span,
840-
});
830+
ty_kind => bug!(
831+
"Unexpected ty kind in check_field_uniqueness_in_nested_adt(): {ty_kind:?}"
832+
),
833+
}
834+
} else {
835+
check(field.ident(tcx), tcx.def_span(field.did));
836+
}
837+
}
838+
}
839+
840+
/// Check the uniqueness of fields in a struct variant, and recursively
841+
/// check the nested fields if it is an unnamed field with type of an
842+
/// annoymous adt.
843+
fn check_field_uniqueness(
844+
tcx: TyCtxt<'_>,
845+
field: &hir::FieldDef<'_>,
846+
check: &mut impl FnMut(Ident, FieldDeclSpan),
847+
) {
848+
if field.ident.name == kw::Underscore {
849+
let ty_span = field.ty.span;
850+
match &field.ty.kind {
851+
hir::TyKind::AnonAdt(item_id) => {
852+
match &tcx.hir_node(item_id.hir_id()).expect_item().kind {
853+
hir::ItemKind::Struct(variant_data, ..)
854+
| hir::ItemKind::Union(variant_data, ..) => {
855+
variant_data
856+
.fields()
857+
.iter()
858+
.for_each(|f| check_field_uniqueness(tcx, f, &mut *check));
841859
}
842-
None =>
843-
seen_fields.insert(f.ident.normalize_to_macros_2_0(), f.span);
860+
item_kind => span_bug!(
861+
ty_span,
862+
"Unexpected item kind in check_field_uniqueness(): {item_kind:?}"
863+
),
844864
}
845865
}
866+
hir::TyKind::Path(hir::QPath::Resolved(_, hir::Path { res, .. })) => {
867+
check_field_uniqueness_in_nested_adt(
868+
tcx,
869+
tcx.adt_def(res.def_id()),
870+
&mut |ident, nested_field_span| {
871+
check(ident, NestedSpan { span: field.span, nested_field_span }.into())
872+
},
873+
);
874+
}
875+
// Abort due to errors (there must be an error if an unnamed field
876+
// has any type kind other than an anonymous adt or a named adt)
877+
_ => {
878+
debug_assert!(tcx.sess.has_errors().is_some());
879+
tcx.sess.abort_if_errors()
880+
}
846881
}
882+
return;
847883
}
884+
check(field.ident, field.span.into());
848885
}
849-
*/
850886

851887
fn convert_variant(
852888
tcx: TyCtxt<'_>,
@@ -856,27 +892,61 @@ fn convert_variant(
856892
def: &hir::VariantData<'_>,
857893
adt_kind: ty::AdtKind,
858894
parent_did: LocalDefId,
895+
is_anonymous: bool,
859896
) -> ty::VariantDef {
860897
let mut has_unnamed_fields = false;
861-
let mut seen_fields: FxHashMap<Ident, Span> = Default::default();
898+
let mut seen_fields: FxHashMap<Ident, FieldDeclSpan> = Default::default();
862899
let fields = def
863900
.fields()
864901
.iter()
865902
.inspect(|f| {
866-
// Skip the unnamed field here, we will check it later.
867-
if f.ident.name == kw::Underscore {
868-
has_unnamed_fields = true;
869-
return;
870-
}
871-
let dup_span = seen_fields.get(&f.ident.normalize_to_macros_2_0()).cloned();
872-
if let Some(prev_span) = dup_span {
873-
tcx.dcx().emit_err(errors::FieldAlreadyDeclared {
874-
field_name: f.ident,
875-
span: f.span,
876-
prev_span,
903+
has_unnamed_fields |= f.ident.name == kw::Underscore;
904+
if !is_anonymous {
905+
check_field_uniqueness(tcx, f, &mut |ident, field_decl| {
906+
use FieldDeclSpan::*;
907+
let field_name = ident.name;
908+
let ident = ident.normalize_to_macros_2_0();
909+
match (field_decl, seen_fields.get(&ident).copied()) {
910+
(NotNested(span), Some(NotNested(prev_span))) => {
911+
tcx.sess.emit_err(errors::FieldAlreadyDeclared::NotNested {
912+
field_name,
913+
span,
914+
prev_span,
915+
});
916+
}
917+
(NotNested(span), Some(Nested(prev))) => {
918+
tcx.sess.emit_err(errors::FieldAlreadyDeclared::PreviousNested {
919+
field_name,
920+
span,
921+
prev_span: prev.span,
922+
prev_nested_field_span: prev.nested_field_span,
923+
});
924+
}
925+
(
926+
Nested(NestedSpan { span, nested_field_span }),
927+
Some(NotNested(prev_span)),
928+
) => {
929+
tcx.sess.emit_err(errors::FieldAlreadyDeclared::CurrentNested {
930+
field_name,
931+
span,
932+
nested_field_span,
933+
prev_span,
934+
});
935+
}
936+
(Nested(NestedSpan { span, nested_field_span }), Some(Nested(prev))) => {
937+
tcx.sess.emit_err(errors::FieldAlreadyDeclared::BothNested {
938+
field_name,
939+
span,
940+
nested_field_span,
941+
prev_span: prev.span,
942+
prev_nested_field_span: prev.nested_field_span,
943+
});
944+
}
945+
(field_decl, None) => {
946+
seen_fields.insert(ident, field_decl);
947+
}
948+
}
877949
});
878-
} else {
879-
seen_fields.insert(f.ident.normalize_to_macros_2_0(), f.span);
880950
}
881951
})
882952
.map(|f| ty::FieldDef {
@@ -937,6 +1007,7 @@ fn adt_def(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::AdtDef<'_> {
9371007
&v.data,
9381008
AdtKind::Enum,
9391009
def_id,
1010+
is_anonymous,
9401011
)
9411012
})
9421013
.collect();
@@ -956,6 +1027,7 @@ fn adt_def(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::AdtDef<'_> {
9561027
def,
9571028
adt_kind,
9581029
def_id,
1030+
is_anonymous,
9591031
))
9601032
.collect();
9611033

compiler/rustc_hir_analysis/src/errors.rs

+45-8
Original file line numberDiff line numberDiff line change
@@ -175,14 +175,51 @@ pub struct DropImplOnWrongItem {
175175
}
176176

177177
#[derive(Diagnostic)]
178-
#[diag(hir_analysis_field_already_declared, code = E0124)]
179-
pub struct FieldAlreadyDeclared {
180-
pub field_name: Ident,
181-
#[primary_span]
182-
#[label]
183-
pub span: Span,
184-
#[label(hir_analysis_previous_decl_label)]
185-
pub prev_span: Span,
178+
pub enum FieldAlreadyDeclared {
179+
#[diag(hir_analysis_field_already_declared, code = E0124)]
180+
NotNested {
181+
field_name: Symbol,
182+
#[primary_span]
183+
#[label]
184+
span: Span,
185+
#[label(hir_analysis_previous_decl_label)]
186+
prev_span: Span,
187+
},
188+
#[diag(hir_analysis_field_already_declared_current_nested)]
189+
CurrentNested {
190+
field_name: Symbol,
191+
#[primary_span]
192+
#[label]
193+
span: Span,
194+
#[note(hir_analysis_nested_field_decl_note)]
195+
nested_field_span: Span,
196+
#[label(hir_analysis_previous_decl_label)]
197+
prev_span: Span,
198+
},
199+
#[diag(hir_analysis_field_already_declared_previous_nested)]
200+
PreviousNested {
201+
field_name: Symbol,
202+
#[primary_span]
203+
#[label]
204+
span: Span,
205+
#[label(hir_analysis_previous_decl_label)]
206+
prev_span: Span,
207+
#[note(hir_analysis_previous_nested_field_decl_note)]
208+
prev_nested_field_span: Span,
209+
},
210+
#[diag(hir_analysis_field_already_declared_both_nested)]
211+
BothNested {
212+
field_name: Symbol,
213+
#[primary_span]
214+
#[label]
215+
span: Span,
216+
#[note(hir_analysis_nested_field_decl_note)]
217+
nested_field_span: Span,
218+
#[label(hir_analysis_previous_decl_label)]
219+
prev_span: Span,
220+
#[note(hir_analysis_previous_nested_field_decl_note)]
221+
prev_nested_field_span: Span,
222+
},
186223
}
187224

188225
#[derive(Diagnostic)]

compiler/rustc_middle/src/ty/adt.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,7 @@ impl<'tcx> AdtDef<'tcx> {
401401
}
402402

403403
/// Returns an iterator over all fields contained
404-
/// by this ADT.
404+
/// by this ADT (nested unnamed fields are not expanded).
405405
#[inline]
406406
pub fn all_fields(self) -> impl Iterator<Item = &'tcx FieldDef> + Clone {
407407
self.variants().iter().flat_map(|v| v.fields.iter())

compiler/rustc_middle/src/ty/mod.rs

+5
Original file line numberDiff line numberDiff line change
@@ -1387,6 +1387,11 @@ impl<'tcx> FieldDef {
13871387
pub fn ident(&self, tcx: TyCtxt<'_>) -> Ident {
13881388
Ident::new(self.name, tcx.def_ident_span(self.did).unwrap())
13891389
}
1390+
1391+
/// Returns whether the field is unnamed
1392+
pub fn is_unnamed(&self) -> bool {
1393+
self.name == rustc_span::symbol::kw::Underscore
1394+
}
13901395
}
13911396

13921397
#[derive(Debug, PartialEq, Eq)]

0 commit comments

Comments
 (0)