Commit 629a54c711

Andrew Kelley <andrew@ziglang.org>
2021-12-27 05:07:16
Sema: improve non-exhaustive enum support
* remove false positive "all prongs handled" compile error for non-exhaustive enums. * implement `@TypeInfo` for enums, except enums which have any declarations is still TODO. * `getBuiltin` uses nomespaceLookup/analyzeDeclVal rather than namespaceLookupRef/analyzeLoad. Avoids a detour through an unnecessary type, and adds a detour through a caching mechanism. * `Value.eql`: add missing code to handle enum comparisons for non-exhaustive enums. It works by converting the enum tags to numeric values and comparing those.
1 parent 405ff91
Changed files (5)
lib/std/builtin.zig
@@ -334,6 +334,7 @@ pub const TypeInfo = union(enum) {
     /// This data structure is used by the Zig language code generation and
     /// therefore must be kept in sync with the compiler implementation.
     pub const Enum = struct {
+        /// TODO enums should no longer have this field in type info.
         layout: ContainerLayout,
         tag_type: type,
         fields: []const EnumField,
src/Sema.zig
@@ -5951,7 +5951,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
             }
             const all_tags_handled = for (seen_fields) |seen_src| {
                 if (seen_src == null) break false;
-            } else true;
+            } else !operand_ty.isNonexhaustiveEnum();
 
             switch (special_prong) {
                 .none => {
@@ -9035,9 +9035,119 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                 }),
             );
         },
-        else => |t| return sema.fail(block, src, "TODO: implement zirTypeInfo for {s}", .{
-            @tagName(t),
-        }),
+        .Enum => {
+            // TODO: look into memoizing this result.
+            var int_tag_type_buffer: Type.Payload.Bits = undefined;
+            const int_tag_ty = try ty.intTagType(&int_tag_type_buffer).copy(sema.arena);
+
+            const is_exhaustive = if (ty.isNonexhaustiveEnum()) Value.@"false" else Value.@"true";
+
+            var fields_anon_decl = try block.startAnonDecl();
+            defer fields_anon_decl.deinit();
+
+            const enum_field_ty = t: {
+                const enum_field_ty_decl = (try sema.namespaceLookup(
+                    block,
+                    src,
+                    type_info_ty.getNamespace().?,
+                    "EnumField",
+                )).?;
+                try sema.mod.declareDeclDependency(sema.owner_decl, enum_field_ty_decl);
+                try sema.ensureDeclAnalyzed(enum_field_ty_decl);
+                var buffer: Value.ToTypeBuffer = undefined;
+                break :t try enum_field_ty_decl.val.toType(&buffer).copy(fields_anon_decl.arena());
+            };
+
+            const enum_fields = ty.enumFields();
+            const enum_field_vals = try fields_anon_decl.arena().alloc(Value, enum_fields.count());
+
+            for (enum_field_vals) |*field_val, i| {
+                var tag_val_payload: Value.Payload.U32 = .{
+                    .base = .{ .tag = .enum_field_index },
+                    .data = @intCast(u32, i),
+                };
+                const tag_val = Value.initPayload(&tag_val_payload.base);
+
+                var buffer: Value.Payload.U64 = undefined;
+                const int_val = try tag_val.enumToInt(ty, &buffer).copy(fields_anon_decl.arena());
+
+                const name = enum_fields.keys()[i];
+                const name_val = v: {
+                    var anon_decl = try block.startAnonDecl();
+                    defer anon_decl.deinit();
+                    const bytes = try anon_decl.arena().dupeZ(u8, name);
+                    const new_decl = try anon_decl.finish(
+                        try Type.Tag.array_u8_sentinel_0.create(anon_decl.arena(), bytes.len),
+                        try Value.Tag.bytes.create(anon_decl.arena(), bytes[0 .. bytes.len + 1]),
+                    );
+                    break :v try Value.Tag.decl_ref.create(fields_anon_decl.arena(), new_decl);
+                };
+
+                const enum_field_fields = try fields_anon_decl.arena().create([2]Value);
+                enum_field_fields.* = .{
+                    // name: []const u8,
+                    name_val,
+                    // value: comptime_int,
+                    int_val,
+                };
+                field_val.* = try Value.Tag.@"struct".create(fields_anon_decl.arena(), enum_field_fields);
+            }
+
+            const fields_val = v: {
+                const new_decl = try fields_anon_decl.finish(
+                    try Type.Tag.array.create(fields_anon_decl.arena(), .{
+                        .len = enum_field_vals.len,
+                        .elem_type = enum_field_ty,
+                    }),
+                    try Value.Tag.array.create(
+                        fields_anon_decl.arena(),
+                        try fields_anon_decl.arena().dupe(Value, enum_field_vals),
+                    ),
+                );
+                break :v try Value.Tag.decl_ref.create(sema.arena, new_decl);
+            };
+
+            if (ty.getNamespace()) |namespace| {
+                if (namespace.decls.count() != 0) {
+                    return sema.fail(block, src, "TODO: implement zirTypeInfo for Enum which has declarations", .{});
+                }
+            }
+            const decls_val = Value.initTag(.empty_array);
+
+            const field_values = try sema.arena.create([5]Value);
+            field_values.* = .{
+                // layout: ContainerLayout,
+                try Value.Tag.enum_field_index.create(
+                    sema.arena,
+                    @enumToInt(std.builtin.TypeInfo.ContainerLayout.Auto),
+                ),
+
+                // tag_type: type,
+                try Value.Tag.ty.create(sema.arena, int_tag_ty),
+                // fields: []const EnumField,
+                fields_val,
+                // decls: []const Declaration,
+                decls_val,
+                // is_exhaustive: bool,
+                is_exhaustive,
+            };
+
+            return sema.addConstant(
+                type_info_ty,
+                try Value.Tag.@"union".create(sema.arena, .{
+                    .tag = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(std.builtin.TypeId.Enum)),
+                    .val = try Value.Tag.@"struct".create(sema.arena, field_values),
+                }),
+            );
+        },
+        .Struct => return sema.fail(block, src, "TODO: implement zirTypeInfo for Struct", .{}),
+        .ErrorSet => return sema.fail(block, src, "TODO: implement zirTypeInfo for ErrorSet", .{}),
+        .Union => return sema.fail(block, src, "TODO: implement zirTypeInfo for Union", .{}),
+        .BoundFn => @panic("TODO remove this type from the language and compiler"),
+        .Opaque => return sema.fail(block, src, "TODO: implement zirTypeInfo for Opaque", .{}),
+        .Frame => return sema.fail(block, src, "TODO: implement zirTypeInfo for Frame", .{}),
+        .AnyFrame => return sema.fail(block, src, "TODO: implement zirTypeInfo for AnyFrame", .{}),
+        .Vector => return sema.fail(block, src, "TODO: implement zirTypeInfo for Vector", .{}),
     }
 }
 
@@ -15153,13 +15263,13 @@ fn getBuiltin(
     );
     const builtin_inst = try sema.analyzeLoad(block, src, opt_builtin_inst.?, src);
     const builtin_ty = try sema.analyzeAsType(block, src, builtin_inst);
-    const opt_ty_inst = try sema.namespaceLookupRef(
+    const opt_ty_decl = try sema.namespaceLookup(
         block,
         src,
         builtin_ty.getNamespace().?,
         name,
     );
-    return sema.analyzeLoad(block, src, opt_ty_inst.?, src);
+    return sema.analyzeDeclVal(block, src, opt_ty_decl.?);
 }
 
 fn getBuiltinType(
src/value.zig
@@ -1461,14 +1461,25 @@ pub const Value = extern union {
             return false;
         }
 
-        if (ty.zigTypeTag() == .Type) {
-            var buf_a: ToTypeBuffer = undefined;
-            var buf_b: ToTypeBuffer = undefined;
-            const a_type = a.toType(&buf_a);
-            const b_type = b.toType(&buf_b);
-            return a_type.eql(b_type);
+        switch (ty.zigTypeTag()) {
+            .Type => {
+                var buf_a: ToTypeBuffer = undefined;
+                var buf_b: ToTypeBuffer = undefined;
+                const a_type = a.toType(&buf_a);
+                const b_type = b.toType(&buf_b);
+                return a_type.eql(b_type);
+            },
+            .Enum => {
+                var buf_a: Payload.U64 = undefined;
+                var buf_b: Payload.U64 = undefined;
+                const a_val = a.enumToInt(ty, &buf_a);
+                const b_val = b.enumToInt(ty, &buf_b);
+                var buf_ty: Type.Payload.Bits = undefined;
+                const int_ty = ty.intTagType(&buf_ty);
+                return eql(a_val, b_val, int_ty);
+            },
+            else => return order(a, b).compare(.eq),
         }
-        return order(a, b).compare(.eq);
     }
 
     pub fn hash(val: Value, ty: Type, hasher: *std.hash.Wyhash) void {
@@ -3037,6 +3048,8 @@ pub const Value = extern union {
     pub const undef = initTag(.undef);
     pub const @"void" = initTag(.void_value);
     pub const @"null" = initTag(.null_value);
+    pub const @"false" = initTag(.bool_false);
+    pub const @"true" = initTag(.bool_true);
 };
 
 var negative_one_payload: Value.Payload.I64 = .{
test/behavior/enum.zig
@@ -605,3 +605,44 @@ test "enum with specified tag values" {
     try testEnumWithSpecifiedTagValues(MultipleChoice.C);
     comptime try testEnumWithSpecifiedTagValues(MultipleChoice.C);
 }
+
+test "non-exhaustive enum" {
+    const S = struct {
+        const E = enum(u8) { a, b, _ };
+
+        fn doTheTest(y: u8) !void {
+            var e: E = .b;
+            try expect(switch (e) {
+                .a => false,
+                .b => true,
+                _ => false,
+            });
+            e = @intToEnum(E, 12);
+            try expect(switch (e) {
+                .a => false,
+                .b => false,
+                _ => true,
+            });
+
+            try expect(switch (e) {
+                .a => false,
+                .b => false,
+                else => true,
+            });
+            e = .b;
+            try expect(switch (e) {
+                .a => false,
+                else => true,
+            });
+
+            try expect(@typeInfo(E).Enum.fields.len == 2);
+            e = @intToEnum(E, 12);
+            try expect(@enumToInt(e) == 12);
+            e = @intToEnum(E, y);
+            try expect(@enumToInt(e) == 52);
+            try expect(@typeInfo(E).Enum.is_exhaustive == false);
+        }
+    };
+    try S.doTheTest(52);
+    comptime try S.doTheTest(52);
+}
test/behavior/enum_stage1.zig
@@ -2,50 +2,6 @@ const expect = @import("std").testing.expect;
 const mem = @import("std").mem;
 const Tag = @import("std").meta.Tag;
 
-test "non-exhaustive enum" {
-    const S = struct {
-        const E = enum(u8) {
-            a,
-            b,
-            _,
-        };
-        fn doTheTest(y: u8) !void {
-            var e: E = .b;
-            try expect(switch (e) {
-                .a => false,
-                .b => true,
-                _ => false,
-            });
-            e = @intToEnum(E, 12);
-            try expect(switch (e) {
-                .a => false,
-                .b => false,
-                _ => true,
-            });
-
-            try expect(switch (e) {
-                .a => false,
-                .b => false,
-                else => true,
-            });
-            e = .b;
-            try expect(switch (e) {
-                .a => false,
-                else => true,
-            });
-
-            try expect(@typeInfo(E).Enum.fields.len == 2);
-            e = @intToEnum(E, 12);
-            try expect(@enumToInt(e) == 12);
-            e = @intToEnum(E, y);
-            try expect(@enumToInt(e) == 52);
-            try expect(@typeInfo(E).Enum.is_exhaustive == false);
-        }
-    };
-    try S.doTheTest(52);
-    comptime try S.doTheTest(52);
-}
-
 test "empty non-exhaustive enum" {
     const S = struct {
         const E = enum(u8) {