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(...@@ -8247,7 +8247,7 @@ fn zirSwitchCond(
8247) CompileError!Air.Inst.Ref {8247) CompileError!Air.Inst.Ref {
8248 const inst_data = sema.code.instructions.items(.data)[inst].un_node;8248 const inst_data = sema.code.instructions.items(.data)[inst].un_node;
8249 const src = inst_data.src();8249 const src = inst_data.src();
8250 const operand_src = src; // TODO make this point at the switch operand8250 const operand_src: LazySrcLoc = .{ .node_offset_switch_operand = inst_data.src_node };
8251 const operand_ptr = try sema.resolveInst(inst_data.operand);8251 const operand_ptr = try sema.resolveInst(inst_data.operand);
8252 const operand = if (is_ref)8252 const operand = if (is_ref)
8253 try sema.analyzeLoad(block, src, operand_ptr, operand_src)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,12 +8345,19 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
8345 },8345 },
8346 };8346 };
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
8348 const operand_ty = sema.typeOf(operand);8355 const operand_ty = sema.typeOf(operand);
83498356
8350 var else_error_ty: ?Type = null;8357 var else_error_ty: ?Type = null;
83518358
8352 // Validate usage of '_' prongs.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 const msg = msg: {8361 const msg = msg: {
8355 const msg = try sema.errMsg(8362 const msg = try sema.errMsg(
8356 block,8363 block,
...@@ -8375,6 +8382,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError...@@ -8375,6 +8382,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
83758382
8376 // Validate for duplicate items, missing else prong, and invalid range.8383 // Validate for duplicate items, missing else prong, and invalid range.
8377 switch (operand_ty.zigTypeTag()) {8384 switch (operand_ty.zigTypeTag()) {
8385 .Union => unreachable, // handled in zirSwitchCond
8378 .Enum => {8386 .Enum => {
8379 var seen_fields = try gpa.alloc(?Module.SwitchProngSrc, operand_ty.enumFieldCount());8387 var seen_fields = try gpa.alloc(?Module.SwitchProngSrc, operand_ty.enumFieldCount());
8380 defer gpa.free(seen_fields);8388 defer gpa.free(seen_fields);
...@@ -8432,60 +8440,54 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError...@@ -8432,60 +8440,54 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
8432 }8440 }
8433 const all_tags_handled = for (seen_fields) |seen_src| {8441 const all_tags_handled = for (seen_fields) |seen_src| {
8434 if (seen_src == null) break false;8442 if (seen_src == null) break false;
8435 } else !operand_ty.isNonexhaustiveEnum();8443 } else true;
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;
84508444
8451 const field_name = operand_ty.enumFieldName(i);8445 if (special_prong == .@"else") {
84528446 if (all_tags_handled and !operand_ty.isNonexhaustiveEnum()) return sema.fail(
8453 // TODO have this point to the tag decl instead of here8447 block,
8454 try sema.errNote(8448 special_prong_src,
8455 block,8449 "unreachable else prong; all cases already handled",
8456 src,8450 .{},
8457 msg,8451 );
8458 "unhandled enumeration value: '{s}'",8452 } else if (!all_tags_handled) {
8459 .{field_name},8453 const msg = msg: {
8460 );8454 const msg = try sema.errMsg(
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(
8475 block,8455 block,
8476 special_prong_src,8456 src,
8477 "unreachable '_' prong; all cases already handled",8457 "switch must handle all possibilities",
8478 .{},8458 .{},
8479 );8459 );
8480 },8460 errdefer msg.destroy(sema.gpa);
8481 .@"else" => {8461 for (seen_fields) |seen_src, i| {
8482 if (all_tags_handled) return sema.fail(8462 if (seen_src != null) continue;
8483 block,8463
8484 special_prong_src,8464 const field_name = operand_ty.enumFieldName(i);
8485 "unreachable else prong; all cases already handled",8465
8486 .{},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 .ErrorSet => {8493 .ErrorSet => {
...@@ -8625,7 +8627,6 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError...@@ -8625,7 +8627,6 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
8625 else_error_ty = try Type.Tag.error_set_merged.create(sema.arena, names);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 .Int, .ComptimeInt => {8630 .Int, .ComptimeInt => {
8630 var range_set = RangeSet.init(gpa, sema.mod);8631 var range_set = RangeSet.init(gpa, sema.mod);
8631 defer range_set.deinit();8632 defer range_set.deinit();
test/behavior/union.zig-1
...@@ -1016,7 +1016,6 @@ test "switching on non exhaustive union" {...@@ -1016,7 +1016,6 @@ test "switching on non exhaustive union" {
1016 switch (a) {1016 switch (a) {
1017 .a => |val| try expect(val == 2),1017 .a => |val| try expect(val == 2),
1018 .b => return error.Fail,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 @@
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