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
This commit is contained in:
David Rubin 2024-01-20 09:23:47 -08:00 committed by GitHub
parent 5c4cb60f4f
commit 1b8f7e46fa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 138 additions and 87 deletions

View File

@ -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);
}

View File

@ -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 {

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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(