Commit 1e835e0fcc

Vexu <git@vexu.eu>
2020-07-27 17:04:08
disallow '_' prong when switching on non-exhaustive tagged union
A tagged union cannot legally be initiated to an invalid enumeration
1 parent 6518501
Changed files (3)
src
test
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<BigInt, AstNode *, bigint_hash, bigint_eql> 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 exhaustive enum has `_` prong"));
+            } else if (target_is_originally_union) {
                 ir_add_error(ira, &instruction->base.base,
-                    buf_sprintf("switch on non-exhaustive enum has `_` prong"));
+                    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"));
             }
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();
+}
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)",