authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-12-26 21:07:16-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-12-26 21:07:16-07:00
log629a54c711c0a71381ea1e5b1a62e5fcc986e048
treeca1753a023ae8593f048e880b376a108b0436125
parent405ff911dae3ca10af380e5dd8e2dfda4a570191

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.

5 files changed, 178 insertions(+), 57 deletions(-)

lib/std/builtin.zig+1
......@@ -334,6 +334,7 @@ pub const TypeInfo = union(enum) {
334334 /// This data structure is used by the Zig language code generation and
335335 /// therefore must be kept in sync with the compiler implementation.
336336 pub const Enum = struct {
337 /// TODO enums should no longer have this field in type info.
337338 layout: ContainerLayout,
338339 tag_type: type,
339340 fields: []const EnumField,
src/Sema.zig+116-6
......@@ -5951,7 +5951,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
59515951 }
59525952 const all_tags_handled = for (seen_fields) |seen_src| {
59535953 if (seen_src == null) break false;
5954 } else true;
5954 } else !operand_ty.isNonexhaustiveEnum();
59555955
59565956 switch (special_prong) {
59575957 .none => {
......@@ -9035,9 +9035,119 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
90359035 }),
90369036 );
90379037 },
9038 else => |t| return sema.fail(block, src, "TODO: implement zirTypeInfo for {s}", .{
9039 @tagName(t),
9040 }),
9038 .Enum => {
9039 // TODO: look into memoizing this result.
9040 var int_tag_type_buffer: Type.Payload.Bits = undefined;
9041 const int_tag_ty = try ty.intTagType(&int_tag_type_buffer).copy(sema.arena);
9042
9043 const is_exhaustive = if (ty.isNonexhaustiveEnum()) Value.@"false" else Value.@"true";
9044
9045 var fields_anon_decl = try block.startAnonDecl();
9046 defer fields_anon_decl.deinit();
9047
9048 const enum_field_ty = t: {
9049 const enum_field_ty_decl = (try sema.namespaceLookup(
9050 block,
9051 src,
9052 type_info_ty.getNamespace().?,
9053 "EnumField",
9054 )).?;
9055 try sema.mod.declareDeclDependency(sema.owner_decl, enum_field_ty_decl);
9056 try sema.ensureDeclAnalyzed(enum_field_ty_decl);
9057 var buffer: Value.ToTypeBuffer = undefined;
9058 break :t try enum_field_ty_decl.val.toType(&buffer).copy(fields_anon_decl.arena());
9059 };
9060
9061 const enum_fields = ty.enumFields();
9062 const enum_field_vals = try fields_anon_decl.arena().alloc(Value, enum_fields.count());
9063
9064 for (enum_field_vals) |*field_val, i| {
9065 var tag_val_payload: Value.Payload.U32 = .{
9066 .base = .{ .tag = .enum_field_index },
9067 .data = @intCast(u32, i),
9068 };
9069 const tag_val = Value.initPayload(&tag_val_payload.base);
9070
9071 var buffer: Value.Payload.U64 = undefined;
9072 const int_val = try tag_val.enumToInt(ty, &buffer).copy(fields_anon_decl.arena());
9073
9074 const name = enum_fields.keys()[i];
9075 const name_val = v: {
9076 var anon_decl = try block.startAnonDecl();
9077 defer anon_decl.deinit();
9078 const bytes = try anon_decl.arena().dupeZ(u8, name);
9079 const new_decl = try anon_decl.finish(
9080 try Type.Tag.array_u8_sentinel_0.create(anon_decl.arena(), bytes.len),
9081 try Value.Tag.bytes.create(anon_decl.arena(), bytes[0 .. bytes.len + 1]),
9082 );
9083 break :v try Value.Tag.decl_ref.create(fields_anon_decl.arena(), new_decl);
9084 };
9085
9086 const enum_field_fields = try fields_anon_decl.arena().create([2]Value);
9087 enum_field_fields.* = .{
9088 // name: []const u8,
9089 name_val,
9090 // value: comptime_int,
9091 int_val,
9092 };
9093 field_val.* = try Value.Tag.@"struct".create(fields_anon_decl.arena(), enum_field_fields);
9094 }
9095
9096 const fields_val = v: {
9097 const new_decl = try fields_anon_decl.finish(
9098 try Type.Tag.array.create(fields_anon_decl.arena(), .{
9099 .len = enum_field_vals.len,
9100 .elem_type = enum_field_ty,
9101 }),
9102 try Value.Tag.array.create(
9103 fields_anon_decl.arena(),
9104 try fields_anon_decl.arena().dupe(Value, enum_field_vals),
9105 ),
9106 );
9107 break :v try Value.Tag.decl_ref.create(sema.arena, new_decl);
9108 };
9109
9110 if (ty.getNamespace()) |namespace| {
9111 if (namespace.decls.count() != 0) {
9112 return sema.fail(block, src, "TODO: implement zirTypeInfo for Enum which has declarations", .{});
9113 }
9114 }
9115 const decls_val = Value.initTag(.empty_array);
9116
9117 const field_values = try sema.arena.create([5]Value);
9118 field_values.* = .{
9119 // layout: ContainerLayout,
9120 try Value.Tag.enum_field_index.create(
9121 sema.arena,
9122 @enumToInt(std.builtin.TypeInfo.ContainerLayout.Auto),
9123 ),
9124
9125 // tag_type: type,
9126 try Value.Tag.ty.create(sema.arena, int_tag_ty),
9127 // fields: []const EnumField,
9128 fields_val,
9129 // decls: []const Declaration,
9130 decls_val,
9131 // is_exhaustive: bool,
9132 is_exhaustive,
9133 };
9134
9135 return sema.addConstant(
9136 type_info_ty,
9137 try Value.Tag.@"union".create(sema.arena, .{
9138 .tag = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(std.builtin.TypeId.Enum)),
9139 .val = try Value.Tag.@"struct".create(sema.arena, field_values),
9140 }),
9141 );
9142 },
9143 .Struct => return sema.fail(block, src, "TODO: implement zirTypeInfo for Struct", .{}),
9144 .ErrorSet => return sema.fail(block, src, "TODO: implement zirTypeInfo for ErrorSet", .{}),
9145 .Union => return sema.fail(block, src, "TODO: implement zirTypeInfo for Union", .{}),
9146 .BoundFn => @panic("TODO remove this type from the language and compiler"),
9147 .Opaque => return sema.fail(block, src, "TODO: implement zirTypeInfo for Opaque", .{}),
9148 .Frame => return sema.fail(block, src, "TODO: implement zirTypeInfo for Frame", .{}),
9149 .AnyFrame => return sema.fail(block, src, "TODO: implement zirTypeInfo for AnyFrame", .{}),
9150 .Vector => return sema.fail(block, src, "TODO: implement zirTypeInfo for Vector", .{}),
90419151 }
90429152}
90439153
......@@ -15153,13 +15263,13 @@ fn getBuiltin(
1515315263 );
1515415264 const builtin_inst = try sema.analyzeLoad(block, src, opt_builtin_inst.?, src);
1515515265 const builtin_ty = try sema.analyzeAsType(block, src, builtin_inst);
15156 const opt_ty_inst = try sema.namespaceLookupRef(
15266 const opt_ty_decl = try sema.namespaceLookup(
1515715267 block,
1515815268 src,
1515915269 builtin_ty.getNamespace().?,
1516015270 name,
1516115271 );
15162 return sema.analyzeLoad(block, src, opt_ty_inst.?, src);
15272 return sema.analyzeDeclVal(block, src, opt_ty_decl.?);
1516315273}
1516415274
1516515275fn getBuiltinType(
src/value.zig+20-7
......@@ -1461,14 +1461,25 @@ pub const Value = extern union {
14611461 return false;
14621462 }
14631463
1464 if (ty.zigTypeTag() == .Type) {
1465 var buf_a: ToTypeBuffer = undefined;
1466 var buf_b: ToTypeBuffer = undefined;
1467 const a_type = a.toType(&buf_a);
1468 const b_type = b.toType(&buf_b);
1469 return a_type.eql(b_type);
1464 switch (ty.zigTypeTag()) {
1465 .Type => {
1466 var buf_a: ToTypeBuffer = undefined;
1467 var buf_b: ToTypeBuffer = undefined;
1468 const a_type = a.toType(&buf_a);
1469 const b_type = b.toType(&buf_b);
1470 return a_type.eql(b_type);
1471 },
1472 .Enum => {
1473 var buf_a: Payload.U64 = undefined;
1474 var buf_b: Payload.U64 = undefined;
1475 const a_val = a.enumToInt(ty, &buf_a);
1476 const b_val = b.enumToInt(ty, &buf_b);
1477 var buf_ty: Type.Payload.Bits = undefined;
1478 const int_ty = ty.intTagType(&buf_ty);
1479 return eql(a_val, b_val, int_ty);
1480 },
1481 else => return order(a, b).compare(.eq),
14701482 }
1471 return order(a, b).compare(.eq);
14721483 }
14731484
14741485 pub fn hash(val: Value, ty: Type, hasher: *std.hash.Wyhash) void {
......@@ -3037,6 +3048,8 @@ pub const Value = extern union {
30373048 pub const undef = initTag(.undef);
30383049 pub const @"void" = initTag(.void_value);
30393050 pub const @"null" = initTag(.null_value);
3051 pub const @"false" = initTag(.bool_false);
3052 pub const @"true" = initTag(.bool_true);
30403053};
30413054
30423055var negative_one_payload: Value.Payload.I64 = .{
test/behavior/enum.zig+41
......@@ -605,3 +605,44 @@ test "enum with specified tag values" {
605605 try testEnumWithSpecifiedTagValues(MultipleChoice.C);
606606 comptime try testEnumWithSpecifiedTagValues(MultipleChoice.C);
607607}
608
609test "non-exhaustive enum" {
610 const S = struct {
611 const E = enum(u8) { a, b, _ };
612
613 fn doTheTest(y: u8) !void {
614 var e: E = .b;
615 try expect(switch (e) {
616 .a => false,
617 .b => true,
618 _ => false,
619 });
620 e = @intToEnum(E, 12);
621 try expect(switch (e) {
622 .a => false,
623 .b => false,
624 _ => true,
625 });
626
627 try expect(switch (e) {
628 .a => false,
629 .b => false,
630 else => true,
631 });
632 e = .b;
633 try expect(switch (e) {
634 .a => false,
635 else => true,
636 });
637
638 try expect(@typeInfo(E).Enum.fields.len == 2);
639 e = @intToEnum(E, 12);
640 try expect(@enumToInt(e) == 12);
641 e = @intToEnum(E, y);
642 try expect(@enumToInt(e) == 52);
643 try expect(@typeInfo(E).Enum.is_exhaustive == false);
644 }
645 };
646 try S.doTheTest(52);
647 comptime try S.doTheTest(52);
648}
test/behavior/enum_stage1.zig-44
......@@ -2,50 +2,6 @@ const expect = @import("std").testing.expect;
22const mem = @import("std").mem;
33const Tag = @import("std").meta.Tag;
44
5test "non-exhaustive enum" {
6 const S = struct {
7 const E = enum(u8) {
8 a,
9 b,
10 _,
11 };
12 fn doTheTest(y: u8) !void {
13 var e: E = .b;
14 try expect(switch (e) {
15 .a => false,
16 .b => true,
17 _ => false,
18 });
19 e = @intToEnum(E, 12);
20 try expect(switch (e) {
21 .a => false,
22 .b => false,
23 _ => true,
24 });
25
26 try expect(switch (e) {
27 .a => false,
28 .b => false,
29 else => true,
30 });
31 e = .b;
32 try expect(switch (e) {
33 .a => false,
34 else => true,
35 });
36
37 try expect(@typeInfo(E).Enum.fields.len == 2);
38 e = @intToEnum(E, 12);
39 try expect(@enumToInt(e) == 12);
40 e = @intToEnum(E, y);
41 try expect(@enumToInt(e) == 52);
42 try expect(@typeInfo(E).Enum.is_exhaustive == false);
43 }
44 };
45 try S.doTheTest(52);
46 comptime try S.doTheTest(52);
47}
48
495test "empty non-exhaustive enum" {
506 const S = struct {
517 const E = enum(u8) {