Commit a0d4ef0acf

mlugg <mlugg@mlugg.co.uk>
2023-05-31 05:42:18
InternPool: add representation for value of empty enums and unions
This is a bit odd, because this value doesn't actually exist: see #15909. This gets all the empty enum/union behavior tests passing. Also adds an assertion to `Sema.analyzeBodyInner` which would have helped figure out the issue here much more quickly.
1 parent 99531b0
src/arch/wasm/CodeGen.zig
@@ -3156,6 +3156,7 @@ fn lowerConstant(func: *CodeGen, arg_val: Value, ty: Type) InnerError!WValue {
         .extern_func,
         .func,
         .enum_literal,
+        .empty_enum_value,
         => unreachable, // non-runtime values
         .int => {
             const int_info = ty.intInfo(mod);
src/codegen/c.zig
@@ -946,6 +946,7 @@ pub const DeclGen = struct {
             .extern_func,
             .func,
             .enum_literal,
+            .empty_enum_value,
             => unreachable, // non-runtime values
             .int => |int| switch (int.storage) {
                 .u64, .i64, .big_int => try writer.print("{}", .{try dg.fmtIntLiteral(ty, val, location)}),
src/codegen/llvm.zig
@@ -3246,6 +3246,7 @@ pub const DeclGen = struct {
             },
             .variable,
             .enum_literal,
+            .empty_enum_value,
             => unreachable, // non-runtime values
             .extern_func, .func => {
                 const fn_decl_index = switch (val_key) {
src/codegen/spirv.zig
@@ -660,6 +660,7 @@ pub const DeclGen = struct {
                 .extern_func,
                 .func,
                 .enum_literal,
+                .empty_enum_value,
                 => unreachable, // non-runtime values
                 .int => try self.addInt(ty, val),
                 .err => |err| {
src/codegen.zig
@@ -242,6 +242,7 @@ pub fn generateSymbol(
         .extern_func,
         .func,
         .enum_literal,
+        .empty_enum_value,
         => unreachable, // non-runtime values
         .int => {
             const abi_size = math.cast(usize, typed_value.ty.abiSize(mod)) orelse return error.Overflow;
src/InternPool.zig
@@ -206,6 +206,10 @@ pub const Key = union(enum) {
     enum_literal: NullTerminatedString,
     /// A specific enum tag, indicated by the integer tag value.
     enum_tag: Key.EnumTag,
+    /// An empty enum or union. TODO: this value's existence is strange, because such a type in
+    /// reality has no values. See #15909.
+    /// Payload is the type for which we are an empty value.
+    empty_enum_value: Index,
     float: Key.Float,
     ptr: Ptr,
     opt: Opt,
@@ -670,6 +674,7 @@ pub const Key = union(enum) {
             .error_union,
             .enum_literal,
             .enum_tag,
+            .empty_enum_value,
             .inferred_error_set_type,
             => |info| {
                 var hasher = std.hash.Wyhash.init(seed);
@@ -957,6 +962,10 @@ pub const Key = union(enum) {
                 const b_info = b.enum_tag;
                 return std.meta.eql(a_info, b_info);
             },
+            .empty_enum_value => |a_info| {
+                const b_info = b.empty_enum_value;
+                return a_info == b_info;
+            },
 
             .variable => |a_info| {
                 const b_info = b.variable;
@@ -1192,6 +1201,7 @@ pub const Key = union(enum) {
             .enum_literal => .enum_literal_type,
 
             .undef => |x| x,
+            .empty_enum_value => |x| x,
 
             .simple_value => |s| switch (s) {
                 .undefined => .undefined_type,
@@ -1980,6 +1990,7 @@ pub const Tag = enum(u8) {
     /// The set of values that are encoded this way is:
     /// * An array or vector which has length 0.
     /// * A struct which has all fields comptime-known.
+    /// * An empty enum or union. TODO: this value's existence is strange, because such a type in reality has no values. See #15909
     /// data is Index of the type, which is known to be zero bits at runtime.
     only_possible_value,
     /// data is extra index to Key.Union.
@@ -2952,6 +2963,13 @@ pub fn indexToKey(ip: *const InternPool, index: Index) Key {
                     } };
                 },
 
+                .type_enum_auto,
+                .type_enum_explicit,
+                .type_union_tagged,
+                .type_union_untagged,
+                .type_union_safety,
+                => .{ .empty_enum_value = ty },
+
                 else => unreachable,
             };
         },
@@ -3755,6 +3773,11 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) Allocator.Error!Index {
             });
         },
 
+        .empty_enum_value => |enum_or_union_ty| ip.items.appendAssumeCapacity(.{
+            .tag = .only_possible_value,
+            .data = @enumToInt(enum_or_union_ty),
+        }),
+
         .float => |float| {
             switch (float.ty) {
                 .f16_type => ip.items.appendAssumeCapacity(.{
@@ -5416,7 +5439,6 @@ pub fn isNoReturn(ip: *const InternPool, ty: Index) bool {
         .noreturn_type => true,
         else => switch (ip.indexToKey(ty)) {
             .error_set_type => |error_set_type| error_set_type.names.len == 0,
-            .enum_type => |enum_type| enum_type.names.len == 0,
             else => false,
         },
     };
src/Sema.zig
@@ -1725,8 +1725,12 @@ fn analyzeBodyInner(
                 break :blk Air.Inst.Ref.void_value;
             },
         };
-        if (sema.isNoReturn(air_inst))
+        if (sema.isNoReturn(air_inst)) {
+            // We're going to assume that the body itself is noreturn, so let's ensure that now
+            assert(block.instructions.items.len > 0);
+            assert(sema.isNoReturn(Air.indexToRef(block.instructions.items[block.instructions.items.len - 1])));
             break always_noreturn;
+        }
         map.putAssumeCapacity(inst, air_inst);
         i += 1;
     };
@@ -32150,6 +32154,7 @@ pub fn resolveTypeRequiresComptime(sema: *Sema, ty: Type) CompileError!bool {
             .error_union,
             .enum_literal,
             .enum_tag,
+            .empty_enum_value,
             .float,
             .ptr,
             .opt,
@@ -33015,10 +33020,6 @@ fn semaUnionFields(mod: *Module, union_obj: *Module.Union) CompileError!void {
         enum_field_names = try sema.arena.alloc(InternPool.NullTerminatedString, fields_len);
     }
 
-    if (fields_len == 0) {
-        return;
-    }
-
     const bits_per_field = 4;
     const fields_per_u32 = 32 / bits_per_field;
     const bit_bags_count = std.math.divCeil(usize, fields_len, fields_per_u32) catch unreachable;
@@ -33301,7 +33302,7 @@ fn generateUnionTagTypeNumbered(
         .decl = new_decl_index,
         .namespace = .none,
         .tag_ty = if (enum_field_vals.len == 0)
-            .noreturn_type
+            (try mod.intType(.unsigned, 0)).toIntern()
         else
             mod.intern_pool.typeOf(enum_field_vals[0]),
         .names = enum_field_names,
@@ -33351,7 +33352,7 @@ fn generateUnionTagTypeSimple(
         .decl = new_decl_index,
         .namespace = .none,
         .tag_ty = if (enum_field_names.len == 0)
-            .noreturn_type
+            (try mod.intType(.unsigned, 0)).toIntern()
         else
             (try mod.smallestUnsignedInt(enum_field_names.len - 1)).toIntern(),
         .names = enum_field_names,
@@ -33590,7 +33591,10 @@ pub fn typeHasOnePossibleValue(sema: *Sema, ty: Type) CompileError!?Value {
                 const tag_val = (try sema.typeHasOnePossibleValue(union_obj.tag_ty)) orelse
                     return null;
                 const fields = union_obj.fields.values();
-                if (fields.len == 0) return Value.@"unreachable";
+                if (fields.len == 0) {
+                    const only = try mod.intern(.{ .empty_enum_value = ty.toIntern() });
+                    return only.toValue();
+                }
                 const only_field = fields[0];
                 if (only_field.ty.eql(resolved_ty, sema.mod)) {
                     const msg = try Module.ErrorMsg.create(
@@ -33630,7 +33634,10 @@ pub fn typeHasOnePossibleValue(sema: *Sema, ty: Type) CompileError!?Value {
                     if (enum_type.tag_ty.toType().hasRuntimeBits(mod)) return null;
 
                     switch (enum_type.names.len) {
-                        0 => return Value.@"unreachable",
+                        0 => {
+                            const only = try mod.intern(.{ .empty_enum_value = ty.toIntern() });
+                            return only.toValue();
+                        },
                         1 => return try mod.getCoerced((if (enum_type.values.len == 0)
                             try mod.intern(.{ .int = .{
                                 .ty = enum_type.tag_ty,
@@ -33655,6 +33662,7 @@ pub fn typeHasOnePossibleValue(sema: *Sema, ty: Type) CompileError!?Value {
             .error_union,
             .enum_literal,
             .enum_tag,
+            .empty_enum_value,
             .float,
             .ptr,
             .opt,
@@ -34143,6 +34151,7 @@ pub fn typeRequiresComptime(sema: *Sema, ty: Type) CompileError!bool {
             .error_union,
             .enum_literal,
             .enum_tag,
+            .empty_enum_value,
             .float,
             .ptr,
             .opt,
@@ -34848,7 +34857,7 @@ fn errorSetMerge(sema: *Sema, lhs: Type, rhs: Type) !Type {
 
 /// Avoids crashing the compiler when asking if inferred allocations are noreturn.
 fn isNoReturn(sema: *Sema, ref: Air.Inst.Ref) bool {
-    if (ref == .noreturn_type) return true;
+    if (ref == .unreachable_value) return true;
     if (Air.refToIndex(ref)) |inst| switch (sema.air_instructions.items(.tag)[inst]) {
         .inferred_alloc, .inferred_alloc_comptime => return false,
         else => {},
src/type.zig
@@ -439,6 +439,7 @@ pub const Type = struct {
             .error_union,
             .enum_literal,
             .enum_tag,
+            .empty_enum_value,
             .float,
             .ptr,
             .opt,
@@ -655,6 +656,7 @@ pub const Type = struct {
                 .error_union,
                 .enum_literal,
                 .enum_tag,
+                .empty_enum_value,
                 .float,
                 .ptr,
                 .opt,
@@ -764,6 +766,7 @@ pub const Type = struct {
             .error_union,
             .enum_literal,
             .enum_tag,
+            .empty_enum_value,
             .float,
             .ptr,
             .opt,
@@ -1098,6 +1101,7 @@ pub const Type = struct {
                 .error_union,
                 .enum_literal,
                 .enum_tag,
+                .empty_enum_value,
                 .float,
                 .ptr,
                 .opt,
@@ -1515,6 +1519,7 @@ pub const Type = struct {
                 .error_union,
                 .enum_literal,
                 .enum_tag,
+                .empty_enum_value,
                 .float,
                 .ptr,
                 .opt,
@@ -1749,6 +1754,7 @@ pub const Type = struct {
             .error_union,
             .enum_literal,
             .enum_tag,
+            .empty_enum_value,
             .float,
             .ptr,
             .opt,
@@ -2302,6 +2308,7 @@ pub const Type = struct {
                 .error_union,
                 .enum_literal,
                 .enum_tag,
+                .empty_enum_value,
                 .float,
                 .ptr,
                 .opt,
@@ -2584,7 +2591,10 @@ pub const Type = struct {
                 .union_type => |union_type| {
                     const union_obj = mod.unionPtr(union_type.index);
                     const tag_val = (try union_obj.tag_ty.onePossibleValue(mod)) orelse return null;
-                    if (union_obj.fields.count() == 0) return Value.@"unreachable";
+                    if (union_obj.fields.count() == 0) {
+                        const only = try mod.intern(.{ .empty_enum_value = ty.toIntern() });
+                        return only.toValue();
+                    }
                     const only_field = union_obj.fields.values()[0];
                     const val_val = (try only_field.ty.onePossibleValue(mod)) orelse return null;
                     const only = try mod.intern(.{ .un = .{
@@ -2613,7 +2623,10 @@ pub const Type = struct {
                         if (enum_type.tag_ty.toType().hasRuntimeBits(mod)) return null;
 
                         switch (enum_type.names.len) {
-                            0 => return Value.@"unreachable",
+                            0 => {
+                                const only = try mod.intern(.{ .empty_enum_value = ty.toIntern() });
+                                return only.toValue();
+                            },
                             1 => {
                                 if (enum_type.values.len == 0) {
                                     const only = try mod.intern(.{ .enum_tag = .{
@@ -2645,6 +2658,7 @@ pub const Type = struct {
                 .error_union,
                 .enum_literal,
                 .enum_tag,
+                .empty_enum_value,
                 .float,
                 .ptr,
                 .opt,
@@ -2790,6 +2804,7 @@ pub const Type = struct {
                 .error_union,
                 .enum_literal,
                 .enum_tag,
+                .empty_enum_value,
                 .float,
                 .ptr,
                 .opt,
src/TypedValue.zig
@@ -248,6 +248,7 @@ pub fn print(
                 try writer.writeAll(")");
                 return;
             },
+            .empty_enum_value => return writer.writeAll("(empty enum value)"),
             .float => |float| switch (float.storage) {
                 inline else => |x| return writer.print("{}", .{x}),
             },
src/value.zig
@@ -441,6 +441,7 @@ pub const Value = struct {
             .err,
             .enum_literal,
             .enum_tag,
+            .empty_enum_value,
             .float,
             => val,