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
signaturelock-open 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 {
347347 }
348348 };
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
350356 if (struct_obj.layout == .auto) {
351357 const runtime_order = struct_obj.field_runtime_order.get(ip);
352358 // This logic does not reorder fields; it only moves the omitted ones to the end so that logic
......@@ -451,7 +457,12 @@ fn resolvePackedStructLayout(
451457 try sema.addDeclaredHereNote(msg, field_ty);
452458 break :msg msg;
453459 });
454 assert(!field_ty.comptimeOnly(zcu)); // packable types are not comptime-only
460 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 }
455466 field_bits += field_ty.bitSize(zcu);
456467 }
457468
......@@ -749,6 +760,13 @@ pub fn resolveUnionLayout(sema: *Sema, union_ty: Type) CompileError!void {
749760 }
750761 }
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
752770 // We only need a runtime tag if there are multiple possible active fields *and* the union is
753771 // not going to be comptime-only. Even if there are still runtime bits in the payload, the tag
754772 // does not require runtime bits in a comptime-only union, because it is impossible to get a
......@@ -874,6 +892,11 @@ fn resolvePackedUnionLayout(
874892 const gpa = comp.gpa;
875893 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
877900 // Resolve the layout of all fields, and check their types are allowed.
878901 for (union_obj.field_types.get(ip), 0..) |field_ty_ip, field_index| {
879902 const field_ty: Type = .fromInterned(field_ty_ip);
......@@ -937,9 +960,6 @@ fn resolvePackedUnionLayout(
937960 });
938961 }
939962 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;
943963 } else ty: {
944964 const field_types = union_obj.field_types.get(ip);
945965 const first_field_type: Type = .fromInterned(field_types[0]);