Commit 796927b900

AdamGoertz <36753247+AdamGoertz@users.noreply.github.com>
2023-07-29 18:45:01
Allow zero-sized fields in extern structs (#16404)
This change allows the following types to appear in extern structs: * Zero-bit integers * void * zero-sized structs and packed structs * enums with zero-bit backing integers * arrays of any length with zero-size elements
1 parent 8d1805f
src/Sema.zig
@@ -24590,7 +24590,7 @@ fn validateExternType(
         .ErrorSet,
         .Frame,
         => return false,
-        .Void => return position == .union_field or position == .ret_ty,
+        .Void => return position == .union_field or position == .ret_ty or position == .struct_field or position == .element,
         .NoReturn => return position == .ret_ty,
         .Opaque,
         .Bool,
@@ -24599,7 +24599,7 @@ fn validateExternType(
         => return true,
         .Pointer => return !(ty.isSlice(mod) or try sema.typeRequiresComptime(ty)),
         .Int => switch (ty.intInfo(mod).bits) {
-            8, 16, 32, 64, 128 => return true,
+            0, 8, 16, 32, 64, 128 => return true,
             else => return false,
         },
         .Fn => {
@@ -24620,11 +24620,11 @@ fn validateExternType(
             .Packed => {
                 const bit_size = try ty.bitSizeAdvanced(mod, sema);
                 switch (bit_size) {
-                    8, 16, 32, 64, 128 => return true,
+                    0, 8, 16, 32, 64, 128 => return true,
                     else => return false,
                 }
             },
-            .Auto => return false,
+            .Auto => return !(try sema.typeHasRuntimeBits(ty)),
         },
         .Array => {
             if (position == .ret_ty or position == .param_ty) return false;
@@ -24673,9 +24673,9 @@ fn explainWhyTypeIsNotExtern(
         .Void => try mod.errNoteNonLazy(src_loc, msg, "'void' is a zero bit type; for C 'void' use 'anyopaque'", .{}),
         .NoReturn => try mod.errNoteNonLazy(src_loc, msg, "'noreturn' is only allowed as a return type", .{}),
         .Int => if (!std.math.isPowerOfTwo(ty.intInfo(mod).bits)) {
-            try mod.errNoteNonLazy(src_loc, msg, "only integers with power of two bits are extern compatible", .{});
+            try mod.errNoteNonLazy(src_loc, msg, "only integers with 0 or power of two bits are extern compatible", .{});
         } else {
-            try mod.errNoteNonLazy(src_loc, msg, "only integers with 8, 16, 32, 64 and 128 bits are extern compatible", .{});
+            try mod.errNoteNonLazy(src_loc, msg, "only integers with 0, 8, 16, 32, 64 and 128 bits are extern compatible", .{});
         },
         .Fn => {
             if (position != .other) {
test/behavior/extern_struct_zero_size_fields.zig
@@ -0,0 +1,21 @@
+const E = enum(u0) {
+    the_only_possible_value,
+};
+
+const S = struct {};
+
+const T = extern struct {
+    foo: u0 = 0,
+    bar: void = {},
+    baz: struct {} = .{},
+    ayy: E = .the_only_possible_value,
+    arr: [0]u0 = .{},
+    matey: [128]void = [_]void{{}} ** 128,
+    running_out_of_ideas: packed struct {} = .{},
+    one_more: [256]S = [_]S{.{}} ** 256,
+};
+
+test {
+    var t: T = .{};
+    _ = t;
+}
test/cases/compile_errors/exported_enum_without_explicit_integer_tag_type.zig
@@ -14,5 +14,5 @@ comptime {
 // :3:5: error: unable to export type 'type'
 // :7:5: error: unable to export type 'tmp.E'
 // :7:5: note: enum tag type 'u1' is not extern compatible
-// :7:5: note: only integers with 8, 16, 32, 64 and 128 bits are extern compatible
+// :7:5: note: only integers with 0, 8, 16, 32, 64 and 128 bits are extern compatible
 // :1:11: note: enum declared here
test/cases/compile_errors/extern_struct_with_extern-compatible_but_inferred_integer_tag_type.zig
@@ -43,5 +43,5 @@ export fn entry() void {
 //
 // :33:8: error: extern structs cannot contain fields of type 'tmp.E'
 // :33:8: note: enum tag type 'u9' is not extern compatible
-// :33:8: note: only integers with power of two bits are extern compatible
+// :33:8: note: only integers with 0 or power of two bits are extern compatible
 // :2:15: note: enum declared here
test/cases/compile_errors/extern_struct_with_non-extern-compatible_integer_tag_type.zig
@@ -13,5 +13,5 @@ export fn entry() void {
 //
 // :3:8: error: extern structs cannot contain fields of type 'tmp.E'
 // :3:8: note: enum tag type 'u31' is not extern compatible
-// :3:8: note: only integers with power of two bits are extern compatible
+// :3:8: note: only integers with 0 or power of two bits are extern compatible
 // :1:15: note: enum declared here
test/cases/compile_errors/extern_variable_has_non_extern_type.zig
@@ -8,4 +8,4 @@ pub export fn entry() void {
 // target=native
 //
 // :1:17: error: extern variable cannot have type 'u3'
-// :1:17: note: only integers with power of two bits are extern compatible
+// :1:17: note: only integers with 0 or power of two bits are extern compatible
test/cases/compile_errors/function_with_non-extern_non-packed_enum_parameter.zig
@@ -9,5 +9,5 @@ export fn entry(foo: Foo) void {
 //
 // :2:17: error: parameter of type 'tmp.Foo' not allowed in function with calling convention 'C'
 // :2:17: note: enum tag type 'u2' is not extern compatible
-// :2:17: note: only integers with 8, 16, 32, 64 and 128 bits are extern compatible
+// :2:17: note: only integers with 0, 8, 16, 32, 64 and 128 bits are extern compatible
 // :1:13: note: enum declared here
test/cases/compile_errors/variadic_arg_validation.zig
@@ -24,6 +24,6 @@ pub export fn entry3() void {
 // :4:33: error: integer and float literals passed to variadic function must be casted to a fixed-size number type
 // :9:24: error: arrays must be passed by reference to variadic function
 // :13:24: error: cannot pass 'u48' to variadic function
-// :13:24: note: only integers with power of two bits are extern compatible
+// :13:24: note: only integers with 0 or power of two bits are extern compatible
 // :17:24: error: cannot pass 'void' to variadic function
 // :17:24: note: 'void' is a zero bit type; for C 'void' use 'anyopaque'