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:
Andrew Kelley 2021-12-26 21:07:16 -07:00
parent 405ff911da
commit 629a54c711
5 changed files with 178 additions and 57 deletions

View File

@ -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,

View File

@ -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(

View File

@ -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 = .{

View File

@ -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);
}

View File

@ -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) {