Sema: fix non-exhaustive union switch checks
This commit is contained in:
parent
460211431f
commit
62120e3d0e
105
src/Sema.zig
105
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();
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -7,16 +7,21 @@ const U = union(E) {
|
|||
a: i32,
|
||||
b: u32,
|
||||
};
|
||||
pub export fn entry() void {
|
||||
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 => {},
|
||||
|
@ -26,10 +31,12 @@ pub export fn entry() void {
|
|||
}
|
||||
|
||||
// error
|
||||
// backend=stage1
|
||||
// backend=stage2
|
||||
// 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
|
||||
// :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
|
Loading…
Reference in New Issue
Block a user