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(); +}