Commit 501a2350ab

Carl Åstholm <carl@astholm.se>
2024-01-04 14:30:52
sema: Prevent reifying non-empty union with empty tag type
1 parent 15f7a47
src/Sema.zig
@@ -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: {
@@ -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]);
@@ -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", .{
test/behavior/type.zig
@@ -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
test/cases/compile_errors/reify_type_for_tagged_union_with_no_enum_fields.zig
@@ -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
test/cases/compile_errors/reify_type_for_tagged_union_with_no_union_fields.zig
@@ -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