From 1b8f7e46fa33cda57caf39c5a5a0f5f52335a99b Mon Sep 17 00:00:00 2001 From: David Rubin <87927264+Rexicon226@users.noreply.github.com> Date: Sat, 20 Jan 2024 09:23:47 -0800 Subject: [PATCH] AstGen: detect duplicate field names This logic was previously in Sema, which was unnecessary complexity, and meant the issue was not detected unless the declaration was semantically analyzed. This commit finishes the work which 941090d started. Resolves: #17916 --- src/AstGen.zig | 112 ++++++++++++++++-- src/Sema.zig | 53 +-------- .../compile_errors/duplicate_enum_field.zig | 5 +- ...cate_field_in_anonymous_struct_literal.zig | 5 +- ...duplicate_field_in_discarded_anon_init.zig | 5 +- ...icate_field_in_struct_value_expression.zig | 5 +- .../compile_errors/duplicate_struct_field.zig | 10 +- .../compile_errors/duplicate_union_field.zig | 4 +- ..._initializer_doesnt_crash_the_compiler.zig | 4 +- .../struct_duplicate_field_name.zig | 4 +- .../union_duplicate_enum_field.zig | 4 +- .../union_duplicate_field_definition.zig | 4 +- test/cbe.zig | 10 +- 13 files changed, 138 insertions(+), 87 deletions(-) diff --git a/src/AstGen.zig b/src/AstGen.zig index b613a6b677..f709751fde 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -1753,7 +1753,6 @@ fn structInitExpr( const sfba_allocator = sfba.get(); var duplicate_names = std.AutoArrayHashMap(Zir.NullTerminatedString, 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. @@ -1783,12 +1782,14 @@ fn structInitExpr( 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.errNoteTok(duplicate, "duplicate name here", .{})); } + try error_notes.append(try astgen.errNoteNode(node, "struct declared here", .{})); + try astgen.appendErrorTokNotes( record.items[0], - "duplicate field", + "duplicate struct field name", .{}, error_notes.items, ); @@ -1815,9 +1816,10 @@ fn structInitExpr( switch (ri.rl) { .none => return structInitExprAnon(gz, scope, node, struct_init), .discard => { - // Even if discarding we must perform an anonymous init to check for duplicate field names. - // TODO: should duplicate field names be caught in AstGen? - _ = try structInitExprAnon(gz, scope, node, struct_init); + // Even if discarding we must perform side-effects. + for (struct_init.ast.fields) |field_init| { + _ = try expr(gz, scope, .{ .rl = .discard }, field_init); + } return .void_value; }, .ref => { @@ -5101,15 +5103,15 @@ fn structDeclInner( 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.errNoteTok(duplicate, "duplicate 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])}, + "duplicate struct field name", + .{}, error_notes.items, ); } @@ -5118,8 +5120,6 @@ fn structDeclInner( return error.AnalysisFail; } - duplicate_names.deinit(); - try gz.setStruct(decl_inst, .{ .src_node = node, .layout = layout, @@ -5211,6 +5211,15 @@ fn unionDeclInner( var wip_members = try WipMembers.init(gpa, &astgen.scratch, decl_count, field_count, bits_per_field, max_field_size); defer wip_members.deinit(); + var sfba = std.heap.stackFallback(256, astgen.arena); + const sfba_allocator = sfba.get(); + + var duplicate_names = std.AutoArrayHashMap(Zir.NullTerminatedString, 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; + for (members) |member_node| { var member = switch (try containerMember(&block_scope, &namespace.base, &wip_members, member_node)) { .decl => continue, @@ -5227,6 +5236,16 @@ fn unionDeclInner( const field_name = try astgen.identAsString(member.ast.main_token); wip_members.appendToField(@intFromEnum(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); + } + const doc_comment_index = try astgen.docCommentAsString(member.firstToken()); wip_members.appendToField(@intFromEnum(doc_comment_index)); @@ -5281,6 +5300,32 @@ fn unionDeclInner( } } + 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, "duplicate field here", .{})); + } + + try error_notes.append(try astgen.errNoteNode(node, "union declared here", .{})); + + try astgen.appendErrorTokNotes( + record.items[0], + "duplicate union field name", + .{}, + error_notes.items, + ); + } + } + + return error.AnalysisFail; + } + if (!block_scope.isEmpty()) { _ = try block_scope.addBreak(.break_inline, decl_inst, .void_value); } @@ -5490,6 +5535,15 @@ fn containerDecl( var wip_members = try WipMembers.init(gpa, &astgen.scratch, @intCast(counts.decls), @intCast(counts.total_fields), bits_per_field, max_field_size); defer wip_members.deinit(); + var sfba = std.heap.stackFallback(256, astgen.arena); + const sfba_allocator = sfba.get(); + + var duplicate_names = std.AutoArrayHashMap(Zir.NullTerminatedString, std.ArrayListUnmanaged(Ast.TokenIndex)).init(sfba_allocator); + try duplicate_names.ensureTotalCapacity(counts.total_fields); + + // When there aren't errors, use this to avoid a second iteration. + var any_duplicate = false; + for (container_decl.ast.members) |member_node| { if (member_node == counts.nonexhaustive_node) continue; @@ -5506,6 +5560,16 @@ fn containerDecl( const field_name = try astgen.identAsString(member.ast.main_token); wip_members.appendToField(@intFromEnum(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); + } + const doc_comment_index = try astgen.docCommentAsString(member.firstToken()); wip_members.appendToField(@intFromEnum(doc_comment_index)); @@ -5533,6 +5597,32 @@ fn containerDecl( } } + 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, "duplicate field here", .{})); + } + + try error_notes.append(try astgen.errNoteNode(node, "enum declared here", .{})); + + try astgen.appendErrorTokNotes( + record.items[0], + "duplicate enum field name", + .{}, + error_notes.items, + ); + } + } + + return error.AnalysisFail; + } + if (!block_scope.isEmpty()) { _ = try block_scope.addBreak(.break_inline, decl_inst, .void_value); } diff --git a/src/Sema.zig b/src/Sema.zig index a38da0adef..96be1a441f 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -3046,23 +3046,10 @@ fn zirEnumDecl( const field_name_index: Zir.NullTerminatedString = @enumFromInt(sema.code.extra[extra_index]); const field_name_zir = sema.code.nullTerminatedString(field_name_index); - extra_index += 1; - - // doc comment - extra_index += 1; + extra_index += 2; // field name, doc comment const field_name = try mod.intern_pool.getOrPutString(gpa, field_name_zir); - if (incomplete_enum.addFieldName(&mod.intern_pool, field_name)) |other_index| { - const field_src = mod.fieldSrcLoc(new_decl_index, .{ .index = field_i }).lazy; - const other_field_src = mod.fieldSrcLoc(new_decl_index, .{ .index = other_index }).lazy; - const msg = msg: { - const msg = try sema.errMsg(block, field_src, "duplicate enum field '{s}'", .{field_name_zir}); - errdefer msg.destroy(gpa); - try sema.errNote(block, other_field_src, msg, "other field here", .{}); - break :msg msg; - }; - return sema.failWithOwnedErrorMsg(block, msg); - } + assert(incomplete_enum.addFieldName(&mod.intern_pool, field_name) == null); const tag_overflow = if (has_tag_value) overflow: { const tag_val_ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_index]); @@ -21732,7 +21719,7 @@ fn reifyStruct( } } else if (struct_type.addFieldName(ip, field_name)) |prev_index| { _ = prev_index; // TODO: better source location - return sema.fail(block, src, "duplicate struct field {}", .{field_name.fmt(ip)}); + return sema.fail(block, src, "duplicate struct field name {}", .{field_name.fmt(ip)}); } const field_ty = type_val.toType(); @@ -36284,7 +36271,6 @@ fn semaStructFields( const zir = mod.namespacePtr(namespace_index).file_scope.zir; const zir_index = struct_type.zir_index; - const src = LazySrcLoc.nodeOffset(0); const fields_len, const small, var extra_index = structZirInfo(zir, zir_index); if (fields_len == 0) switch (struct_type.layout) { @@ -36384,19 +36370,7 @@ fn semaStructFields( // This string needs to outlive the ZIR code. if (opt_field_name_zir) |field_name_zir| { const field_name = try ip.getOrPutString(gpa, field_name_zir); - if (struct_type.addFieldName(ip, field_name)) |other_index| { - const msg = msg: { - const field_src = mod.fieldSrcLoc(decl_index, .{ .index = field_i }).lazy; - const msg = try sema.errMsg(&block_scope, field_src, "duplicate struct field: '{}'", .{field_name.fmt(ip)}); - errdefer msg.destroy(gpa); - - const prev_field_src = mod.fieldSrcLoc(decl_index, .{ .index = other_index }); - try mod.errNoteNonLazy(prev_field_src, msg, "other field here", .{}); - try sema.errNote(&block_scope, src, msg, "struct declared here", .{}); - break :msg msg; - }; - return sema.failWithOwnedErrorMsg(&block_scope, msg); - } + assert(struct_type.addFieldName(ip, field_name) == null); } if (has_align) { @@ -36840,12 +36814,10 @@ fn semaUnionFields(mod: *Module, arena: Allocator, union_type: InternPool.Key.Un var field_types: std.ArrayListUnmanaged(InternPool.Index) = .{}; var field_aligns: std.ArrayListUnmanaged(InternPool.Alignment) = .{}; - var field_name_table: std.AutoArrayHashMapUnmanaged(InternPool.NullTerminatedString, void) = .{}; try field_types.ensureTotalCapacityPrecise(sema.arena, fields_len); if (small.any_aligned_fields) try field_aligns.ensureTotalCapacityPrecise(sema.arena, fields_len); - try field_name_table.ensureTotalCapacity(sema.arena, fields_len); const bits_per_field = 4; const fields_per_u32 = 32 / bits_per_field; @@ -36961,23 +36933,6 @@ fn semaUnionFields(mod: *Module, arena: Allocator, union_type: InternPool.Key.Un return error.GenericPoison; } - const gop = field_name_table.getOrPutAssumeCapacity(field_name); - if (gop.found_existing) { - const msg = msg: { - const field_src = mod.fieldSrcLoc(union_type.decl, .{ .index = field_i }).lazy; - const msg = try sema.errMsg(&block_scope, field_src, "duplicate union field: '{}'", .{ - field_name.fmt(ip), - }); - errdefer msg.destroy(gpa); - - const prev_field_src = mod.fieldSrcLoc(union_type.decl, .{ .index = gop.index }).lazy; - try mod.errNoteNonLazy(prev_field_src.toSrcLoc(decl, mod), msg, "other field here", .{}); - try sema.errNote(&block_scope, src, msg, "union declared here", .{}); - break :msg msg; - }; - return sema.failWithOwnedErrorMsg(&block_scope, msg); - } - if (explicit_tags_seen.len > 0) { const tag_info = ip.indexToKey(union_type.tagTypePtr(ip).*).enum_type; const enum_index = tag_info.nameIndex(ip, field_name) orelse { diff --git a/test/cases/compile_errors/duplicate_enum_field.zig b/test/cases/compile_errors/duplicate_enum_field.zig index ebf01f99a7..02738f1b37 100644 --- a/test/cases/compile_errors/duplicate_enum_field.zig +++ b/test/cases/compile_errors/duplicate_enum_field.zig @@ -12,5 +12,6 @@ export fn entry() void { // backend=stage2 // target=native // -// :3:5: error: duplicate enum field 'Bar' -// :2:5: note: other field here +// :2:5: error: duplicate enum field name +// :3:5: note: duplicate field here +// :1:13: note: enum declared here diff --git a/test/cases/compile_errors/duplicate_field_in_anonymous_struct_literal.zig b/test/cases/compile_errors/duplicate_field_in_anonymous_struct_literal.zig index ee81f72b1d..e1e220e346 100644 --- a/test/cases/compile_errors/duplicate_field_in_anonymous_struct_literal.zig +++ b/test/cases/compile_errors/duplicate_field_in_anonymous_struct_literal.zig @@ -14,5 +14,6 @@ export fn entry() void { // backend=stage2 // target=native // -// :4:14: error: duplicate field -// :7:14: note: other field here +// :4:14: error: duplicate struct field name +// :7:14: note: duplicate name here +// :3:19: note: struct declared here diff --git a/test/cases/compile_errors/duplicate_field_in_discarded_anon_init.zig b/test/cases/compile_errors/duplicate_field_in_discarded_anon_init.zig index ff1912d27b..913100f79b 100644 --- a/test/cases/compile_errors/duplicate_field_in_discarded_anon_init.zig +++ b/test/cases/compile_errors/duplicate_field_in_discarded_anon_init.zig @@ -6,5 +6,6 @@ pub export fn entry() void { // backend=stage2 // target=native // -// :2:13: error: duplicate field -// :2:21: note: other field here +// :2:13: error: duplicate struct field name +// :2:21: note: duplicate name here +// :2:10: note: struct declared here diff --git a/test/cases/compile_errors/duplicate_field_in_struct_value_expression.zig b/test/cases/compile_errors/duplicate_field_in_struct_value_expression.zig index e52f835146..b0f656b3c4 100644 --- a/test/cases/compile_errors/duplicate_field_in_struct_value_expression.zig +++ b/test/cases/compile_errors/duplicate_field_in_struct_value_expression.zig @@ -17,5 +17,6 @@ export fn f() void { // backend=stage2 // target=native // -// :8:10: error: duplicate field -// :11:10: note: other field here +// :8:10: error: duplicate struct field name +// :11:10: note: duplicate name here +// :7:16: note: struct declared here diff --git a/test/cases/compile_errors/duplicate_struct_field.zig b/test/cases/compile_errors/duplicate_struct_field.zig index 61340a423e..954ed24e92 100644 --- a/test/cases/compile_errors/duplicate_struct_field.zig +++ b/test/cases/compile_errors/duplicate_struct_field.zig @@ -24,10 +24,10 @@ export fn b() void { // backend=stage2 // target=native // -// :2:5: error: duplicate struct field: 'Bar' -// :3:5: note: other field here +// :2:5: error: duplicate struct field name +// :3:5: note: duplicate 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 +// :7:5: error: duplicate struct field name +// :9:5: note: duplicate field here +// :10:5: note: duplicate field here // :6:11: note: struct declared here diff --git a/test/cases/compile_errors/duplicate_union_field.zig b/test/cases/compile_errors/duplicate_union_field.zig index 6a0265dd02..b57842c1f7 100644 --- a/test/cases/compile_errors/duplicate_union_field.zig +++ b/test/cases/compile_errors/duplicate_union_field.zig @@ -11,6 +11,6 @@ export fn entry() void { // backend=stage2 // target=native // -// :3:5: error: duplicate union field: 'Bar' -// :2:5: note: other field here +// :2:5: error: duplicate union field name +// :3:5: note: duplicate field here // :1:13: note: union declared here diff --git a/test/cases/compile_errors/error_in_struct_initializer_doesnt_crash_the_compiler.zig b/test/cases/compile_errors/error_in_struct_initializer_doesnt_crash_the_compiler.zig index 515ac855ce..ea799c561a 100644 --- a/test/cases/compile_errors/error_in_struct_initializer_doesnt_crash_the_compiler.zig +++ b/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 // -// :3:9: error: duplicate struct field: 'e' -// :4:9: note: other field here +// :3:9: error: duplicate struct field name +// :4:9: note: duplicate field here // :2:22: note: struct declared here diff --git a/test/cases/compile_errors/struct_duplicate_field_name.zig b/test/cases/compile_errors/struct_duplicate_field_name.zig index d19e94b9d2..057083c3cb 100644 --- a/test/cases/compile_errors/struct_duplicate_field_name.zig +++ b/test/cases/compile_errors/struct_duplicate_field_name.zig @@ -11,6 +11,6 @@ export fn entry() void { // error // target=native // -// :2:5: error: duplicate struct field: 'foo' -// :3:5: note: other field here +// :2:5: error: duplicate struct field name +// :3:5: note: duplicate field here // :1:11: note: struct declared here diff --git a/test/cases/compile_errors/union_duplicate_enum_field.zig b/test/cases/compile_errors/union_duplicate_enum_field.zig index 6a5fafbb25..21c307755b 100644 --- a/test/cases/compile_errors/union_duplicate_enum_field.zig +++ b/test/cases/compile_errors/union_duplicate_enum_field.zig @@ -12,6 +12,6 @@ export fn foo() void { // error // target=native // -// :4:5: error: duplicate union field: 'a' -// :3:5: note: other field here +// :3:5: error: duplicate union field name +// :4:5: note: duplicate field here // :2:11: note: union declared here diff --git a/test/cases/compile_errors/union_duplicate_field_definition.zig b/test/cases/compile_errors/union_duplicate_field_definition.zig index 7993f27141..e0866964eb 100644 --- a/test/cases/compile_errors/union_duplicate_field_definition.zig +++ b/test/cases/compile_errors/union_duplicate_field_definition.zig @@ -11,6 +11,6 @@ export fn entry() void { // error // target=native // -// :3:5: error: duplicate union field: 'foo' -// :2:5: note: other field here +// :2:5: error: duplicate union field name +// :3:5: note: duplicate field here // :1:11: note: union declared here diff --git a/test/cbe.zig b/test/cbe.zig index 26724a1df0..35c2d8e8f9 100644 --- a/test/cbe.zig +++ b/test/cbe.zig @@ -507,8 +507,9 @@ pub fn addCases(ctx: *Cases, b: *std.Build) !void { \\ return p.y - p.x - p.x; \\} , &.{ - ":4:10: error: duplicate field", - ":6:10: note: other field here", + ":4:10: error: duplicate struct field name", + ":6:10: note: duplicate name here", + ":3:21: note: struct declared here", }); case.addError( \\const Point = struct { x: i32, y: i32 }; @@ -722,8 +723,9 @@ pub fn addCases(ctx: *Cases, b: *std.Build) !void { \\ _ = E1.a; \\} , &.{ - ":1:28: error: duplicate enum field 'b'", - ":1:22: note: other field here", + ":1:22: error: duplicate enum field name", + ":1:28: note: duplicate field here", + ":1:12: note: enum declared here", }); case.addError(