Commit 941090d94f

David <87927264+Rexicon226@users.noreply.github.com>
2023-11-16 15:38:16
Move duplicate field detection for struct init expressions into AstGen
Partially addresses #17916.
1 parent acf9de3
lib/std/os/linux/sparc64.zig
@@ -445,7 +445,7 @@ pub const ucontext_t = extern struct {
     sigmask: u64,
     mcontext: mcontext_t,
     stack: stack_t,
-    sigmask: sigset_t,
+    sigset: sigset_t,
 };
 
 pub const rlimit_resource = enum(c_int) {
src/AstGen.zig
@@ -1722,6 +1722,57 @@ fn structInitExpr(
         }
     }
 
+    {
+        var sfba = std.heap.stackFallback(256, astgen.arena);
+        const sfba_allocator = sfba.get();
+
+        var duplicate_names = std.AutoArrayHashMap(u32, ArrayListUnmanaged(Ast.TokenIndex)).init(sfba_allocator);
+        defer duplicate_names.deinit();
+        try duplicate_names.ensureTotalCapacity(@intCast(struct_init.ast.fields.len));
+
+        // When there aren't errors, use this to avoid a second iteration.
+        var any_duplicate = false;
+
+        for (struct_init.ast.fields) |field| {
+            const name_token = tree.firstToken(field) - 2;
+            const name_index = try astgen.identAsString(name_token);
+
+            const gop = try duplicate_names.getOrPut(name_index);
+
+            if (gop.found_existing) {
+                try gop.value_ptr.append(sfba_allocator, name_token);
+                any_duplicate = true;
+            } else {
+                gop.value_ptr.* = .{};
+                try gop.value_ptr.append(sfba_allocator, name_token);
+            }
+        }
+
+        if (any_duplicate) {
+            var it = duplicate_names.iterator();
+
+            while (it.next()) |entry| {
+                const record = entry.value_ptr.*;
+                if (record.items.len > 1) {
+                    var error_notes = std.ArrayList(u32).init(astgen.arena);
+
+                    for (record.items[1..]) |duplicate| {
+                        try error_notes.append(try astgen.errNoteTok(duplicate, "other field here", .{}));
+                    }
+
+                    try astgen.appendErrorTokNotes(
+                        record.items[0],
+                        "duplicate field",
+                        .{},
+                        error_notes.items,
+                    );
+                }
+            }
+
+            return error.AnalysisFail;
+        }
+    }
+
     if (struct_init.ast.type_expr != 0) {
         // Typed inits do not use RLS for language simplicity.
         const ty_inst = try typeExpr(gz, scope, struct_init.ast.type_expr);
@@ -4874,6 +4925,15 @@ fn structDeclInner(
         }
     };
 
+    var sfba = std.heap.stackFallback(256, astgen.arena);
+    const sfba_allocator = sfba.get();
+
+    var duplicate_names = std.AutoArrayHashMap(u32, std.ArrayListUnmanaged(Ast.TokenIndex)).init(sfba_allocator);
+    try duplicate_names.ensureTotalCapacity(field_count);
+
+    // When there aren't errors, use this to avoid a second iteration.
+    var any_duplicate = false;
+
     var known_non_opv = false;
     var known_comptime_only = false;
     var any_comptime_fields = false;
@@ -4886,11 +4946,22 @@ fn structDeclInner(
         };
 
         if (!is_tuple) {
+            const field_name = try astgen.identAsString(member.ast.main_token);
+
             member.convertToNonTupleLike(astgen.tree.nodes);
             assert(!member.ast.tuple_like);
 
-            const field_name = try astgen.identAsString(member.ast.main_token);
             wip_members.appendToField(field_name);
+
+            const gop = try duplicate_names.getOrPut(field_name);
+
+            if (gop.found_existing) {
+                try gop.value_ptr.append(sfba_allocator, member.ast.main_token);
+                any_duplicate = true;
+            } else {
+                gop.value_ptr.* = .{};
+                try gop.value_ptr.append(sfba_allocator, member.ast.main_token);
+            }
         } else if (!member.ast.tuple_like) {
             return astgen.failTok(member.ast.main_token, "tuple field has a name", .{});
         }
@@ -4975,6 +5046,34 @@ fn structDeclInner(
         }
     }
 
+    if (any_duplicate) {
+        var it = duplicate_names.iterator();
+
+        while (it.next()) |entry| {
+            const record = entry.value_ptr.*;
+            if (record.items.len > 1) {
+                var error_notes = std.ArrayList(u32).init(astgen.arena);
+
+                for (record.items[1..]) |duplicate| {
+                    try error_notes.append(try astgen.errNoteTok(duplicate, "other field here", .{}));
+                }
+
+                try error_notes.append(try astgen.errNoteNode(node, "struct declared here", .{}));
+
+                try astgen.appendErrorTokNotes(
+                    record.items[0],
+                    "duplicate struct field: '{s}'",
+                    .{try astgen.identifierTokenString(record.items[0])},
+                    error_notes.items,
+                );
+            }
+        }
+
+        return error.AnalysisFail;
+    }
+
+    duplicate_names.deinit();
+
     try gz.setStruct(decl_inst, .{
         .src_node = node,
         .layout = layout,
src/Sema.zig
@@ -4690,17 +4690,7 @@ fn validateStructInit(
             try sema.tupleFieldIndex(block, struct_ty, field_name, field_src)
         else
             try sema.structFieldIndex(block, struct_ty, field_name, field_src);
-        if (found_fields[field_index.*].unwrap()) |other_field_ptr| {
-            const other_field_ptr_data = sema.code.instructions.items(.data)[@intFromEnum(other_field_ptr)].pl_node;
-            const other_field_src: LazySrcLoc = .{ .node_offset_initializer = other_field_ptr_data.src_node };
-            const msg = msg: {
-                const msg = try sema.errMsg(block, field_src, "duplicate field", .{});
-                errdefer msg.destroy(gpa);
-                try sema.errNote(block, other_field_src, msg, "other field here", .{});
-                break :msg msg;
-            };
-            return sema.failWithOwnedErrorMsg(block, msg);
-        }
+        assert(found_fields[field_index.*] == .none);
         found_fields[field_index.*] = field_ptr.toOptional();
     }
 
@@ -19222,18 +19212,7 @@ fn zirStructInit(
                 try sema.tupleFieldIndex(block, resolved_ty, field_name, field_src)
             else
                 try sema.structFieldIndex(block, resolved_ty, field_name, field_src);
-            if (field_inits[field_index] != .none) {
-                const other_field_type = found_fields[field_index];
-                const other_field_type_data = zir_datas[@intFromEnum(other_field_type)].pl_node;
-                const other_field_src: LazySrcLoc = .{ .node_offset_initializer = other_field_type_data.src_node };
-                const msg = msg: {
-                    const msg = try sema.errMsg(block, field_src, "duplicate field", .{});
-                    errdefer msg.destroy(gpa);
-                    try sema.errNote(block, other_field_src, msg, "other field here", .{});
-                    break :msg msg;
-                };
-                return sema.failWithOwnedErrorMsg(block, msg);
-            }
+            assert(field_inits[field_index] == .none);
             found_fields[field_index] = item.data.field_type;
             const uncoerced_init = try sema.resolveInst(item.data.init);
             const field_ty = resolved_ty.structFieldType(field_index, mod);
@@ -19533,16 +19512,13 @@ fn structInitAnon(
 
     const types = try sema.arena.alloc(InternPool.Index, extra_data.fields_len);
     const values = try sema.arena.alloc(InternPool.Index, types.len);
-
-    var fields = std.AutoArrayHashMap(InternPool.NullTerminatedString, u32).init(sema.arena);
-    try fields.ensureUnusedCapacity(types.len);
+    const names = try sema.arena.alloc(InternPool.NullTerminatedString, types.len);
 
     // Find which field forces the expression to be runtime, if any.
     const opt_runtime_index = rs: {
         var runtime_index: ?usize = null;
         var extra_index = extra_end;
-        for (types, 0..) |*field_ty, i_usize| {
-            const i: u32 = @intCast(i_usize);
+        for (types, values, names, 0..) |*field_ty, *field_val, *field_name, i_usize| {
             const item = switch (kind) {
                 .anon_init => sema.code.extraData(Zir.Inst.StructInitAnon.Item, extra_index),
                 .typed_init => sema.code.extraData(Zir.Inst.StructInit.Item, extra_index),
@@ -19558,29 +19534,16 @@ fn structInitAnon(
                     break :name sema.code.nullTerminatedString(field_type_extra.data.name_start);
                 },
             };
-            const name_ip = try mod.intern_pool.getOrPutString(gpa, name);
-            const gop = fields.getOrPutAssumeCapacity(name_ip);
-            if (gop.found_existing) {
-                const msg = msg: {
-                    const decl = mod.declPtr(block.src_decl);
-                    const field_src = mod.initSrc(src.node_offset.x, decl, i);
-                    const msg = try sema.errMsg(block, field_src, "duplicate field", .{});
-                    errdefer msg.destroy(gpa);
 
-                    const prev_source = mod.initSrc(src.node_offset.x, decl, gop.value_ptr.*);
-                    try sema.errNote(block, prev_source, msg, "other field here", .{});
-                    break :msg msg;
-                };
-                return sema.failWithOwnedErrorMsg(block, msg);
-            }
-            gop.value_ptr.* = i;
+            const name_ip = try mod.intern_pool.getOrPutString(gpa, name);
+            field_name.* = name_ip;
 
             const init = try sema.resolveInst(item.data.init);
             field_ty.* = sema.typeOf(init).toIntern();
             if (field_ty.toType().zigTypeTag(mod) == .Opaque) {
                 const msg = msg: {
                     const decl = mod.declPtr(block.src_decl);
-                    const field_src = mod.initSrc(src.node_offset.x, decl, i);
+                    const field_src = mod.initSrc(src.node_offset.x, decl, @intCast(i_usize));
                     const msg = try sema.errMsg(block, field_src, "opaque types have unknown size and therefore cannot be directly embedded in structs", .{});
                     errdefer msg.destroy(sema.gpa);
 
@@ -19590,17 +19553,17 @@ fn structInitAnon(
                 return sema.failWithOwnedErrorMsg(block, msg);
             }
             if (try sema.resolveValue(init)) |init_val| {
-                values[i] = try init_val.intern(field_ty.toType(), mod);
+                field_val.* = try init_val.intern(field_ty.toType(), mod);
             } else {
-                values[i] = .none;
-                runtime_index = i;
+                field_val.* = .none;
+                runtime_index = @intCast(i_usize);
             }
         }
         break :rs runtime_index;
     };
 
     const tuple_ty = try ip.getAnonStructType(gpa, .{
-        .names = fields.keys(),
+        .names = names,
         .types = types,
         .values = values,
     });
test/cases/compile_errors/duplicate_field_in_anonymous_struct_literal.zig
@@ -14,5 +14,5 @@ export fn entry() void {
 // backend=stage2
 // target=native
 //
-// :7:16: error: duplicate field
-// :4:16: note: other field here
+// :4:14: error: duplicate field
+// :7:14: note: other field here
test/cases/compile_errors/duplicate_field_in_discarded_anon_init.zig
@@ -6,5 +6,5 @@ pub export fn entry() void {
 // backend=stage2
 // target=native
 //
-// :2:21: error: duplicate field
-// :2:13: note: other field here
+// :2:13: error: duplicate field
+// :2:21: note: other field here
test/cases/compile_errors/duplicate_field_in_struct_value_expression.zig
@@ -17,5 +17,5 @@ export fn f() void {
 // backend=stage2
 // target=native
 //
-// :11:10: error: duplicate field
-// :8:10: note: other field here
+// :8:10: error: duplicate field
+// :11:10: note: other field here
test/cases/compile_errors/duplicate_struct_field.zig
@@ -2,15 +2,32 @@ const Foo = struct {
     Bar: i32,
     Bar: usize,
 };
-export fn entry() void {
-    const a: Foo = undefined;
-    _ = a;
+
+const S = struct {
+    a: u32,
+    b: u32,
+    a: u32,
+    a: u64,
+};
+
+export fn a() void {
+    const f: Foo = undefined;
+    _ = f;
+}
+
+export fn b() void {
+    const s: S = undefined;
+    _ = s;
 }
 
 // error
 // backend=stage2
 // target=native
 //
-// :3:5: error: duplicate struct field: 'Bar'
-// :2:5: note: other field here
+// :2:5: error: duplicate struct field: 'Bar'
+// :3:5: note: other field here
 // :1:13: note: struct declared here
+// :7:5: error: duplicate struct field: 'a'
+// :9:5: note: other field here
+// :10:5: note: other field here
+// :6:11: note: struct declared here
test/cases/compile_errors/error_in_struct_initializer_doesnt_crash_the_compiler.zig
@@ -11,6 +11,6 @@ pub export fn entry() void {
 // backend=stage2
 // target=native
 //
-// :4:9: error: duplicate struct field: 'e'
-// :3:9: note: other field here
+// :3:9: error: duplicate struct field: 'e'
+// :4:9: note: other field here
 // :2:22: note: struct declared here
test/cases/compile_errors/struct_duplicate_field_name.zig
@@ -11,6 +11,6 @@ export fn entry() void {
 // error
 // target=native
 //
-// :3:5: error: duplicate struct field: 'foo'
-// :2:5: note: other field here
+// :2:5: error: duplicate struct field: 'foo'
+// :3:5: note: other field here
 // :1:11: note: struct declared here
test/cbe.zig
@@ -507,8 +507,8 @@ pub fn addCases(ctx: *Cases) !void {
             \\    return p.y - p.x - p.x;
             \\}
         , &.{
-            ":6:10: error: duplicate field",
-            ":4:10: note: other field here",
+            ":4:10: error: duplicate field",
+            ":6:10: note: other field here",
         });
         case.addError(
             \\const Point = struct { x: i32, y: i32 };