diff --git a/src/Sema.zig b/src/Sema.zig index 702991bf5e69009eb2f9a81b789f124b1014df28..2ac27cf6b3d74a91cb93786241043d7bc21024e7 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -8247,7 +8247,7 @@ fn zirSwitchCond( ) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].un_node; const src = inst_data.src(); - const operand_src = src; // TODO make this point at the switch operand + const operand_src: LazySrcLoc = .{ .node_offset_switch_operand = inst_data.src_node }; const operand_ptr = try sema.resolveInst(inst_data.operand); const operand = if (is_ref) try sema.analyzeLoad(block, src, operand_ptr, operand_src) @@ -8345,12 +8345,19 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError }, }; + const union_originally = blk: { + const zir_data = sema.code.instructions.items(.data); + const cond_index = Zir.refToIndex(extra.data.operand).?; + const raw_operand = sema.resolveInst(zir_data[cond_index].un_node.operand) catch unreachable; + break :blk sema.typeOf(raw_operand).zigTypeTag() == .Union; + }; + const operand_ty = sema.typeOf(operand); var else_error_ty: ?Type = null; // Validate usage of '_' prongs. - if (special_prong == .under and !operand_ty.isNonexhaustiveEnum()) { + if (special_prong == .under and (!operand_ty.isNonexhaustiveEnum() or union_originally)) { const msg = msg: { const msg = try sema.errMsg( block, @@ -8375,6 +8382,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError // Validate for duplicate items, missing else prong, and invalid range. switch (operand_ty.zigTypeTag()) { + .Union => unreachable, // handled in zirSwitchCond .Enum => { var seen_fields = try gpa.alloc(?Module.SwitchProngSrc, operand_ty.enumFieldCount()); defer gpa.free(seen_fields); @@ -8432,60 +8440,54 @@ 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 !operand_ty.isNonexhaustiveEnum(); + } else true; - switch (special_prong) { - .none => { - if (!all_tags_handled) { - const msg = msg: { - const msg = try sema.errMsg( - block, - src, - "switch must handle all possibilities", - .{}, - ); - errdefer msg.destroy(sema.gpa); - for (seen_fields) |seen_src, i| { - if (seen_src != null) continue; + if (special_prong == .@"else") { + if (all_tags_handled and !operand_ty.isNonexhaustiveEnum()) return sema.fail( + block, + special_prong_src, + "unreachable else prong; all cases already handled", + .{}, + ); + } else if (!all_tags_handled) { + const msg = msg: { + const msg = try sema.errMsg( + block, + src, + "switch must handle all possibilities", + .{}, + ); + errdefer msg.destroy(sema.gpa); + for (seen_fields) |seen_src, i| { + if (seen_src != null) continue; - const field_name = operand_ty.enumFieldName(i); + const field_name = operand_ty.enumFieldName(i); - // TODO have this point to the tag decl instead of here - try sema.errNote( - block, - src, - msg, - "unhandled enumeration value: '{s}'", - .{field_name}, - ); - } - try sema.mod.errNoteNonLazy( - operand_ty.declSrcLoc(sema.mod), - msg, - "enum '{}' declared here", - .{operand_ty.fmt(sema.mod)}, - ); - break :msg msg; - }; - return sema.failWithOwnedErrorMsg(block, msg); + const field_src = src; // TODO better source location + try sema.errNote( + block, + field_src, + msg, + "unhandled enumeration value: '{s}'", + .{field_name}, + ); } - }, - .under => { - if (all_tags_handled) return sema.fail( - block, - special_prong_src, - "unreachable '_' prong; all cases already handled", - .{}, + try sema.mod.errNoteNonLazy( + operand_ty.declSrcLoc(sema.mod), + msg, + "enum '{}' declared here", + .{operand_ty.fmt(sema.mod)}, ); - }, - .@"else" => { - if (all_tags_handled) return sema.fail( - block, - special_prong_src, - "unreachable else prong; all cases already handled", - .{}, - ); - }, + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(block, msg); + } else if (special_prong == .none and operand_ty.isNonexhaustiveEnum() and !union_originally) { + return sema.fail( + block, + src, + "switch on non-exhaustive enum must include 'else' or '_' prong", + .{}, + ); } }, .ErrorSet => { @@ -8625,7 +8627,6 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError else_error_ty = try Type.Tag.error_set_merged.create(sema.arena, names); } }, - .Union => return sema.fail(block, src, "TODO validate switch .Union", .{}), .Int, .ComptimeInt => { var range_set = RangeSet.init(gpa, sema.mod); defer range_set.deinit(); diff --git a/test/behavior/union.zig b/test/behavior/union.zig index caa78be977925085cfe040b726ca6efdcf7afe8c..1787a9f30ea42c79dc4c24f717f2fe06b713b952 100644 --- a/test/behavior/union.zig +++ b/test/behavior/union.zig @@ -1016,7 +1016,6 @@ test "switching on non exhaustive union" { switch (a) { .a => |val| try expect(val == 2), .b => return error.Fail, - _ => return error.Fail, } } }; diff --git a/test/cases/compile_errors/helpful_return_type_error_message.zig b/test/cases/compile_errors/helpful_return_type_error_message.zig new file mode 100644 index 0000000000000000000000000000000000000000..b8e48036de78a8e026b5ce97b59e3ac926ae1dc6 --- /dev/null +++ b/test/cases/compile_errors/helpful_return_type_error_message.zig @@ -0,0 +1,32 @@ +export fn foo() u32 { + return error.Ohno; +} +fn bar() !u32 { + return error.Ohno; +} +export fn baz() void { + try bar(); +} +export fn qux() u32 { + return bar(); +} +export fn quux() u32 { + var buf: u32 = 0; + buf = bar(); +} + +// error +// backend=stage2 +// target=native +// +// :2:18: error: expected type 'u32', found 'error{Ohno}' +// :1:17: note: function cannot return an error +// :8:5: error: expected type 'void', found '@typeInfo(@typeInfo(@TypeOf(tmp.bar)).Fn.return_type.?).ErrorUnion.error_set' +// :7:17: note: function cannot return an error +// :11:15: error: expected type 'u32', found '@typeInfo(@typeInfo(@TypeOf(tmp.bar)).Fn.return_type.?).ErrorUnion.error_set!u32' +// :10:17: note: function cannot return an error +// :11:15: note: cannot convert error union to payload type +// :11:15: note: consider using `try`, `catch`, or `if` +// :15:14: error: expected type 'u32', found '@typeInfo(@typeInfo(@TypeOf(tmp.bar)).Fn.return_type.?).ErrorUnion.error_set!u32' +// :15:14: note: cannot convert error union to payload type +// :15:14: note: consider using `try`, `catch`, or `if` diff --git a/test/cases/compile_errors/stage1/test/helpful_return_type_error_message.zig b/test/cases/compile_errors/stage1/test/helpful_return_type_error_message.zig deleted file mode 100644 index b8e48036de78a8e026b5ce97b59e3ac926ae1dc6..0000000000000000000000000000000000000000 --- a/test/cases/compile_errors/stage1/test/helpful_return_type_error_message.zig +++ /dev/null @@ -1,32 +0,0 @@ -export fn foo() u32 { - return error.Ohno; -} -fn bar() !u32 { - return error.Ohno; -} -export fn baz() void { - try bar(); -} -export fn qux() u32 { - return bar(); -} -export fn quux() u32 { - var buf: u32 = 0; - buf = bar(); -} - -// error -// backend=stage2 -// target=native -// -// :2:18: error: expected type 'u32', found 'error{Ohno}' -// :1:17: note: function cannot return an error -// :8:5: error: expected type 'void', found '@typeInfo(@typeInfo(@TypeOf(tmp.bar)).Fn.return_type.?).ErrorUnion.error_set' -// :7:17: note: function cannot return an error -// :11:15: error: expected type 'u32', found '@typeInfo(@typeInfo(@TypeOf(tmp.bar)).Fn.return_type.?).ErrorUnion.error_set!u32' -// :10:17: note: function cannot return an error -// :11:15: note: cannot convert error union to payload type -// :11:15: note: consider using `try`, `catch`, or `if` -// :15:14: error: expected type 'u32', found '@typeInfo(@typeInfo(@TypeOf(tmp.bar)).Fn.return_type.?).ErrorUnion.error_set!u32' -// :15:14: note: cannot convert error union to payload type -// :15:14: note: consider using `try`, `catch`, or `if` diff --git a/test/cases/compile_errors/stage1/test/switching_with_non-exhaustive_enums.zig b/test/cases/compile_errors/stage1/test/switching_with_non-exhaustive_enums.zig deleted file mode 100644 index ed46c5f2f14337e7d1e87ed2ca90861bf20bae00..0000000000000000000000000000000000000000 --- a/test/cases/compile_errors/stage1/test/switching_with_non-exhaustive_enums.zig +++ /dev/null @@ -1,35 +0,0 @@ -const E = enum(u8) { - a, - 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` - .a => {}, - _ => {}, - } - switch (e) { // error: switch on non-exhaustive enum must include `else` or `_` prong - .a => {}, - .b => {}, - } - var u = U{.a = 2}; - switch (u) { // error: `_` prong not allowed when switching on tagged union - .a => {}, - .b => {}, - _ => {}, - } -} - -// error -// backend=stage1 -// target=native -// is_test=1 -// -// 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 diff --git a/test/cases/compile_errors/switching_with_non-exhaustive_enums.zig b/test/cases/compile_errors/switching_with_non-exhaustive_enums.zig new file mode 100644 index 0000000000000000000000000000000000000000..e33d686d8634922a9f5e1f306828f5ceadda9cd2 --- /dev/null +++ b/test/cases/compile_errors/switching_with_non-exhaustive_enums.zig @@ -0,0 +1,42 @@ +const E = enum(u8) { + a, + b, + _, +}; +const U = union(E) { + a: i32, + b: u32, +}; +pub export fn entry1() void { + var e: E = .b; + switch (e) { // error: switch not handling the tag `b` + .a => {}, + _ => {}, + } +} +pub export fn entry2() void { + var e: E = .b; + switch (e) { // error: switch on non-exhaustive enum must include `else` or `_` prong + .a => {}, + .b => {}, + } +} +pub export fn entry3() void { + var u = U{.a = 2}; + switch (u) { // error: `_` prong not allowed when switching on tagged union + .a => {}, + .b => {}, + _ => {}, + } +} + +// error +// backend=stage2 +// target=native +// +// :12:5: error: switch must handle all possibilities +// :12:5: note: unhandled enumeration value: 'b' +// :1:11: note: enum 'tmp.E' declared here +// :19:5: error: switch on non-exhaustive enum must include 'else' or '_' prong +// :26:5: error: '_' prong only allowed when switching on non-exhaustive enums +// :29:11: note: '_' prong here