| author | |
| committer | |
| log | 629a54c711c0a71381ea1e5b1a62e5fcc986e048 |
| tree | ca1753a023ae8593f048e880b376a108b0436125 |
| parent | 405ff911dae3ca10af380e5dd8e2dfda4a570191 |
* 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) { | ... | @@ -334,6 +334,7 @@ pub const TypeInfo = union(enum) { |
| 334 | /// This data structure is used by the Zig language code generation and | 334 | /// This data structure is used by the Zig language code generation and |
| 335 | /// therefore must be kept in sync with the compiler implementation. | 335 | /// therefore must be kept in sync with the compiler implementation. |
| 336 | pub const Enum = struct { | 336 | pub const Enum = struct { |
| 337 | /// TODO enums should no longer have this field in type info. | ||
| 337 | layout: ContainerLayout, | 338 | layout: ContainerLayout, |
| 338 | tag_type: type, | 339 | tag_type: type, |
| 339 | fields: []const EnumField, | 340 | fields: []const EnumField, |
src/Sema.zig+116-6| ... | @@ -5951,7 +5951,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError | ... | @@ -5951,7 +5951,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError |
| 5951 | } | 5951 | } |
| 5952 | const all_tags_handled = for (seen_fields) |seen_src| { | 5952 | const all_tags_handled = for (seen_fields) |seen_src| { |
| 5953 | if (seen_src == null) break false; | 5953 | if (seen_src == null) break false; |
| 5954 | } else true; | 5954 | } else !operand_ty.isNonexhaustiveEnum(); |
| 5955 | 5955 | ||
| 5956 | switch (special_prong) { | 5956 | switch (special_prong) { |
| 5957 | .none => { | 5957 | .none => { |
| ... | @@ -9035,9 +9035,119 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai | ... | @@ -9035,9 +9035,119 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai |
| 9035 | }), | 9035 | }), |
| 9036 | ); | 9036 | ); |
| 9037 | }, | 9037 | }, |
| 9038 | else => |t| return sema.fail(block, src, "TODO: implement zirTypeInfo for {s}", .{ | 9038 | .Enum => { |
| 9039 | @tagName(t), | 9039 | // TODO: look into memoizing this result. |
| 9040 | }), | 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", .{}), | ||
| 9041 | } | 9151 | } |
| 9042 | } | 9152 | } |
| 9043 | 9153 | ||
| ... | @@ -15153,13 +15263,13 @@ fn getBuiltin( | ... | @@ -15153,13 +15263,13 @@ fn getBuiltin( |
| 15153 | ); | 15263 | ); |
| 15154 | const builtin_inst = try sema.analyzeLoad(block, src, opt_builtin_inst.?, src); | 15264 | const builtin_inst = try sema.analyzeLoad(block, src, opt_builtin_inst.?, src); |
| 15155 | const builtin_ty = try sema.analyzeAsType(block, src, builtin_inst); | 15265 | 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( |
| 15157 | block, | 15267 | block, |
| 15158 | src, | 15268 | src, |
| 15159 | builtin_ty.getNamespace().?, | 15269 | builtin_ty.getNamespace().?, |
| 15160 | name, | 15270 | name, |
| 15161 | ); | 15271 | ); |
| 15162 | return sema.analyzeLoad(block, src, opt_ty_inst.?, src); | 15272 | return sema.analyzeDeclVal(block, src, opt_ty_decl.?); |
| 15163 | } | 15273 | } |
| 15164 | 15274 | ||
| 15165 | fn getBuiltinType( | 15275 | fn getBuiltinType( |
src/value.zig+20-7| ... | @@ -1461,14 +1461,25 @@ pub const Value = extern union { | ... | @@ -1461,14 +1461,25 @@ pub const Value = extern union { |
| 1461 | return false; | 1461 | return false; |
| 1462 | } | 1462 | } |
| 1463 | 1463 | ||
| 1464 | if (ty.zigTypeTag() == .Type) { | 1464 | switch (ty.zigTypeTag()) { |
| 1465 | var buf_a: ToTypeBuffer = undefined; | 1465 | .Type => { |
| 1466 | var buf_b: ToTypeBuffer = undefined; | 1466 | var buf_a: ToTypeBuffer = undefined; |
| 1467 | const a_type = a.toType(&buf_a); | 1467 | var buf_b: ToTypeBuffer = undefined; |
| 1468 | const b_type = b.toType(&buf_b); | 1468 | const a_type = a.toType(&buf_a); |
| 1469 | return a_type.eql(b_type); | 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), | ||
| 1470 | } | 1482 | } |
| 1471 | return order(a, b).compare(.eq); | ||
| 1472 | } | 1483 | } |
| 1473 | 1484 | ||
| 1474 | pub fn hash(val: Value, ty: Type, hasher: *std.hash.Wyhash) void { | 1485 | pub fn hash(val: Value, ty: Type, hasher: *std.hash.Wyhash) void { |
| ... | @@ -3037,6 +3048,8 @@ pub const Value = extern union { | ... | @@ -3037,6 +3048,8 @@ pub const Value = extern union { |
| 3037 | pub const undef = initTag(.undef); | 3048 | pub const undef = initTag(.undef); |
| 3038 | pub const @"void" = initTag(.void_value); | 3049 | pub const @"void" = initTag(.void_value); |
| 3039 | pub const @"null" = initTag(.null_value); | 3050 | pub const @"null" = initTag(.null_value); |
| 3051 | pub const @"false" = initTag(.bool_false); | ||
| 3052 | pub const @"true" = initTag(.bool_true); | ||
| 3040 | }; | 3053 | }; |
| 3041 | 3054 | ||
| 3042 | var negative_one_payload: Value.Payload.I64 = .{ | 3055 | var negative_one_payload: Value.Payload.I64 = .{ |
test/behavior/enum.zig+41| ... | @@ -605,3 +605,44 @@ test "enum with specified tag values" { | ... | @@ -605,3 +605,44 @@ test "enum with specified tag values" { |
| 605 | try testEnumWithSpecifiedTagValues(MultipleChoice.C); | 605 | try testEnumWithSpecifiedTagValues(MultipleChoice.C); |
| 606 | comptime try testEnumWithSpecifiedTagValues(MultipleChoice.C); | 606 | comptime try testEnumWithSpecifiedTagValues(MultipleChoice.C); |
| 607 | } | 607 | } |
| 608 | |||
| 609 | test "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; | ... | @@ -2,50 +2,6 @@ const expect = @import("std").testing.expect; |
| 2 | const mem = @import("std").mem; | 2 | const mem = @import("std").mem; |
| 3 | const Tag = @import("std").meta.Tag; | 3 | const Tag = @import("std").meta.Tag; |
| 4 | 4 | ||
| 5 | test "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 | |||
| 49 | test "empty non-exhaustive enum" { | 5 | test "empty non-exhaustive enum" { |
| 50 | const S = struct { | 6 | const S = struct { |
| 51 | const E = enum(u8) { | 7 | const E = enum(u8) { |