From 65185016f15ca69363113f85537542b0bdebe33f Mon Sep 17 00:00:00 2001 From: xackus <14938807+xackus@users.noreply.github.com> Date: Sun, 31 May 2020 19:15:21 +0200 Subject: [PATCH 1/3] stage1: fix non-exhaustive enums with one field --- src/analyze.cpp | 9 ++++++++- src/ir.cpp | 11 +++++++---- test/stage1/behavior/enum.zig | 37 +++++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 5 deletions(-) diff --git a/src/analyze.cpp b/src/analyze.cpp index 6a4a2ec052..4920571438 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -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: diff --git a/src/ir.cpp b/src/ir.cpp index 40be4e147b..f56a0681df 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -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) // TODO are non-exhaustive union tag types supposed to be allowed? { IrInstGen *result = ir_const(ira, source_instr, wanted_type); result->value->special = ConstValSpecialStatic; @@ -23814,7 +23816,8 @@ 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) { + // TODO are non-exhaustive union tag types supposed to be allowed? + 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); @@ -23829,7 +23832,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); diff --git a/test/stage1/behavior/enum.zig b/test/stage1/behavior/enum.zig index f569264520..1d424d6e39 100644 --- a/test/stage1/behavior/enum.zig +++ b/test/stage1/behavior/enum.zig @@ -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{ From 1e835e0fccfd82deb31922ffdee821b0b418709f Mon Sep 17 00:00:00 2001 From: Vexu Date: Mon, 27 Jul 2020 18:04:08 +0300 Subject: [PATCH 2/3] disallow '_' prong when switching on non-exhaustive tagged union A tagged union cannot legally be initiated to an invalid enumeration --- src/ir.cpp | 16 +++++++++++----- test/compile_errors.zig | 32 ++++++++++++++++++++++++++++++-- test/stage1/behavior/union.zig | 23 +++++++++++++++++++++++ 3 files changed, 64 insertions(+), 7 deletions(-) diff --git a/src/ir.cpp b/src/ir.cpp index f56a0681df..347cae7e79 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -14138,7 +14138,7 @@ 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.non_exhaustive) // TODO are non-exhaustive union tag types supposed to be allowed? + !wanted_type->data.enumeration.non_exhaustive) { IrInstGen *result = ir_const(ira, source_instr, wanted_type); result->value->special = ConstValSpecialStatic; @@ -23816,7 +23816,6 @@ 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; } - // TODO are non-exhaustive union tag types supposed to be allowed? 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]; @@ -28839,6 +28838,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 field_prev_uses = {}; field_prev_uses.init(switch_type->data.enumeration.src_field_count); @@ -28894,9 +28897,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]; @@ -28911,7 +28917,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")); } diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 70a9c47998..6c123925da 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -51,6 +51,23 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { "tmp.zig:17:23: error: cannot adjust alignment of zero sized type 'fn(u32) anytype'", }); + 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 +581,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 +595,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)", diff --git a/test/stage1/behavior/union.zig b/test/stage1/behavior/union.zig index cf3412eb5b..e7c9ee406f 100644 --- a/test/stage1/behavior/union.zig +++ b/test/stage1/behavior/union.zig @@ -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(); +} From 2948f2d262926725b4b8cb5beeb4fdba00b48336 Mon Sep 17 00:00:00 2001 From: Vexu Date: Mon, 27 Jul 2020 18:42:13 +0300 Subject: [PATCH 3/3] fix cast from invalid non-exhaustive enum to union --- src/ir.cpp | 16 +++++++++++++++- test/compile_errors.zig | 23 +++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/ir.cpp b/src/ir.cpp index 347cae7e79..209dd6d7d0 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -14177,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; @@ -14213,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) { diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 6c123925da..0b9c754329 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -51,6 +51,29 @@ 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,