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(); const sfba_allocator = sfba.get();
var duplicate_names = std.AutoArrayHashMap(Zir.NullTerminatedString, ArrayListUnmanaged(Ast.TokenIndex)).init(sfba_allocator); 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)); try duplicate_names.ensureTotalCapacity(@intCast(struct_init.ast.fields.len));
// When there aren't errors, use this to avoid a second iteration. // 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); var error_notes = std.ArrayList(u32).init(astgen.arena);
for (record.items[1..]) |duplicate| { 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( try astgen.appendErrorTokNotes(
record.items[0], record.items[0],
"duplicate field", "duplicate struct field name",
.{}, .{},
error_notes.items, error_notes.items,
); );
@ -1815,9 +1816,10 @@ fn structInitExpr(
switch (ri.rl) { switch (ri.rl) {
.none => return structInitExprAnon(gz, scope, node, struct_init), .none => return structInitExprAnon(gz, scope, node, struct_init),
.discard => { .discard => {
// Even if discarding we must perform an anonymous init to check for duplicate field names. // Even if discarding we must perform side-effects.
// TODO: should duplicate field names be caught in AstGen? for (struct_init.ast.fields) |field_init| {
_ = try structInitExprAnon(gz, scope, node, struct_init); _ = try expr(gz, scope, .{ .rl = .discard }, field_init);
}
return .void_value; return .void_value;
}, },
.ref => { .ref => {
@ -5101,15 +5103,15 @@ fn structDeclInner(
var error_notes = std.ArrayList(u32).init(astgen.arena); var error_notes = std.ArrayList(u32).init(astgen.arena);
for (record.items[1..]) |duplicate| { 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 error_notes.append(try astgen.errNoteNode(node, "struct declared here", .{}));
try astgen.appendErrorTokNotes( try astgen.appendErrorTokNotes(
record.items[0], record.items[0],
"duplicate struct field: '{s}'", "duplicate struct field name",
.{try astgen.identifierTokenString(record.items[0])}, .{},
error_notes.items, error_notes.items,
); );
} }
@ -5118,8 +5120,6 @@ fn structDeclInner(
return error.AnalysisFail; return error.AnalysisFail;
} }
duplicate_names.deinit();
try gz.setStruct(decl_inst, .{ try gz.setStruct(decl_inst, .{
.src_node = node, .src_node = node,
.layout = layout, .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); var wip_members = try WipMembers.init(gpa, &astgen.scratch, decl_count, field_count, bits_per_field, max_field_size);
defer wip_members.deinit(); 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| { for (members) |member_node| {
var member = switch (try containerMember(&block_scope, &namespace.base, &wip_members, member_node)) { var member = switch (try containerMember(&block_scope, &namespace.base, &wip_members, member_node)) {
.decl => continue, .decl => continue,
@ -5227,6 +5236,16 @@ fn unionDeclInner(
const field_name = try astgen.identAsString(member.ast.main_token); const field_name = try astgen.identAsString(member.ast.main_token);
wip_members.appendToField(@intFromEnum(field_name)); 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()); const doc_comment_index = try astgen.docCommentAsString(member.firstToken());
wip_members.appendToField(@intFromEnum(doc_comment_index)); 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()) { if (!block_scope.isEmpty()) {
_ = try block_scope.addBreak(.break_inline, decl_inst, .void_value); _ = 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); 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(); 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| { for (container_decl.ast.members) |member_node| {
if (member_node == counts.nonexhaustive_node) if (member_node == counts.nonexhaustive_node)
continue; continue;
@ -5506,6 +5560,16 @@ fn containerDecl(
const field_name = try astgen.identAsString(member.ast.main_token); const field_name = try astgen.identAsString(member.ast.main_token);
wip_members.appendToField(@intFromEnum(field_name)); 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()); const doc_comment_index = try astgen.docCommentAsString(member.firstToken());
wip_members.appendToField(@intFromEnum(doc_comment_index)); 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()) { if (!block_scope.isEmpty()) {
_ = try block_scope.addBreak(.break_inline, decl_inst, .void_value); _ = 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_index: Zir.NullTerminatedString = @enumFromInt(sema.code.extra[extra_index]);
const field_name_zir = sema.code.nullTerminatedString(field_name_index); const field_name_zir = sema.code.nullTerminatedString(field_name_index);
extra_index += 1; extra_index += 2; // field name, doc comment
// doc comment
extra_index += 1;
const field_name = try mod.intern_pool.getOrPutString(gpa, field_name_zir); const field_name = try mod.intern_pool.getOrPutString(gpa, field_name_zir);
if (incomplete_enum.addFieldName(&mod.intern_pool, field_name)) |other_index| { assert(incomplete_enum.addFieldName(&mod.intern_pool, field_name) == null);
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);
}
const tag_overflow = if (has_tag_value) overflow: { const tag_overflow = if (has_tag_value) overflow: {
const tag_val_ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_index]); 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| { } else if (struct_type.addFieldName(ip, field_name)) |prev_index| {
_ = prev_index; // TODO: better source location _ = 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(); const field_ty = type_val.toType();
@ -36284,7 +36271,6 @@ fn semaStructFields(
const zir = mod.namespacePtr(namespace_index).file_scope.zir; const zir = mod.namespacePtr(namespace_index).file_scope.zir;
const zir_index = struct_type.zir_index; const zir_index = struct_type.zir_index;
const src = LazySrcLoc.nodeOffset(0);
const fields_len, const small, var extra_index = structZirInfo(zir, zir_index); const fields_len, const small, var extra_index = structZirInfo(zir, zir_index);
if (fields_len == 0) switch (struct_type.layout) { if (fields_len == 0) switch (struct_type.layout) {
@ -36384,19 +36370,7 @@ fn semaStructFields(
// This string needs to outlive the ZIR code. // This string needs to outlive the ZIR code.
if (opt_field_name_zir) |field_name_zir| { if (opt_field_name_zir) |field_name_zir| {
const field_name = try ip.getOrPutString(gpa, field_name_zir); const field_name = try ip.getOrPutString(gpa, field_name_zir);
if (struct_type.addFieldName(ip, field_name)) |other_index| { assert(struct_type.addFieldName(ip, field_name) == null);
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);
}
} }
if (has_align) { 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_types: std.ArrayListUnmanaged(InternPool.Index) = .{};
var field_aligns: std.ArrayListUnmanaged(InternPool.Alignment) = .{}; var field_aligns: std.ArrayListUnmanaged(InternPool.Alignment) = .{};
var field_name_table: std.AutoArrayHashMapUnmanaged(InternPool.NullTerminatedString, void) = .{};
try field_types.ensureTotalCapacityPrecise(sema.arena, fields_len); try field_types.ensureTotalCapacityPrecise(sema.arena, fields_len);
if (small.any_aligned_fields) if (small.any_aligned_fields)
try field_aligns.ensureTotalCapacityPrecise(sema.arena, fields_len); try field_aligns.ensureTotalCapacityPrecise(sema.arena, fields_len);
try field_name_table.ensureTotalCapacity(sema.arena, fields_len);
const bits_per_field = 4; const bits_per_field = 4;
const fields_per_u32 = 32 / bits_per_field; 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; 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) { if (explicit_tags_seen.len > 0) {
const tag_info = ip.indexToKey(union_type.tagTypePtr(ip).*).enum_type; const tag_info = ip.indexToKey(union_type.tagTypePtr(ip).*).enum_type;
const enum_index = tag_info.nameIndex(ip, field_name) orelse { const enum_index = tag_info.nameIndex(ip, field_name) orelse {

View File

@ -12,5 +12,6 @@ export fn entry() void {
// backend=stage2 // backend=stage2
// target=native // target=native
// //
// :3:5: error: duplicate enum field 'Bar' // :2:5: error: duplicate enum field name
// :2:5: note: other field here // :3:5: note: duplicate field here
// :1:13: note: enum declared here

View File

@ -14,5 +14,6 @@ export fn entry() void {
// backend=stage2 // backend=stage2
// target=native // target=native
// //
// :4:14: error: duplicate field // :4:14: error: duplicate struct field name
// :7:14: note: other field here // :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 // backend=stage2
// target=native // target=native
// //
// :2:13: error: duplicate field // :2:13: error: duplicate struct field name
// :2:21: note: other field here // :2:21: note: duplicate name here
// :2:10: note: struct declared here

View File

@ -17,5 +17,6 @@ export fn f() void {
// backend=stage2 // backend=stage2
// target=native // target=native
// //
// :8:10: error: duplicate field // :8:10: error: duplicate struct field name
// :11:10: note: other field here // :11:10: note: duplicate name here
// :7:16: note: struct declared here

View File

@ -24,10 +24,10 @@ export fn b() void {
// backend=stage2 // backend=stage2
// target=native // target=native
// //
// :2:5: error: duplicate struct field: 'Bar' // :2:5: error: duplicate struct field name
// :3:5: note: other field here // :3:5: note: duplicate field here
// :1:13: note: struct declared here // :1:13: note: struct declared here
// :7:5: error: duplicate struct field: 'a' // :7:5: error: duplicate struct field name
// :9:5: note: other field here // :9:5: note: duplicate field here
// :10:5: note: other field here // :10:5: note: duplicate field here
// :6:11: note: struct declared here // :6:11: note: struct declared here

View File

@ -11,6 +11,6 @@ export fn entry() void {
// backend=stage2 // backend=stage2
// target=native // target=native
// //
// :3:5: error: duplicate union field: 'Bar' // :2:5: error: duplicate union field name
// :2:5: note: other field here // :3:5: note: duplicate field here
// :1:13: note: union declared here // :1:13: note: union declared here

View File

@ -11,6 +11,6 @@ pub export fn entry() void {
// backend=stage2 // backend=stage2
// target=native // target=native
// //
// :3:9: error: duplicate struct field: 'e' // :3:9: error: duplicate struct field name
// :4:9: note: other field here // :4:9: note: duplicate field here
// :2:22: note: struct declared here // :2:22: note: struct declared here

View File

@ -11,6 +11,6 @@ export fn entry() void {
// error // error
// target=native // target=native
// //
// :2:5: error: duplicate struct field: 'foo' // :2:5: error: duplicate struct field name
// :3:5: note: other field here // :3:5: note: duplicate field here
// :1:11: note: struct declared here // :1:11: note: struct declared here

View File

@ -12,6 +12,6 @@ export fn foo() void {
// error // error
// target=native // target=native
// //
// :4:5: error: duplicate union field: 'a' // :3:5: error: duplicate union field name
// :3:5: note: other field here // :4:5: note: duplicate field here
// :2:11: note: union declared here // :2:11: note: union declared here

View File

@ -11,6 +11,6 @@ export fn entry() void {
// error // error
// target=native // target=native
// //
// :3:5: error: duplicate union field: 'foo' // :2:5: error: duplicate union field name
// :2:5: note: other field here // :3:5: note: duplicate field here
// :1:11: note: union declared 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; \\ return p.y - p.x - p.x;
\\} \\}
, &.{ , &.{
":4:10: error: duplicate field", ":4:10: error: duplicate struct field name",
":6:10: note: other field here", ":6:10: note: duplicate name here",
":3:21: note: struct declared here",
}); });
case.addError( case.addError(
\\const Point = struct { x: i32, y: i32 }; \\const Point = struct { x: i32, y: i32 };
@ -722,8 +723,9 @@ pub fn addCases(ctx: *Cases, b: *std.Build) !void {
\\ _ = E1.a; \\ _ = E1.a;
\\} \\}
, &.{ , &.{
":1:28: error: duplicate enum field 'b'", ":1:22: error: duplicate enum field name",
":1:22: note: other field here", ":1:28: note: duplicate field here",
":1:12: note: enum declared here",
}); });
case.addError( case.addError(