Skip to content

Commit

Permalink
sema: Prevent reifying non-empty union with empty tag type
Browse files Browse the repository at this point in the history
  • Loading branch information
castholm authored and Vexu committed Jan 4, 2024
1 parent 15f7a47 commit 501a235
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 2 deletions.
6 changes: 4 additions & 2 deletions src/Sema.zig
Original file line number Diff line number Diff line change
Expand Up @@ -20888,7 +20888,7 @@ fn zirReify(
enum_field_names[i] = field_name;
}

if (explicit_tags_seen.len > 0) {
if (enum_tag_ty != .none) {
const tag_info = ip.indexToKey(enum_tag_ty).enum_type;
const enum_index = tag_info.nameIndex(ip, field_name) orelse {
const msg = msg: {
Expand All @@ -20902,6 +20902,7 @@ fn zirReify(
};
return sema.failWithOwnedErrorMsg(block, msg);
};
assert(explicit_tags_seen.len == tag_info.names.len);
// No check for duplicate because the check already happened in order
// to create the enum type in the first place.
assert(!explicit_tags_seen[enum_index]);
Expand Down Expand Up @@ -20967,13 +20968,14 @@ fn zirReify(
}
}

if (explicit_tags_seen.len > 0) {
if (enum_tag_ty != .none) {
const tag_info = ip.indexToKey(enum_tag_ty).enum_type;
if (tag_info.names.len > fields_len) {
const msg = msg: {
const msg = try sema.errMsg(block, src, "enum field(s) missing in union", .{});
errdefer msg.destroy(gpa);

assert(explicit_tags_seen.len == tag_info.names.len);
for (tag_info.names.get(ip), 0..) |field_name, field_index| {
if (explicit_tags_seen[field_index]) continue;
try sema.addFieldErrNote(Type.fromInterned(enum_tag_ty), field_index, msg, "field '{}' missing, declared here", .{
Expand Down
33 changes: 33 additions & 0 deletions test/behavior/type.zig
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,39 @@ test "Type.Union from regular enum" {
_ = @typeInfo(T).Union;
}

test "Type.Union from empty regular enum" {
const E = enum {};
const U = @Type(.{
.Union = .{
.layout = .Auto,
.tag_type = E,
.fields = &.{},
.decls = &.{},
},
});
try testing.expectEqual(@sizeOf(U), 0);
}

test "Type.Union from empty Type.Enum" {
const E = @Type(.{
.Enum = .{
.tag_type = u0,
.fields = &.{},
.decls = &.{},
.is_exhaustive = true,
},
});
const U = @Type(.{
.Union = .{
.layout = .Auto,
.tag_type = E,
.fields = &.{},
.decls = &.{},
},
});
try testing.expectEqual(@sizeOf(U), 0);
}

test "Type.Fn" {
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const Tag = @Type(.{
.Enum = .{
.tag_type = u0,
.fields = &.{},
.decls = &.{},
.is_exhaustive = true,
},
});
const Tagged = @Type(.{
.Union = .{
.layout = .Auto,
.tag_type = Tag,
.fields = &.{
.{ .name = "signed", .type = i32, .alignment = @alignOf(i32) },
.{ .name = "unsigned", .type = u32, .alignment = @alignOf(u32) },
},
.decls = &.{},
},
});
export fn entry() void {
const tagged: Tagged = undefined;
_ = tagged;
}

// error
// backend=stage2
// target=native
//
// :9:16: error: no field named 'signed' in enum 'tmp.Tag'
// :1:13: note: enum declared here
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const Tag = @Type(.{
.Enum = .{
.tag_type = u1,
.fields = &.{
.{ .name = "signed", .value = 0 },
.{ .name = "unsigned", .value = 1 },
},
.decls = &.{},
.is_exhaustive = true,
},
});
const Tagged = @Type(.{
.Union = .{
.layout = .Auto,
.tag_type = Tag,
.fields = &.{},
.decls = &.{},
},
});
export fn entry() void {
const tagged: Tagged = undefined;
_ = tagged;
}

// error
// backend=stage2
// target=native
//
// :12:16: error: enum field(s) missing in union
// :1:13: note: field 'signed' missing, declared here
// :1:13: note: field 'unsigned' missing, declared here
// :1:13: note: enum declared here

0 comments on commit 501a235

Please sign in to comment.