@@ -1722,6 +1722,57 @@ fn structInitExpr(
17221722 }
17231723 }
17241724
1725+ {
1726+ var sfba = std .heap .stackFallback (256 , astgen .arena );
1727+ const sfba_allocator = sfba .get ();
1728+
1729+ var duplicate_names = std .AutoArrayHashMap (u32 , ArrayListUnmanaged (Ast .TokenIndex )).init (sfba_allocator );
1730+ defer duplicate_names .deinit ();
1731+ try duplicate_names .ensureTotalCapacity (@intCast (struct_init .ast .fields .len ));
1732+
1733+ // When there aren't errors, use this to avoid a second iteration.
1734+ var any_duplicate = false ;
1735+
1736+ for (struct_init .ast .fields ) | field | {
1737+ const name_token = tree .firstToken (field ) - 2 ;
1738+ const name_index = try astgen .identAsString (name_token );
1739+
1740+ const gop = try duplicate_names .getOrPut (name_index );
1741+
1742+ if (gop .found_existing ) {
1743+ try gop .value_ptr .append (sfba_allocator , name_token );
1744+ any_duplicate = true ;
1745+ } else {
1746+ gop .value_ptr .* = .{};
1747+ try gop .value_ptr .append (sfba_allocator , name_token );
1748+ }
1749+ }
1750+
1751+ if (any_duplicate ) {
1752+ var it = duplicate_names .iterator ();
1753+
1754+ while (it .next ()) | entry | {
1755+ const record = entry .value_ptr .* ;
1756+ if (record .items .len > 1 ) {
1757+ var error_notes = std .ArrayList (u32 ).init (astgen .arena );
1758+
1759+ for (record .items [1.. ]) | duplicate | {
1760+ try error_notes .append (try astgen .errNoteTok (duplicate , "other field here" , .{}));
1761+ }
1762+
1763+ try astgen .appendErrorTokNotes (
1764+ record .items [0 ],
1765+ "duplicate field" ,
1766+ .{},
1767+ error_notes .items ,
1768+ );
1769+ }
1770+ }
1771+
1772+ return error .AnalysisFail ;
1773+ }
1774+ }
1775+
17251776 if (struct_init .ast .type_expr != 0 ) {
17261777 // Typed inits do not use RLS for language simplicity.
17271778 const ty_inst = try typeExpr (gz , scope , struct_init .ast .type_expr );
@@ -4880,6 +4931,15 @@ fn structDeclInner(
48804931 }
48814932 };
48824933
4934+ var sfba = std .heap .stackFallback (256 , astgen .arena );
4935+ const sfba_allocator = sfba .get ();
4936+
4937+ var duplicate_names = std .AutoArrayHashMap (u32 , std .ArrayListUnmanaged (Ast .TokenIndex )).init (sfba_allocator );
4938+ try duplicate_names .ensureTotalCapacity (field_count );
4939+
4940+ // When there aren't errors, use this to avoid a second iteration.
4941+ var any_duplicate = false ;
4942+
48834943 var known_non_opv = false ;
48844944 var known_comptime_only = false ;
48854945 var any_comptime_fields = false ;
@@ -4892,11 +4952,21 @@ fn structDeclInner(
48924952 };
48934953
48944954 if (! is_tuple ) {
4955+ const field_name = try astgen .identAsString (member .ast .main_token );
48954956 member .convertToNonTupleLike (astgen .tree .nodes );
48964957 assert (! member .ast .tuple_like );
48974958
4898- const field_name = try astgen .identAsString (member .ast .main_token );
48994959 wip_members .appendToField (field_name );
4960+
4961+ const gop = try duplicate_names .getOrPut (field_name );
4962+
4963+ if (gop .found_existing ) {
4964+ try gop .value_ptr .append (sfba_allocator , member .ast .main_token );
4965+ any_duplicate = true ;
4966+ } else {
4967+ gop .value_ptr .* = .{};
4968+ try gop .value_ptr .append (sfba_allocator , member .ast .main_token );
4969+ }
49004970 } else if (! member .ast .tuple_like ) {
49014971 return astgen .failTok (member .ast .main_token , "tuple field has a name" , .{});
49024972 }
@@ -4978,6 +5048,34 @@ fn structDeclInner(
49785048 }
49795049 }
49805050
5051+ if (any_duplicate ) {
5052+ var it = duplicate_names .iterator ();
5053+
5054+ while (it .next ()) | entry | {
5055+ const record = entry .value_ptr .* ;
5056+ if (record .items .len > 1 ) {
5057+ var error_notes = std .ArrayList (u32 ).init (astgen .arena );
5058+
5059+ for (record .items [1.. ]) | duplicate | {
5060+ try error_notes .append (try astgen .errNoteTok (duplicate , "other field here" , .{}));
5061+ }
5062+
5063+ try error_notes .append (try astgen .errNoteNode (node , "struct declared here" , .{}));
5064+
5065+ try astgen .appendErrorTokNotes (
5066+ record .items [0 ],
5067+ "duplicate struct field: '{s}'" ,
5068+ .{try astgen .identifierTokenString (record .items [0 ])},
5069+ error_notes .items ,
5070+ );
5071+ }
5072+ }
5073+
5074+ return error .AnalysisFail ;
5075+ }
5076+
5077+ duplicate_names .deinit ();
5078+
49815079 try gz .setStruct (decl_inst , .{
49825080 .src_node = node ,
49835081 .layout = layout ,
0 commit comments