authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-02-08 13:56:01+00:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-03-10 10:26:09+00:00
log5c41b6db87702017791b824ec9d76d5da00c354b
tree390db777cc95a4b9f6d4bd80ae808da1cdf063db
parent96d6b22067cccd644190e9b63f8f5bc13b6e93db
signature Commit is signed but in an unrecognized format.

Sema: disallow empty extern/packed unions

These types don't really make much sense: you can't pack together bits of a type which cannot exist, nor can you pass it over an ABI boundary.

1 files changed, 24 insertions(+), 4 deletions(-)

src/Sema/type_resolution.zig+24-4
...@@ -347,6 +347,12 @@ pub fn resolveStructLayout(sema: *Sema, struct_ty: Type) CompileError!void {...@@ -347,6 +347,12 @@ pub fn resolveStructLayout(sema: *Sema, struct_ty: Type) CompileError!void {
347 }347 }
348 };348 };
349349
350 switch (struct_obj.layout) {
351 .auto => {},
352 .@"extern" => assert(class != .no_possible_value), // field types are all extern, so are not NPV
353 .@"packed" => unreachable,
354 }
355
350 if (struct_obj.layout == .auto) {356 if (struct_obj.layout == .auto) {
351 const runtime_order = struct_obj.field_runtime_order.get(ip);357 const runtime_order = struct_obj.field_runtime_order.get(ip);
352 // This logic does not reorder fields; it only moves the omitted ones to the end so that logic358 // This logic does not reorder fields; it only moves the omitted ones to the end so that logic
...@@ -451,7 +457,12 @@ fn resolvePackedStructLayout(...@@ -451,7 +457,12 @@ fn resolvePackedStructLayout(
451 try sema.addDeclaredHereNote(msg, field_ty);457 try sema.addDeclaredHereNote(msg, field_ty);
452 break :msg msg;458 break :msg msg;
453 });459 });
454 assert(!field_ty.comptimeOnly(zcu)); // packable types are not comptime-only460 switch (field_ty.classify(zcu)) {
461 .one_possible_value, .runtime => {},
462 .no_possible_value => unreachable, // packable types are not NPV
463 .partially_comptime => unreachable, // packable types are not comptime-only
464 .fully_comptime => unreachable, // packable types are not comptime-only
465 }
455 field_bits += field_ty.bitSize(zcu);466 field_bits += field_ty.bitSize(zcu);
456 }467 }
457468
...@@ -749,6 +760,13 @@ pub fn resolveUnionLayout(sema: *Sema, union_ty: Type) CompileError!void {...@@ -749,6 +760,13 @@ pub fn resolveUnionLayout(sema: *Sema, union_ty: Type) CompileError!void {
749 }760 }
750 }761 }
751762
763 // Uninstantiable `extern union`s don't make sense; disallow them.
764 if (possible_tags == 0 and union_obj.layout != .auto) {
765 // Field types are all extern, so not NPV; thus zero possible tags means no tags at all.
766 assert(union_obj.field_types.len == 0);
767 return sema.fail(&block, union_ty.srcLoc(zcu), "extern union has no fields", .{});
768 }
769
752 // We only need a runtime tag if there are multiple possible active fields *and* the union is770 // We only need a runtime tag if there are multiple possible active fields *and* the union is
753 // not going to be comptime-only. Even if there are still runtime bits in the payload, the tag771 // not going to be comptime-only. Even if there are still runtime bits in the payload, the tag
754 // does not require runtime bits in a comptime-only union, because it is impossible to get a772 // does not require runtime bits in a comptime-only union, because it is impossible to get a
...@@ -874,6 +892,11 @@ fn resolvePackedUnionLayout(...@@ -874,6 +892,11 @@ fn resolvePackedUnionLayout(
874 const gpa = comp.gpa;892 const gpa = comp.gpa;
875 const ip = &zcu.intern_pool;893 const ip = &zcu.intern_pool;
876894
895 // Uninstantiable `packed union`s don't make sense; disallow them.
896 if (union_obj.field_types.len == 0) {
897 return sema.fail(block, union_ty.srcLoc(zcu), "packed union has no fields", .{});
898 }
899
877 // Resolve the layout of all fields, and check their types are allowed.900 // Resolve the layout of all fields, and check their types are allowed.
878 for (union_obj.field_types.get(ip), 0..) |field_ty_ip, field_index| {901 for (union_obj.field_types.get(ip), 0..) |field_ty_ip, field_index| {
879 const field_ty: Type = .fromInterned(field_ty_ip);902 const field_ty: Type = .fromInterned(field_ty_ip);
...@@ -937,9 +960,6 @@ fn resolvePackedUnionLayout(...@@ -937,9 +960,6 @@ fn resolvePackedUnionLayout(
937 });960 });
938 }961 }
939 break :ty backing_ty;962 break :ty backing_ty;
940 } else if (union_obj.field_types.len == 0) ty: {
941 // Special case: there is no first field to infer the type from. Treat the union as empty (zero-bit).
942 break :ty .u0;
943 } else ty: {963 } else ty: {
944 const field_types = union_obj.field_types.get(ip);964 const field_types = union_obj.field_types.get(ip);
945 const first_field_type: Type = .fromInterned(field_types[0]);965 const first_field_type: Type = .fromInterned(field_types[0]);