authorgravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2022-07-12 13:52:39+03:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-07-21 12:21:30-07:00
log62120e3d0e382274dec2962f376ceb1e645f1a30
tree3487d96203fce9443b06d419a13b34d6d2c00712
parent460211431f407c9f707e3ac3bbff61610a487926

Sema: fix non-exhaustive union switch checks


6 files changed, 127 insertions(+), 120 deletions(-)

src/Sema.zig+53-52
......@@ -8247,7 +8247,7 @@ fn zirSwitchCond(
82478247) CompileError!Air.Inst.Ref {
82488248 const inst_data = sema.code.instructions.items(.data)[inst].un_node;
82498249 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 };
82518251 const operand_ptr = try sema.resolveInst(inst_data.operand);
82528252 const operand = if (is_ref)
82538253 try sema.analyzeLoad(block, src, operand_ptr, operand_src)
......@@ -8345,12 +8345,19 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
83458345 },
83468346 };
83478347
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
83488355 const operand_ty = sema.typeOf(operand);
83498356
83508357 var else_error_ty: ?Type = null;
83518358
83528359 // 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)) {
83548361 const msg = msg: {
83558362 const msg = try sema.errMsg(
83568363 block,
......@@ -8375,6 +8382,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
83758382
83768383 // Validate for duplicate items, missing else prong, and invalid range.
83778384 switch (operand_ty.zigTypeTag()) {
8385 .Union => unreachable, // handled in zirSwitchCond
83788386 .Enum => {
83798387 var seen_fields = try gpa.alloc(?Module.SwitchProngSrc, operand_ty.enumFieldCount());
83808388 defer gpa.free(seen_fields);
......@@ -8432,60 +8440,54 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
84328440 }
84338441 const all_tags_handled = for (seen_fields) |seen_src| {
84348442 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;
84508444
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(
84758455 block,
8476 special_prong_src,
8477 "unreachable '_' prong; all cases already handled",
8456 src,
8457 "switch must handle all possibilities",
84788458 .{},
84798459 );
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)},
84878480 );
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 );
84898491 }
84908492 },
84918493 .ErrorSet => {
......@@ -8625,7 +8627,6 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
86258627 else_error_ty = try Type.Tag.error_set_merged.create(sema.arena, names);
86268628 }
86278629 },
8628 .Union => return sema.fail(block, src, "TODO validate switch .Union", .{}),
86298630 .Int, .ComptimeInt => {
86308631 var range_set = RangeSet.init(gpa, sema.mod);
86318632 defer range_set.deinit();
test/behavior/union.zig-1
......@@ -1016,7 +1016,6 @@ test "switching on non exhaustive union" {
10161016 switch (a) {
10171017 .a => |val| try expect(val == 2),
10181018 .b => return error.Fail,
1019 _ => return error.Fail,
10201019 }
10211020 }
10221021 };
test/cases/compile_errors/helpful_return_type_error_message.zig created+32
......@@ -0,0 +1,32 @@
1export fn foo() u32 {
2 return error.Ohno;
3}
4fn bar() !u32 {
5 return error.Ohno;
6}
7export fn baz() void {
8 try bar();
9}
10export fn qux() u32 {
11 return bar();
12}
13export 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 @@
1export fn foo() u32 {
2 return error.Ohno;
3}
4fn bar() !u32 {
5 return error.Ohno;
6}
7export fn baz() void {
8 try bar();
9}
10export fn qux() u32 {
11 return bar();
12}
13export 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 @@
1const E = enum(u8) {
2 a,
3 b,
4 _,
5};
6const U = union(E) {
7 a: i32,
8 b: u32,
9};
10pub 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 @@
1const E = enum(u8) {
2 a,
3 b,
4 _,
5};
6const U = union(E) {
7 a: i32,
8 b: u32,
9};
10pub export fn entry1() void {
11 var e: E = .b;
12 switch (e) { // error: switch not handling the tag `b`
13 .a => {},
14 _ => {},
15 }
16}
17pub 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}
24pub 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