mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 06:13:07 +00:00
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.
This commit is contained in:
parent
405ff911da
commit
629a54c711
@ -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,
|
||||
|
||||
122
src/Sema.zig
122
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(
|
||||
|
||||
@ -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 = .{
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user