| author | |
| committer | |
| log | 62120e3d0e382274dec2962f376ceb1e645f1a30 |
| tree | 3487d96203fce9443b06d419a13b34d6d2c00712 |
| parent | 460211431f407c9f707e3ac3bbff61610a487926 |
6 files changed, 127 insertions(+), 120 deletions(-)
src/Sema.zig+53-52| ... | ... | @@ -8247,7 +8247,7 @@ fn zirSwitchCond( |
| 8247 | 8247 | ) CompileError!Air.Inst.Ref { |
| 8248 | 8248 | const inst_data = sema.code.instructions.items(.data)[inst].un_node; |
| 8249 | 8249 | const src = inst_data.src(); |
| 8250 | const operand_src = src; // TODO make this point at the switch operand | |
| 8250 | const operand_src: LazySrcLoc = .{ .node_offset_switch_operand = inst_data.src_node }; | |
| 8251 | 8251 | const operand_ptr = try sema.resolveInst(inst_data.operand); |
| 8252 | 8252 | const operand = if (is_ref) |
| 8253 | 8253 | try sema.analyzeLoad(block, src, operand_ptr, operand_src) |
| ... | ... | @@ -8345,12 +8345,19 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError |
| 8345 | 8345 | }, |
| 8346 | 8346 | }; |
| 8347 | 8347 | |
| 8348 | const union_originally = blk: { | |
| 8349 | const zir_data = sema.code.instructions.items(.data); | |
| 8350 | const cond_index = Zir.refToIndex(extra.data.operand).?; | |
| 8351 | const raw_operand = sema.resolveInst(zir_data[cond_index].un_node.operand) catch unreachable; | |
| 8352 | break :blk sema.typeOf(raw_operand).zigTypeTag() == .Union; | |
| 8353 | }; | |
| 8354 | ||
| 8348 | 8355 | const operand_ty = sema.typeOf(operand); |
| 8349 | 8356 | |
| 8350 | 8357 | var else_error_ty: ?Type = null; |
| 8351 | 8358 | |
| 8352 | 8359 | // Validate usage of '_' prongs. |
| 8353 | if (special_prong == .under and !operand_ty.isNonexhaustiveEnum()) { | |
| 8360 | if (special_prong == .under and (!operand_ty.isNonexhaustiveEnum() or union_originally)) { | |
| 8354 | 8361 | const msg = msg: { |
| 8355 | 8362 | const msg = try sema.errMsg( |
| 8356 | 8363 | block, |
| ... | ... | @@ -8375,6 +8382,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError |
| 8375 | 8382 | |
| 8376 | 8383 | // Validate for duplicate items, missing else prong, and invalid range. |
| 8377 | 8384 | switch (operand_ty.zigTypeTag()) { |
| 8385 | .Union => unreachable, // handled in zirSwitchCond | |
| 8378 | 8386 | .Enum => { |
| 8379 | 8387 | var seen_fields = try gpa.alloc(?Module.SwitchProngSrc, operand_ty.enumFieldCount()); |
| 8380 | 8388 | defer gpa.free(seen_fields); |
| ... | ... | @@ -8432,60 +8440,54 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError |
| 8432 | 8440 | } |
| 8433 | 8441 | const all_tags_handled = for (seen_fields) |seen_src| { |
| 8434 | 8442 | if (seen_src == null) break false; |
| 8435 | } else !operand_ty.isNonexhaustiveEnum(); | |
| 8436 | ||
| 8437 | switch (special_prong) { | |
| 8438 | .none => { | |
| 8439 | if (!all_tags_handled) { | |
| 8440 | const msg = msg: { | |
| 8441 | const msg = try sema.errMsg( | |
| 8442 | block, | |
| 8443 | src, | |
| 8444 | "switch must handle all possibilities", | |
| 8445 | .{}, | |
| 8446 | ); | |
| 8447 | errdefer msg.destroy(sema.gpa); | |
| 8448 | for (seen_fields) |seen_src, i| { | |
| 8449 | if (seen_src != null) continue; | |
| 8443 | } else true; | |
| 8450 | 8444 | |
| 8451 | const field_name = operand_ty.enumFieldName(i); | |
| 8452 | ||
| 8453 | // TODO have this point to the tag decl instead of here | |
| 8454 | try sema.errNote( | |
| 8455 | block, | |
| 8456 | src, | |
| 8457 | msg, | |
| 8458 | "unhandled enumeration value: '{s}'", | |
| 8459 | .{field_name}, | |
| 8460 | ); | |
| 8461 | } | |
| 8462 | try sema.mod.errNoteNonLazy( | |
| 8463 | operand_ty.declSrcLoc(sema.mod), | |
| 8464 | msg, | |
| 8465 | "enum '{}' declared here", | |
| 8466 | .{operand_ty.fmt(sema.mod)}, | |
| 8467 | ); | |
| 8468 | break :msg msg; | |
| 8469 | }; | |
| 8470 | return sema.failWithOwnedErrorMsg(block, msg); | |
| 8471 | } | |
| 8472 | }, | |
| 8473 | .under => { | |
| 8474 | if (all_tags_handled) return sema.fail( | |
| 8445 | if (special_prong == .@"else") { | |
| 8446 | if (all_tags_handled and !operand_ty.isNonexhaustiveEnum()) return sema.fail( | |
| 8447 | block, | |
| 8448 | special_prong_src, | |
| 8449 | "unreachable else prong; all cases already handled", | |
| 8450 | .{}, | |
| 8451 | ); | |
| 8452 | } else if (!all_tags_handled) { | |
| 8453 | const msg = msg: { | |
| 8454 | const msg = try sema.errMsg( | |
| 8475 | 8455 | block, |
| 8476 | special_prong_src, | |
| 8477 | "unreachable '_' prong; all cases already handled", | |
| 8456 | src, | |
| 8457 | "switch must handle all possibilities", | |
| 8478 | 8458 | .{}, |
| 8479 | 8459 | ); |
| 8480 | }, | |
| 8481 | .@"else" => { | |
| 8482 | if (all_tags_handled) return sema.fail( | |
| 8483 | block, | |
| 8484 | special_prong_src, | |
| 8485 | "unreachable else prong; all cases already handled", | |
| 8486 | .{}, | |
| 8460 | errdefer msg.destroy(sema.gpa); | |
| 8461 | for (seen_fields) |seen_src, i| { | |
| 8462 | if (seen_src != null) continue; | |
| 8463 | ||
| 8464 | const field_name = operand_ty.enumFieldName(i); | |
| 8465 | ||
| 8466 | const field_src = src; // TODO better source location | |
| 8467 | try sema.errNote( | |
| 8468 | block, | |
| 8469 | field_src, | |
| 8470 | msg, | |
| 8471 | "unhandled enumeration value: '{s}'", | |
| 8472 | .{field_name}, | |
| 8473 | ); | |
| 8474 | } | |
| 8475 | try sema.mod.errNoteNonLazy( | |
| 8476 | operand_ty.declSrcLoc(sema.mod), | |
| 8477 | msg, | |
| 8478 | "enum '{}' declared here", | |
| 8479 | .{operand_ty.fmt(sema.mod)}, | |
| 8487 | 8480 | ); |
| 8488 | }, | |
| 8481 | break :msg msg; | |
| 8482 | }; | |
| 8483 | return sema.failWithOwnedErrorMsg(block, msg); | |
| 8484 | } else if (special_prong == .none and operand_ty.isNonexhaustiveEnum() and !union_originally) { | |
| 8485 | return sema.fail( | |
| 8486 | block, | |
| 8487 | src, | |
| 8488 | "switch on non-exhaustive enum must include 'else' or '_' prong", | |
| 8489 | .{}, | |
| 8490 | ); | |
| 8489 | 8491 | } |
| 8490 | 8492 | }, |
| 8491 | 8493 | .ErrorSet => { |
| ... | ... | @@ -8625,7 +8627,6 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError |
| 8625 | 8627 | else_error_ty = try Type.Tag.error_set_merged.create(sema.arena, names); |
| 8626 | 8628 | } |
| 8627 | 8629 | }, |
| 8628 | .Union => return sema.fail(block, src, "TODO validate switch .Union", .{}), | |
| 8629 | 8630 | .Int, .ComptimeInt => { |
| 8630 | 8631 | var range_set = RangeSet.init(gpa, sema.mod); |
| 8631 | 8632 | defer range_set.deinit(); |
test/behavior/union.zig-1| ... | ... | @@ -1016,7 +1016,6 @@ test "switching on non exhaustive union" { |
| 1016 | 1016 | switch (a) { |
| 1017 | 1017 | .a => |val| try expect(val == 2), |
| 1018 | 1018 | .b => return error.Fail, |
| 1019 | _ => return error.Fail, | |
| 1020 | 1019 | } |
| 1021 | 1020 | } |
| 1022 | 1021 | }; |
test/cases/compile_errors/helpful_return_type_error_message.zig created+32| ... | ... | @@ -0,0 +1,32 @@ |
| 1 | export fn foo() u32 { | |
| 2 | return error.Ohno; | |
| 3 | } | |
| 4 | fn bar() !u32 { | |
| 5 | return error.Ohno; | |
| 6 | } | |
| 7 | export fn baz() void { | |
| 8 | try bar(); | |
| 9 | } | |
| 10 | export fn qux() u32 { | |
| 11 | return bar(); | |
| 12 | } | |
| 13 | export fn quux() u32 { | |
| 14 | var buf: u32 = 0; | |
| 15 | buf = bar(); | |
| 16 | } | |
| 17 | ||
| 18 | // error | |
| 19 | // backend=stage2 | |
| 20 | // target=native | |
| 21 | // | |
| 22 | // :2:18: error: expected type 'u32', found 'error{Ohno}' | |
| 23 | // :1:17: note: function cannot return an error | |
| 24 | // :8:5: error: expected type 'void', found '@typeInfo(@typeInfo(@TypeOf(tmp.bar)).Fn.return_type.?).ErrorUnion.error_set' | |
| 25 | // :7:17: note: function cannot return an error | |
| 26 | // :11:15: error: expected type 'u32', found '@typeInfo(@typeInfo(@TypeOf(tmp.bar)).Fn.return_type.?).ErrorUnion.error_set!u32' | |
| 27 | // :10:17: note: function cannot return an error | |
| 28 | // :11:15: note: cannot convert error union to payload type | |
| 29 | // :11:15: note: consider using `try`, `catch`, or `if` | |
| 30 | // :15:14: error: expected type 'u32', found '@typeInfo(@typeInfo(@TypeOf(tmp.bar)).Fn.return_type.?).ErrorUnion.error_set!u32' | |
| 31 | // :15:14: note: cannot convert error union to payload type | |
| 32 | // :15:14: note: consider using `try`, `catch`, or `if` |
test/cases/compile_errors/stage1/test/helpful_return_type_error_message.zig deleted-32| ... | ... | @@ -1,32 +0,0 @@ |
| 1 | export fn foo() u32 { | |
| 2 | return error.Ohno; | |
| 3 | } | |
| 4 | fn bar() !u32 { | |
| 5 | return error.Ohno; | |
| 6 | } | |
| 7 | export fn baz() void { | |
| 8 | try bar(); | |
| 9 | } | |
| 10 | export fn qux() u32 { | |
| 11 | return bar(); | |
| 12 | } | |
| 13 | export fn quux() u32 { | |
| 14 | var buf: u32 = 0; | |
| 15 | buf = bar(); | |
| 16 | } | |
| 17 | ||
| 18 | // error | |
| 19 | // backend=stage2 | |
| 20 | // target=native | |
| 21 | // | |
| 22 | // :2:18: error: expected type 'u32', found 'error{Ohno}' | |
| 23 | // :1:17: note: function cannot return an error | |
| 24 | // :8:5: error: expected type 'void', found '@typeInfo(@typeInfo(@TypeOf(tmp.bar)).Fn.return_type.?).ErrorUnion.error_set' | |
| 25 | // :7:17: note: function cannot return an error | |
| 26 | // :11:15: error: expected type 'u32', found '@typeInfo(@typeInfo(@TypeOf(tmp.bar)).Fn.return_type.?).ErrorUnion.error_set!u32' | |
| 27 | // :10:17: note: function cannot return an error | |
| 28 | // :11:15: note: cannot convert error union to payload type | |
| 29 | // :11:15: note: consider using `try`, `catch`, or `if` | |
| 30 | // :15:14: error: expected type 'u32', found '@typeInfo(@typeInfo(@TypeOf(tmp.bar)).Fn.return_type.?).ErrorUnion.error_set!u32' | |
| 31 | // :15:14: note: cannot convert error union to payload type | |
| 32 | // :15:14: note: consider using `try`, `catch`, or `if` |
test/cases/compile_errors/stage1/test/switching_with_non-exhaustive_enums.zig deleted-35| ... | ... | @@ -1,35 +0,0 @@ |
| 1 | const E = enum(u8) { | |
| 2 | a, | |
| 3 | b, | |
| 4 | _, | |
| 5 | }; | |
| 6 | const U = union(E) { | |
| 7 | a: i32, | |
| 8 | b: u32, | |
| 9 | }; | |
| 10 | pub export fn entry() void { | |
| 11 | var e: E = .b; | |
| 12 | switch (e) { // error: switch not handling the tag `b` | |
| 13 | .a => {}, | |
| 14 | _ => {}, | |
| 15 | } | |
| 16 | switch (e) { // error: switch on non-exhaustive enum must include `else` or `_` prong | |
| 17 | .a => {}, | |
| 18 | .b => {}, | |
| 19 | } | |
| 20 | var u = U{.a = 2}; | |
| 21 | switch (u) { // error: `_` prong not allowed when switching on tagged union | |
| 22 | .a => {}, | |
| 23 | .b => {}, | |
| 24 | _ => {}, | |
| 25 | } | |
| 26 | } | |
| 27 | ||
| 28 | // error | |
| 29 | // backend=stage1 | |
| 30 | // target=native | |
| 31 | // is_test=1 | |
| 32 | // | |
| 33 | // tmp.zig:12:5: error: enumeration value 'E.b' not handled in switch | |
| 34 | // tmp.zig:16:5: error: switch on non-exhaustive enum must include `else` or `_` prong | |
| 35 | // tmp.zig:21:5: error: `_` prong not allowed when switching on tagged union |
test/cases/compile_errors/switching_with_non-exhaustive_enums.zig created+42| ... | ... | @@ -0,0 +1,42 @@ |
| 1 | const E = enum(u8) { | |
| 2 | a, | |
| 3 | b, | |
| 4 | _, | |
| 5 | }; | |
| 6 | const U = union(E) { | |
| 7 | a: i32, | |
| 8 | b: u32, | |
| 9 | }; | |
| 10 | pub export fn entry1() void { | |
| 11 | var e: E = .b; | |
| 12 | switch (e) { // error: switch not handling the tag `b` | |
| 13 | .a => {}, | |
| 14 | _ => {}, | |
| 15 | } | |
| 16 | } | |
| 17 | pub export fn entry2() void { | |
| 18 | var e: E = .b; | |
| 19 | switch (e) { // error: switch on non-exhaustive enum must include `else` or `_` prong | |
| 20 | .a => {}, | |
| 21 | .b => {}, | |
| 22 | } | |
| 23 | } | |
| 24 | pub export fn entry3() void { | |
| 25 | var u = U{.a = 2}; | |
| 26 | switch (u) { // error: `_` prong not allowed when switching on tagged union | |
| 27 | .a => {}, | |
| 28 | .b => {}, | |
| 29 | _ => {}, | |
| 30 | } | |
| 31 | } | |
| 32 | ||
| 33 | // error | |
| 34 | // backend=stage2 | |
| 35 | // target=native | |
| 36 | // | |
| 37 | // :12:5: error: switch must handle all possibilities | |
| 38 | // :12:5: note: unhandled enumeration value: 'b' | |
| 39 | // :1:11: note: enum 'tmp.E' declared here | |
| 40 | // :19:5: error: switch on non-exhaustive enum must include 'else' or '_' prong | |
| 41 | // :26:5: error: '_' prong only allowed when switching on non-exhaustive enums | |
| 42 | // :29:11: note: '_' prong here |