Move duplicate field detection for struct init expressions into AstGen

Partially addresses #17916.
This commit is contained in:
David 2023-11-16 06:38:16 -08:00 committed by GitHub
parent acf9de376d
commit 941090d94f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 146 additions and 67 deletions

View File

@ -445,7 +445,7 @@ pub const ucontext_t = extern struct {
sigmask: u64, sigmask: u64,
mcontext: mcontext_t, mcontext: mcontext_t,
stack: stack_t, stack: stack_t,
sigmask: sigset_t, sigset: sigset_t,
}; };
pub const rlimit_resource = enum(c_int) { pub const rlimit_resource = enum(c_int) {

View File

@ -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) { if (struct_init.ast.type_expr != 0) {
// Typed inits do not use RLS for language simplicity. // Typed inits do not use RLS for language simplicity.
const ty_inst = try typeExpr(gz, scope, struct_init.ast.type_expr); 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_non_opv = false;
var known_comptime_only = false; var known_comptime_only = false;
var any_comptime_fields = false; var any_comptime_fields = false;
@ -4886,11 +4946,22 @@ fn structDeclInner(
}; };
if (!is_tuple) { if (!is_tuple) {
const field_name = try astgen.identAsString(member.ast.main_token);
member.convertToNonTupleLike(astgen.tree.nodes); member.convertToNonTupleLike(astgen.tree.nodes);
assert(!member.ast.tuple_like); assert(!member.ast.tuple_like);
const field_name = try astgen.identAsString(member.ast.main_token);
wip_members.appendToField(field_name); 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) { } else if (!member.ast.tuple_like) {
return astgen.failTok(member.ast.main_token, "tuple field has a name", .{}); 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, .{ try gz.setStruct(decl_inst, .{
.src_node = node, .src_node = node,
.layout = layout, .layout = layout,

View File

@ -4690,17 +4690,7 @@ fn validateStructInit(
try sema.tupleFieldIndex(block, struct_ty, field_name, field_src) try sema.tupleFieldIndex(block, struct_ty, field_name, field_src)
else else
try sema.structFieldIndex(block, struct_ty, field_name, field_src); try sema.structFieldIndex(block, struct_ty, field_name, field_src);
if (found_fields[field_index.*].unwrap()) |other_field_ptr| { assert(found_fields[field_index.*] == .none);
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);
}
found_fields[field_index.*] = field_ptr.toOptional(); found_fields[field_index.*] = field_ptr.toOptional();
} }
@ -19222,18 +19212,7 @@ fn zirStructInit(
try sema.tupleFieldIndex(block, resolved_ty, field_name, field_src) try sema.tupleFieldIndex(block, resolved_ty, field_name, field_src)
else else
try sema.structFieldIndex(block, resolved_ty, field_name, field_src); try sema.structFieldIndex(block, resolved_ty, field_name, field_src);
if (field_inits[field_index] != .none) { assert(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);
}
found_fields[field_index] = item.data.field_type; found_fields[field_index] = item.data.field_type;
const uncoerced_init = try sema.resolveInst(item.data.init); const uncoerced_init = try sema.resolveInst(item.data.init);
const field_ty = resolved_ty.structFieldType(field_index, mod); 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 types = try sema.arena.alloc(InternPool.Index, extra_data.fields_len);
const values = try sema.arena.alloc(InternPool.Index, types.len); const values = try sema.arena.alloc(InternPool.Index, types.len);
const names = try sema.arena.alloc(InternPool.NullTerminatedString, types.len);
var fields = std.AutoArrayHashMap(InternPool.NullTerminatedString, u32).init(sema.arena);
try fields.ensureUnusedCapacity(types.len);
// Find which field forces the expression to be runtime, if any. // Find which field forces the expression to be runtime, if any.
const opt_runtime_index = rs: { const opt_runtime_index = rs: {
var runtime_index: ?usize = null; var runtime_index: ?usize = null;
var extra_index = extra_end; var extra_index = extra_end;
for (types, 0..) |*field_ty, i_usize| { for (types, values, names, 0..) |*field_ty, *field_val, *field_name, i_usize| {
const i: u32 = @intCast(i_usize);
const item = switch (kind) { const item = switch (kind) {
.anon_init => sema.code.extraData(Zir.Inst.StructInitAnon.Item, extra_index), .anon_init => sema.code.extraData(Zir.Inst.StructInitAnon.Item, extra_index),
.typed_init => sema.code.extraData(Zir.Inst.StructInit.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); 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.*); const name_ip = try mod.intern_pool.getOrPutString(gpa, name);
try sema.errNote(block, prev_source, msg, "other field here", .{}); field_name.* = name_ip;
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
gop.value_ptr.* = i;
const init = try sema.resolveInst(item.data.init); const init = try sema.resolveInst(item.data.init);
field_ty.* = sema.typeOf(init).toIntern(); field_ty.* = sema.typeOf(init).toIntern();
if (field_ty.toType().zigTypeTag(mod) == .Opaque) { if (field_ty.toType().zigTypeTag(mod) == .Opaque) {
const msg = msg: { const msg = msg: {
const decl = mod.declPtr(block.src_decl); 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", .{}); 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); errdefer msg.destroy(sema.gpa);
@ -19590,17 +19553,17 @@ fn structInitAnon(
return sema.failWithOwnedErrorMsg(block, msg); return sema.failWithOwnedErrorMsg(block, msg);
} }
if (try sema.resolveValue(init)) |init_val| { 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 { } else {
values[i] = .none; field_val.* = .none;
runtime_index = i; runtime_index = @intCast(i_usize);
} }
} }
break :rs runtime_index; break :rs runtime_index;
}; };
const tuple_ty = try ip.getAnonStructType(gpa, .{ const tuple_ty = try ip.getAnonStructType(gpa, .{
.names = fields.keys(), .names = names,
.types = types, .types = types,
.values = values, .values = values,
}); });

View File

@ -14,5 +14,5 @@ export fn entry() void {
// backend=stage2 // backend=stage2
// target=native // target=native
// //
// :7:16: error: duplicate field // :4:14: error: duplicate field
// :4:16: note: other field here // :7:14: note: other field here

View File

@ -6,5 +6,5 @@ pub export fn entry() void {
// backend=stage2 // backend=stage2
// target=native // target=native
// //
// :2:21: error: duplicate field // :2:13: error: duplicate field
// :2:13: note: other field here // :2:21: note: other field here

View File

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

View File

@ -2,15 +2,32 @@ const Foo = struct {
Bar: i32, Bar: i32,
Bar: usize, Bar: usize,
}; };
export fn entry() void {
const a: Foo = undefined; const S = struct {
_ = a; 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 // error
// backend=stage2 // backend=stage2
// target=native // target=native
// //
// :3:5: error: duplicate struct field: 'Bar' // :2:5: error: duplicate struct field: 'Bar'
// :2:5: note: other field here // :3:5: note: other field here
// :1:13: note: struct declared 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

View File

@ -11,6 +11,6 @@ pub export fn entry() void {
// backend=stage2 // backend=stage2
// target=native // target=native
// //
// :4:9: error: duplicate struct field: 'e' // :3:9: error: duplicate struct field: 'e'
// :3:9: note: other field here // :4:9: note: other 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
// //
// :3:5: error: duplicate struct field: 'foo' // :2:5: error: duplicate struct field: 'foo'
// :2:5: note: other field here // :3:5: note: other field here
// :1:11: note: struct declared here // :1:11: note: struct declared here

View File

@ -507,8 +507,8 @@ pub fn addCases(ctx: *Cases) !void {
\\ return p.y - p.x - p.x; \\ return p.y - p.x - p.x;
\\} \\}
, &.{ , &.{
":6:10: error: duplicate field", ":4:10: error: duplicate field",
":4:10: note: other field here", ":6:10: note: other field here",
}); });
case.addError( case.addError(
\\const Point = struct { x: i32, y: i32 }; \\const Point = struct { x: i32, y: i32 };