Merge pull request #5495 from xackus/fix_5314

stage1: fix non-exhaustive enums with one field
This commit is contained in:
Veikka Tuominen 2020-08-18 23:55:44 +03:00 committed by GitHub
commit d139e44cfa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 152 additions and 11 deletions

View File

@ -7303,7 +7303,14 @@ void render_const_value(CodeGen *g, Buf *buf, ZigValue *const_val) {
case ZigTypeIdEnum:
{
TypeEnumField *field = find_enum_field_by_tag(type_entry, &const_val->data.x_enum_tag);
buf_appendf(buf, "%s.%s", buf_ptr(&type_entry->name), buf_ptr(field->name));
if(field != nullptr){
buf_appendf(buf, "%s.%s", buf_ptr(&type_entry->name), buf_ptr(field->name));
} else {
// untagged value in a non-exhaustive enum
buf_appendf(buf, "%s.(", buf_ptr(&type_entry->name));
bigint_append_buf(buf, &const_val->data.x_enum_tag, 10);
buf_appendf(buf, ")");
}
return;
}
case ZigTypeIdErrorUnion:

View File

@ -14096,7 +14096,8 @@ static IrInstGen *ir_analyze_enum_to_int(IrAnalyze *ira, IrInst *source_instr, I
// If there is only one possible tag, then we know at comptime what it is.
if (enum_type->data.enumeration.layout == ContainerLayoutAuto &&
enum_type->data.enumeration.src_field_count == 1)
enum_type->data.enumeration.src_field_count == 1 &&
!enum_type->data.enumeration.non_exhaustive)
{
IrInstGen *result = ir_const(ira, source_instr, tag_type);
init_const_bigint(result->value, tag_type,
@ -14136,7 +14137,8 @@ static IrInstGen *ir_analyze_union_to_tag(IrAnalyze *ira, IrInst* source_instr,
// If there is only 1 possible tag, then we know at comptime what it is.
if (wanted_type->data.enumeration.layout == ContainerLayoutAuto &&
wanted_type->data.enumeration.src_field_count == 1)
wanted_type->data.enumeration.src_field_count == 1 &&
!wanted_type->data.enumeration.non_exhaustive)
{
IrInstGen *result = ir_const(ira, source_instr, wanted_type);
result->value->special = ConstValSpecialStatic;
@ -14175,7 +14177,14 @@ static IrInstGen *ir_analyze_enum_to_union(IrAnalyze *ira, IrInst* source_instr,
if (!val)
return ira->codegen->invalid_inst_gen;
TypeUnionField *union_field = find_union_field_by_tag(wanted_type, &val->data.x_enum_tag);
assert(union_field != nullptr);
if (union_field == nullptr) {
Buf *int_buf = buf_alloc();
bigint_append_buf(int_buf, &target->value->data.x_enum_tag, 10);
ir_add_error(ira, &target->base,
buf_sprintf("no tag by value %s", buf_ptr(int_buf)));
return ira->codegen->invalid_inst_gen;
}
ZigType *field_type = resolve_union_field_type(ira->codegen, union_field);
if (field_type == nullptr)
return ira->codegen->invalid_inst_gen;
@ -14211,6 +14220,13 @@ static IrInstGen *ir_analyze_enum_to_union(IrAnalyze *ira, IrInst* source_instr,
return result;
}
if (target->value->type->data.enumeration.non_exhaustive) {
ir_add_error(ira, source_instr,
buf_sprintf("runtime cast to union '%s' from non-exhustive enum",
buf_ptr(&wanted_type->name)));
return ira->codegen->invalid_inst_gen;
}
// if the union has all fields 0 bits, we can do it
// and in fact it's a noop cast because the union value is just the enum value
if (wanted_type->data.unionation.gen_field_count == 0) {
@ -23816,7 +23832,7 @@ static IrInstGen *ir_analyze_instruction_switch_target(IrAnalyze *ira,
bigint_init_bigint(&result->value->data.x_enum_tag, &pointee_val->data.x_union.tag);
return result;
}
if (tag_type->data.enumeration.src_field_count == 1) {
if (tag_type->data.enumeration.src_field_count == 1 && !tag_type->data.enumeration.non_exhaustive) {
IrInstGen *result = ir_const(ira, &switch_target_instruction->base.base, tag_type);
TypeEnumField *only_field = &tag_type->data.enumeration.fields[0];
bigint_init_bigint(&result->value->data.x_enum_tag, &only_field->value);
@ -23831,7 +23847,7 @@ static IrInstGen *ir_analyze_instruction_switch_target(IrAnalyze *ira,
case ZigTypeIdEnum: {
if ((err = type_resolve(ira->codegen, target_type, ResolveStatusZeroBitsKnown)))
return ira->codegen->invalid_inst_gen;
if (target_type->data.enumeration.src_field_count == 1) {
if (target_type->data.enumeration.src_field_count == 1 && !target_type->data.enumeration.non_exhaustive) {
TypeEnumField *only_field = &target_type->data.enumeration.fields[0];
IrInstGen *result = ir_const(ira, &switch_target_instruction->base.base, target_type);
bigint_init_bigint(&result->value->data.x_enum_tag, &only_field->value);
@ -28838,6 +28854,10 @@ static IrInstGen *ir_analyze_instruction_check_switch_prongs(IrAnalyze *ira,
if (type_is_invalid(switch_type))
return ira->codegen->invalid_inst_gen;
ZigValue *original_value = ((IrInstSrcSwitchTarget *)(instruction->target_value))->target_value_ptr->child->value;
bool target_is_originally_union = original_value->type->id == ZigTypeIdPointer &&
original_value->type->data.pointer.child_type->id == ZigTypeIdUnion;
if (switch_type->id == ZigTypeIdEnum) {
HashMap<BigInt, AstNode *, bigint_hash, bigint_eql> field_prev_uses = {};
field_prev_uses.init(switch_type->data.enumeration.src_field_count);
@ -28893,9 +28913,12 @@ static IrInstGen *ir_analyze_instruction_check_switch_prongs(IrAnalyze *ira,
}
}
if (instruction->have_underscore_prong) {
if (!switch_type->data.enumeration.non_exhaustive){
if (!switch_type->data.enumeration.non_exhaustive) {
ir_add_error(ira, &instruction->base.base,
buf_sprintf("switch on non-exhaustive enum has `_` prong"));
buf_sprintf("switch on exhaustive enum has `_` prong"));
} else if (target_is_originally_union) {
ir_add_error(ira, &instruction->base.base,
buf_sprintf("`_` prong not allowed when switching on tagged union"));
}
for (uint32_t i = 0; i < switch_type->data.enumeration.src_field_count; i += 1) {
TypeEnumField *enum_field = &switch_type->data.enumeration.fields[i];
@ -28910,7 +28933,7 @@ static IrInstGen *ir_analyze_instruction_check_switch_prongs(IrAnalyze *ira,
}
}
} else if (instruction->else_prong == nullptr) {
if (switch_type->data.enumeration.non_exhaustive) {
if (switch_type->data.enumeration.non_exhaustive && !target_is_originally_union) {
ir_add_error(ira, &instruction->base.base,
buf_sprintf("switch on non-exhaustive enum must include `else` or `_` prong"));
}

View File

@ -51,6 +51,46 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
"tmp.zig:17:23: error: cannot adjust alignment of zero sized type 'fn(u32) anytype'",
});
cases.addTest("invalid non-exhaustive enum to union",
\\const E = enum(u8) {
\\ a,
\\ b,
\\ _,
\\};
\\const U = union(E) {
\\ a,
\\ b,
\\};
\\export fn foo() void {
\\ var e = @intToEnum(E, 15);
\\ var u: U = e;
\\}
\\export fn bar() void {
\\ const e = @intToEnum(E, 15);
\\ var u: U = e;
\\}
, &[_][]const u8{
"tmp.zig:12:16: error: runtime cast to union 'U' from non-exhustive enum",
"tmp.zig:16:16: error: no tag by value 15",
});
cases.addTest("switching with exhaustive enum has '_' prong ",
\\const E = enum{
\\ a,
\\ b,
\\};
\\pub export fn entry() void {
\\ var e: E = .b;
\\ switch (e) {
\\ .a => {},
\\ .b => {},
\\ _ => {},
\\ }
\\}
, &[_][]const u8{
"tmp.zig:7:5: error: switch on exhaustive enum has `_` prong",
});
cases.addTest("invalid pointer with @Type",
\\export fn entry() void {
\\ _ = @Type(.{ .Pointer = .{
@ -564,6 +604,10 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
\\ b,
\\ _,
\\};
\\const U = union(E) {
\\ a: i32,
\\ b: u32,
\\};
\\pub export fn entry() void {
\\ var e: E = .b;
\\ switch (e) { // error: switch not handling the tag `b`
@ -574,10 +618,17 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
\\ .a => {},
\\ .b => {},
\\ }
\\ var u = U{.a = 2};
\\ switch (u) { // error: `_` prong not allowed when switching on tagged union
\\ .a => {},
\\ .b => {},
\\ _ => {},
\\ }
\\}
, &[_][]const u8{
"tmp.zig:8:5: error: enumeration value 'E.b' not handled in switch",
"tmp.zig:12:5: error: switch on non-exhaustive enum must include `else` or `_` prong",
"tmp.zig:12:5: error: enumeration value 'E.b' not handled in switch",
"tmp.zig:16:5: error: switch on non-exhaustive enum must include `else` or `_` prong",
"tmp.zig:21:5: error: `_` prong not allowed when switching on tagged union",
});
cases.add("switch expression - unreachable else prong (bool)",

View File

@ -85,6 +85,43 @@ test "empty non-exhaustive enum" {
comptime S.doTheTest(42);
}
test "single field non-exhaustive enum" {
const S = struct {
const E = enum(u8) {
a,
_,
};
fn doTheTest(y: u8) void {
var e: E = .a;
expect(switch (e) {
.a => true,
_ => false,
});
e = @intToEnum(E, 12);
expect(switch (e) {
.a => false,
_ => true,
});
expect(switch (e) {
.a => false,
else => true,
});
e = .a;
expect(switch (e) {
.a => true,
else => false,
});
expect(@enumToInt(@intToEnum(E, y)) == y);
expect(@typeInfo(E).Enum.fields.len == 1);
expect(@typeInfo(E).Enum.is_exhaustive == false);
}
};
S.doTheTest(23);
comptime S.doTheTest(23);
}
test "enum type" {
const foo1 = Foo{ .One = 13 };
const foo2 = Foo{

View File

@ -690,3 +690,26 @@ test "method call on an empty union" {
S.doTheTest();
comptime S.doTheTest();
}
test "switching on non exhaustive union" {
const S = struct {
const E = enum(u8) {
a,
b,
_,
};
const U = union(E) {
a: i32,
b: u32,
};
fn doTheTest() void {
var a = U{ .a = 2 };
switch (a) {
.a => |val| expect(val == 2),
.b => unreachable,
}
}
};
S.doTheTest();
comptime S.doTheTest();
}