Merge pull request #18173 from dweiller/switch-err-union
Special-case switching on error union capture
This commit is contained in:
@@ -6801,6 +6801,32 @@ test "peer type resolution: *const T and ?*T" {
|
||||
try expect(a == b);
|
||||
try expect(b == a);
|
||||
}
|
||||
|
||||
test "peer type resolution: error union switch" {
|
||||
// The non-error and error cases are only peers if the error case is just a switch expression;
|
||||
// the pattern `if (x) {...} else |err| blk: { switch (err) {...} }` does not consider the
|
||||
// non-error and error case to be peers.
|
||||
var a: error{ A, B, C }!u32 = 0;
|
||||
_ = &a;
|
||||
const b = if (a) |x|
|
||||
x + 3
|
||||
else |err| switch (err) {
|
||||
error.A => 0,
|
||||
error.B => 1,
|
||||
error.C => null,
|
||||
};
|
||||
try expect(@TypeOf(b) == ?u32);
|
||||
|
||||
// The non-error and error cases are only peers if the error case is just a switch expression;
|
||||
// the pattern `x catch |err| blk: { switch (err) {...} }` does not consider the unwrapped `x`
|
||||
// and error case to be peers.
|
||||
const c = a catch |err| switch (err) {
|
||||
error.A => 0,
|
||||
error.B => 1,
|
||||
error.C => null,
|
||||
};
|
||||
try expect(@TypeOf(c) == ?u32);
|
||||
}
|
||||
{#code_end#}
|
||||
{#header_close#}
|
||||
{#header_close#}
|
||||
|
||||
562
src/AstGen.zig
562
src/AstGen.zig
@@ -93,6 +93,7 @@ fn setExtra(astgen: *AstGen, index: usize, extra: anytype) void {
|
||||
Zir.Inst.Call.Flags,
|
||||
Zir.Inst.BuiltinCall.Flags,
|
||||
Zir.Inst.SwitchBlock.Bits,
|
||||
Zir.Inst.SwitchBlockErrUnion.Bits,
|
||||
Zir.Inst.FuncFancy.Bits,
|
||||
=> @bitCast(@field(extra, field.name)),
|
||||
|
||||
@@ -838,7 +839,18 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE
|
||||
|
||||
.if_simple,
|
||||
.@"if",
|
||||
=> return ifExpr(gz, scope, ri.br(), node, tree.fullIf(node).?),
|
||||
=> {
|
||||
const if_full = tree.fullIf(node).?;
|
||||
if (if_full.error_token) |error_token| {
|
||||
const tag = node_tags[if_full.ast.else_expr];
|
||||
if ((tag == .@"switch" or tag == .switch_comma) and
|
||||
std.mem.eql(u8, tree.tokenSlice(error_token), tree.tokenSlice(error_token + 4)))
|
||||
{
|
||||
return switchExprErrUnion(gz, scope, ri.br(), node, .@"if");
|
||||
}
|
||||
}
|
||||
return ifExpr(gz, scope, ri.br(), node, if_full);
|
||||
},
|
||||
|
||||
.while_simple,
|
||||
.while_cont,
|
||||
@@ -1014,10 +1026,16 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE
|
||||
},
|
||||
.@"catch" => {
|
||||
const catch_token = main_tokens[node];
|
||||
const payload_token: ?Ast.TokenIndex = if (token_tags[catch_token + 1] == .pipe)
|
||||
catch_token + 2
|
||||
else
|
||||
null;
|
||||
const payload_token: ?Ast.TokenIndex = if (token_tags[catch_token + 1] == .pipe) blk: {
|
||||
if (token_tags.len > catch_token + 6 and
|
||||
token_tags[catch_token + 4] == .keyword_switch)
|
||||
{
|
||||
if (std.mem.eql(u8, tree.tokenSlice(catch_token + 2), tree.tokenSlice(catch_token + 6))) {
|
||||
return switchExprErrUnion(gz, scope, ri.br(), node, .@"catch");
|
||||
}
|
||||
}
|
||||
break :blk catch_token + 2;
|
||||
} else null;
|
||||
switch (ri.rl) {
|
||||
.ref, .ref_coerced_ty => return orelseCatchExpr(
|
||||
gz,
|
||||
@@ -2556,7 +2574,6 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As
|
||||
.vector_type,
|
||||
.indexable_ptr_len,
|
||||
.anyframe_type,
|
||||
.as,
|
||||
.as_node,
|
||||
.as_shift_operand,
|
||||
.bit_and,
|
||||
@@ -2641,6 +2658,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As
|
||||
.import,
|
||||
.switch_block,
|
||||
.switch_block_ref,
|
||||
.switch_block_err_union,
|
||||
.union_init,
|
||||
.field_type_ref,
|
||||
.error_set_decl,
|
||||
@@ -6858,6 +6876,538 @@ fn forExpr(
|
||||
return result;
|
||||
}
|
||||
|
||||
fn switchExprErrUnion(
|
||||
parent_gz: *GenZir,
|
||||
scope: *Scope,
|
||||
ri: ResultInfo,
|
||||
catch_or_if_node: Ast.Node.Index,
|
||||
node_ty: enum { @"catch", @"if" },
|
||||
) InnerError!Zir.Inst.Ref {
|
||||
const astgen = parent_gz.astgen;
|
||||
const gpa = astgen.gpa;
|
||||
const tree = astgen.tree;
|
||||
const node_datas = tree.nodes.items(.data);
|
||||
const node_tags = tree.nodes.items(.tag);
|
||||
const main_tokens = tree.nodes.items(.main_token);
|
||||
const token_tags = tree.tokens.items(.tag);
|
||||
|
||||
const if_full = switch (node_ty) {
|
||||
.@"catch" => undefined,
|
||||
.@"if" => tree.fullIf(catch_or_if_node).?,
|
||||
};
|
||||
|
||||
const switch_node, const operand_node, const error_payload = switch (node_ty) {
|
||||
.@"catch" => .{
|
||||
node_datas[catch_or_if_node].rhs,
|
||||
node_datas[catch_or_if_node].lhs,
|
||||
main_tokens[catch_or_if_node] + 2,
|
||||
},
|
||||
.@"if" => .{
|
||||
if_full.ast.else_expr,
|
||||
if_full.ast.cond_expr,
|
||||
if_full.error_token.?,
|
||||
},
|
||||
};
|
||||
assert(node_tags[switch_node] == .@"switch" or node_tags[switch_node] == .switch_comma);
|
||||
|
||||
const extra = tree.extraData(node_datas[switch_node].rhs, Ast.Node.SubRange);
|
||||
const case_nodes = tree.extra_data[extra.start..extra.end];
|
||||
|
||||
const need_rl = astgen.nodes_need_rl.contains(catch_or_if_node);
|
||||
const block_ri: ResultInfo = if (need_rl) ri else .{
|
||||
.rl = switch (ri.rl) {
|
||||
.ptr => .{ .ty = (try ri.rl.resultType(parent_gz, catch_or_if_node)).? },
|
||||
.inferred_ptr => .none,
|
||||
else => ri.rl,
|
||||
},
|
||||
.ctx = ri.ctx,
|
||||
};
|
||||
|
||||
const payload_is_ref = node_ty == .@"if" and
|
||||
if_full.payload_token != null and token_tags[if_full.payload_token.?] == .asterisk;
|
||||
|
||||
// We need to call `rvalue` to write through to the pointer only if we had a
|
||||
// result pointer and aren't forwarding it.
|
||||
const LocTag = @typeInfo(ResultInfo.Loc).Union.tag_type.?;
|
||||
const need_result_rvalue = @as(LocTag, block_ri.rl) != @as(LocTag, ri.rl);
|
||||
var scalar_cases_len: u32 = 0;
|
||||
var multi_cases_len: u32 = 0;
|
||||
var inline_cases_len: u32 = 0;
|
||||
var has_else = false;
|
||||
var else_node: Ast.Node.Index = 0;
|
||||
var else_src: ?Ast.TokenIndex = null;
|
||||
for (case_nodes) |case_node| {
|
||||
const case = tree.fullSwitchCase(case_node).?;
|
||||
|
||||
if (case.ast.values.len == 0) {
|
||||
const case_src = case.ast.arrow_token - 1;
|
||||
if (else_src) |src| {
|
||||
return astgen.failTokNotes(
|
||||
case_src,
|
||||
"multiple else prongs in switch expression",
|
||||
.{},
|
||||
&[_]u32{
|
||||
try astgen.errNoteTok(
|
||||
src,
|
||||
"previous else prong here",
|
||||
.{},
|
||||
),
|
||||
},
|
||||
);
|
||||
}
|
||||
has_else = true;
|
||||
else_node = case_node;
|
||||
else_src = case_src;
|
||||
continue;
|
||||
} else if (case.ast.values.len == 1 and
|
||||
node_tags[case.ast.values[0]] == .identifier and
|
||||
mem.eql(u8, tree.tokenSlice(main_tokens[case.ast.values[0]]), "_"))
|
||||
{
|
||||
const case_src = case.ast.arrow_token - 1;
|
||||
return astgen.failTokNotes(
|
||||
case_src,
|
||||
"'_' prong is not allowed when switching on errors",
|
||||
.{},
|
||||
&[_]u32{
|
||||
try astgen.errNoteTok(
|
||||
case_src,
|
||||
"consider using 'else'",
|
||||
.{},
|
||||
),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
for (case.ast.values) |val| {
|
||||
if (node_tags[val] == .string_literal)
|
||||
return astgen.failNode(val, "cannot switch on strings", .{});
|
||||
}
|
||||
|
||||
if (case.ast.values.len == 1 and node_tags[case.ast.values[0]] != .switch_range) {
|
||||
scalar_cases_len += 1;
|
||||
} else {
|
||||
multi_cases_len += 1;
|
||||
}
|
||||
if (case.inline_token != null) {
|
||||
inline_cases_len += 1;
|
||||
}
|
||||
}
|
||||
|
||||
const operand_ri: ResultInfo = .{
|
||||
.rl = if (payload_is_ref) .ref else .none,
|
||||
.ctx = .error_handling_expr,
|
||||
};
|
||||
|
||||
astgen.advanceSourceCursorToNode(operand_node);
|
||||
const operand_lc = LineColumn{ astgen.source_line - parent_gz.decl_line, astgen.source_column };
|
||||
|
||||
const raw_operand = try reachableExpr(parent_gz, scope, operand_ri, operand_node, switch_node);
|
||||
const item_ri: ResultInfo = .{ .rl = .none };
|
||||
|
||||
// This contains the data that goes into the `extra` array for the SwitchBlockErrUnion, except
|
||||
// the first cases_nodes.len slots are a table that indexes payloads later in the array,
|
||||
// with the non-error and else case indices coming first, then scalar_cases_len indexes, then
|
||||
// multi_cases_len indexes
|
||||
const payloads = &astgen.scratch;
|
||||
const scratch_top = astgen.scratch.items.len;
|
||||
const case_table_start = scratch_top;
|
||||
const scalar_case_table = case_table_start + 1 + @intFromBool(has_else);
|
||||
const multi_case_table = scalar_case_table + scalar_cases_len;
|
||||
const case_table_end = multi_case_table + multi_cases_len;
|
||||
|
||||
try astgen.scratch.resize(gpa, case_table_end);
|
||||
defer astgen.scratch.items.len = scratch_top;
|
||||
|
||||
var block_scope = parent_gz.makeSubBlock(scope);
|
||||
// block_scope not used for collecting instructions
|
||||
block_scope.instructions_top = GenZir.unstacked_top;
|
||||
block_scope.setBreakResultInfo(block_ri);
|
||||
|
||||
// Sema expects a dbg_stmt immediately before switch_block_err_union
|
||||
try emitDbgStmt(parent_gz, operand_lc);
|
||||
// This gets added to the parent block later, after the item expressions.
|
||||
const switch_block = try parent_gz.makeBlockInst(.switch_block_err_union, switch_node);
|
||||
|
||||
// We re-use this same scope for all cases, including the special prong, if any.
|
||||
var case_scope = parent_gz.makeSubBlock(&block_scope.base);
|
||||
case_scope.instructions_top = GenZir.unstacked_top;
|
||||
|
||||
{
|
||||
const body_len_index: u32 = @intCast(payloads.items.len);
|
||||
payloads.items[case_table_start] = body_len_index;
|
||||
try payloads.resize(gpa, body_len_index + 1); // body_len
|
||||
|
||||
case_scope.instructions_top = parent_gz.instructions.items.len;
|
||||
defer case_scope.unstack();
|
||||
|
||||
try case_scope.addDbgBlockBegin();
|
||||
|
||||
const unwrap_payload_tag: Zir.Inst.Tag = if (payload_is_ref)
|
||||
.err_union_payload_unsafe_ptr
|
||||
else
|
||||
.err_union_payload_unsafe;
|
||||
|
||||
const unwrapped_payload = try case_scope.addUnNode(
|
||||
unwrap_payload_tag,
|
||||
raw_operand,
|
||||
catch_or_if_node,
|
||||
);
|
||||
|
||||
switch (node_ty) {
|
||||
.@"catch" => {
|
||||
const case_result = switch (ri.rl) {
|
||||
.ref, .ref_coerced_ty => unwrapped_payload,
|
||||
else => try rvalue(
|
||||
&case_scope,
|
||||
block_scope.break_result_info,
|
||||
unwrapped_payload,
|
||||
catch_or_if_node,
|
||||
),
|
||||
};
|
||||
try case_scope.addDbgBlockEnd();
|
||||
_ = try case_scope.addBreakWithSrcNode(
|
||||
.@"break",
|
||||
switch_block,
|
||||
case_result,
|
||||
catch_or_if_node,
|
||||
);
|
||||
},
|
||||
.@"if" => {
|
||||
var payload_val_scope: Scope.LocalVal = undefined;
|
||||
|
||||
try case_scope.addDbgBlockBegin();
|
||||
const then_node = if_full.ast.then_expr;
|
||||
const then_sub_scope = s: {
|
||||
assert(if_full.error_token != null);
|
||||
if (if_full.payload_token) |payload_token| {
|
||||
const token_name_index = payload_token + @intFromBool(payload_is_ref);
|
||||
const ident_name = try astgen.identAsString(token_name_index);
|
||||
const token_name_str = tree.tokenSlice(token_name_index);
|
||||
if (mem.eql(u8, "_", token_name_str))
|
||||
break :s &case_scope.base;
|
||||
try astgen.detectLocalShadowing(
|
||||
&case_scope.base,
|
||||
ident_name,
|
||||
token_name_index,
|
||||
token_name_str,
|
||||
.capture,
|
||||
);
|
||||
payload_val_scope = .{
|
||||
.parent = &case_scope.base,
|
||||
.gen_zir = &case_scope,
|
||||
.name = ident_name,
|
||||
.inst = unwrapped_payload,
|
||||
.token_src = payload_token,
|
||||
.id_cat = .capture,
|
||||
};
|
||||
try case_scope.addDbgVar(.dbg_var_val, ident_name, unwrapped_payload);
|
||||
break :s &payload_val_scope.base;
|
||||
} else {
|
||||
_ = try case_scope.addUnNode(
|
||||
.ensure_err_union_payload_void,
|
||||
raw_operand,
|
||||
catch_or_if_node,
|
||||
);
|
||||
break :s &case_scope.base;
|
||||
}
|
||||
};
|
||||
const then_result = try expr(
|
||||
&case_scope,
|
||||
then_sub_scope,
|
||||
block_scope.break_result_info,
|
||||
then_node,
|
||||
);
|
||||
try checkUsed(parent_gz, &case_scope.base, then_sub_scope);
|
||||
if (!case_scope.endsWithNoReturn()) {
|
||||
try case_scope.addDbgBlockEnd();
|
||||
_ = try case_scope.addBreakWithSrcNode(
|
||||
.@"break",
|
||||
switch_block,
|
||||
then_result,
|
||||
then_node,
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const case_slice = case_scope.instructionsSlice();
|
||||
// Since we use the switch_block_err_union instruction itself to refer
|
||||
// to the capture, which will not be added to the child block, we need
|
||||
// to handle ref_table manually.
|
||||
const refs_len = refs: {
|
||||
var n: usize = 0;
|
||||
var check_inst = switch_block;
|
||||
while (astgen.ref_table.get(check_inst)) |ref_inst| {
|
||||
n += 1;
|
||||
check_inst = ref_inst;
|
||||
}
|
||||
break :refs n;
|
||||
};
|
||||
const body_len = refs_len + astgen.countBodyLenAfterFixups(case_slice);
|
||||
try payloads.ensureUnusedCapacity(gpa, body_len);
|
||||
const capture: Zir.Inst.SwitchBlock.ProngInfo.Capture = switch (node_ty) {
|
||||
.@"catch" => .none,
|
||||
.@"if" => if (if_full.payload_token == null)
|
||||
.none
|
||||
else if (payload_is_ref)
|
||||
.by_ref
|
||||
else
|
||||
.by_val,
|
||||
};
|
||||
payloads.items[body_len_index] = @bitCast(Zir.Inst.SwitchBlock.ProngInfo{
|
||||
.body_len = @intCast(body_len),
|
||||
.capture = capture,
|
||||
.is_inline = false,
|
||||
.has_tag_capture = false,
|
||||
});
|
||||
if (astgen.ref_table.fetchRemove(switch_block)) |kv| {
|
||||
appendPossiblyRefdBodyInst(astgen, payloads, kv.value);
|
||||
}
|
||||
appendBodyWithFixupsArrayList(astgen, payloads, case_slice);
|
||||
}
|
||||
|
||||
const err_name = blk: {
|
||||
const err_str = tree.tokenSlice(error_payload);
|
||||
if (mem.eql(u8, err_str, "_")) {
|
||||
return astgen.failTok(error_payload, "discard of error capture; omit it instead", .{});
|
||||
}
|
||||
const err_name = try astgen.identAsString(error_payload);
|
||||
try astgen.detectLocalShadowing(scope, err_name, error_payload, err_str, .capture);
|
||||
|
||||
break :blk err_name;
|
||||
};
|
||||
|
||||
// allocate a shared dummy instruction for the error capture
|
||||
const err_inst = err_inst: {
|
||||
const inst: Zir.Inst.Index = @enumFromInt(astgen.instructions.len);
|
||||
try astgen.instructions.append(astgen.gpa, .{
|
||||
.tag = .extended,
|
||||
.data = .{ .extended = .{
|
||||
.opcode = .value_placeholder,
|
||||
.small = undefined,
|
||||
.operand = undefined,
|
||||
} },
|
||||
});
|
||||
break :err_inst inst;
|
||||
};
|
||||
|
||||
// In this pass we generate all the item and prong expressions for error cases.
|
||||
var multi_case_index: u32 = 0;
|
||||
var scalar_case_index: u32 = 0;
|
||||
var any_uses_err_capture = false;
|
||||
for (case_nodes) |case_node| {
|
||||
const case = tree.fullSwitchCase(case_node).?;
|
||||
|
||||
const is_multi_case = case.ast.values.len > 1 or
|
||||
(case.ast.values.len == 1 and node_tags[case.ast.values[0]] == .switch_range);
|
||||
|
||||
var dbg_var_name: Zir.NullTerminatedString = .empty;
|
||||
var dbg_var_inst: Zir.Inst.Ref = undefined;
|
||||
var err_scope: Scope.LocalVal = undefined;
|
||||
var capture_scope: Scope.LocalVal = undefined;
|
||||
|
||||
const sub_scope = blk: {
|
||||
err_scope = .{
|
||||
.parent = &case_scope.base,
|
||||
.gen_zir = &case_scope,
|
||||
.name = err_name,
|
||||
.inst = err_inst.toRef(),
|
||||
.token_src = error_payload,
|
||||
.id_cat = .capture,
|
||||
};
|
||||
|
||||
const capture_token = case.payload_token orelse break :blk &err_scope.base;
|
||||
assert(token_tags[capture_token] == .identifier);
|
||||
|
||||
const capture_slice = tree.tokenSlice(capture_token);
|
||||
if (mem.eql(u8, capture_slice, "_")) {
|
||||
return astgen.failTok(capture_token, "discard of error capture; omit it instead", .{});
|
||||
}
|
||||
const tag_name = try astgen.identAsString(capture_token);
|
||||
try astgen.detectLocalShadowing(&case_scope.base, tag_name, capture_token, capture_slice, .capture);
|
||||
|
||||
capture_scope = .{
|
||||
.parent = &case_scope.base,
|
||||
.gen_zir = &case_scope,
|
||||
.name = tag_name,
|
||||
.inst = switch_block.toRef(),
|
||||
.token_src = capture_token,
|
||||
.id_cat = .capture,
|
||||
};
|
||||
dbg_var_name = tag_name;
|
||||
dbg_var_inst = switch_block.toRef();
|
||||
|
||||
err_scope.parent = &capture_scope.base;
|
||||
|
||||
break :blk &err_scope.base;
|
||||
};
|
||||
|
||||
const header_index: u32 = @intCast(payloads.items.len);
|
||||
const body_len_index = if (is_multi_case) blk: {
|
||||
payloads.items[multi_case_table + multi_case_index] = header_index;
|
||||
multi_case_index += 1;
|
||||
try payloads.resize(gpa, header_index + 3); // items_len, ranges_len, body_len
|
||||
|
||||
// items
|
||||
var items_len: u32 = 0;
|
||||
for (case.ast.values) |item_node| {
|
||||
if (node_tags[item_node] == .switch_range) continue;
|
||||
items_len += 1;
|
||||
|
||||
const item_inst = try comptimeExpr(parent_gz, scope, item_ri, item_node);
|
||||
try payloads.append(gpa, @intFromEnum(item_inst));
|
||||
}
|
||||
|
||||
// ranges
|
||||
var ranges_len: u32 = 0;
|
||||
for (case.ast.values) |range| {
|
||||
if (node_tags[range] != .switch_range) continue;
|
||||
ranges_len += 1;
|
||||
|
||||
const first = try comptimeExpr(parent_gz, scope, item_ri, node_datas[range].lhs);
|
||||
const last = try comptimeExpr(parent_gz, scope, item_ri, node_datas[range].rhs);
|
||||
try payloads.appendSlice(gpa, &[_]u32{
|
||||
@intFromEnum(first), @intFromEnum(last),
|
||||
});
|
||||
}
|
||||
|
||||
payloads.items[header_index] = items_len;
|
||||
payloads.items[header_index + 1] = ranges_len;
|
||||
break :blk header_index + 2;
|
||||
} else if (case_node == else_node) blk: {
|
||||
payloads.items[case_table_start + 1] = header_index;
|
||||
try payloads.resize(gpa, header_index + 1); // body_len
|
||||
break :blk header_index;
|
||||
} else blk: {
|
||||
payloads.items[scalar_case_table + scalar_case_index] = header_index;
|
||||
scalar_case_index += 1;
|
||||
try payloads.resize(gpa, header_index + 2); // item, body_len
|
||||
const item_node = case.ast.values[0];
|
||||
const item_inst = try comptimeExpr(parent_gz, scope, item_ri, item_node);
|
||||
payloads.items[header_index] = @intFromEnum(item_inst);
|
||||
break :blk header_index + 1;
|
||||
};
|
||||
|
||||
{
|
||||
// temporarily stack case_scope on parent_gz
|
||||
case_scope.instructions_top = parent_gz.instructions.items.len;
|
||||
defer case_scope.unstack();
|
||||
|
||||
try case_scope.addDbgBlockBegin();
|
||||
if (dbg_var_name != .empty) {
|
||||
try case_scope.addDbgVar(.dbg_var_val, dbg_var_name, dbg_var_inst);
|
||||
}
|
||||
const target_expr_node = case.ast.target_expr;
|
||||
const case_result = try expr(&case_scope, sub_scope, block_scope.break_result_info, target_expr_node);
|
||||
// check capture_scope, not err_scope to avoid false positive unused error capture
|
||||
try checkUsed(parent_gz, &case_scope.base, err_scope.parent);
|
||||
const uses_err = err_scope.used != 0 or err_scope.discarded != 0;
|
||||
if (uses_err) {
|
||||
try case_scope.addDbgVar(.dbg_var_val, err_name, err_inst.toRef());
|
||||
any_uses_err_capture = true;
|
||||
}
|
||||
try case_scope.addDbgBlockEnd();
|
||||
if (!parent_gz.refIsNoReturn(case_result)) {
|
||||
_ = try case_scope.addBreakWithSrcNode(.@"break", switch_block, case_result, target_expr_node);
|
||||
}
|
||||
|
||||
const case_slice = case_scope.instructionsSlice();
|
||||
// Since we use the switch_block_err_union instruction itself to refer
|
||||
// to the capture, which will not be added to the child block, we need
|
||||
// to handle ref_table manually.
|
||||
const refs_len = refs: {
|
||||
var n: usize = 0;
|
||||
var check_inst = switch_block;
|
||||
while (astgen.ref_table.get(check_inst)) |ref_inst| {
|
||||
n += 1;
|
||||
check_inst = ref_inst;
|
||||
}
|
||||
if (uses_err) {
|
||||
check_inst = err_inst;
|
||||
while (astgen.ref_table.get(check_inst)) |ref_inst| {
|
||||
n += 1;
|
||||
check_inst = ref_inst;
|
||||
}
|
||||
}
|
||||
break :refs n;
|
||||
};
|
||||
const body_len = refs_len + astgen.countBodyLenAfterFixups(case_slice);
|
||||
try payloads.ensureUnusedCapacity(gpa, body_len);
|
||||
payloads.items[body_len_index] = @bitCast(Zir.Inst.SwitchBlock.ProngInfo{
|
||||
.body_len = @intCast(body_len),
|
||||
.capture = if (case.payload_token != null) .by_val else .none,
|
||||
.is_inline = case.inline_token != null,
|
||||
.has_tag_capture = false,
|
||||
});
|
||||
if (astgen.ref_table.fetchRemove(switch_block)) |kv| {
|
||||
appendPossiblyRefdBodyInst(astgen, payloads, kv.value);
|
||||
}
|
||||
if (uses_err) {
|
||||
if (astgen.ref_table.fetchRemove(err_inst)) |kv| {
|
||||
appendPossiblyRefdBodyInst(astgen, payloads, kv.value);
|
||||
}
|
||||
}
|
||||
appendBodyWithFixupsArrayList(astgen, payloads, case_slice);
|
||||
}
|
||||
}
|
||||
// Now that the item expressions are generated we can add this.
|
||||
try parent_gz.instructions.append(gpa, switch_block);
|
||||
|
||||
try astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.SwitchBlockErrUnion).Struct.fields.len +
|
||||
@intFromBool(multi_cases_len != 0) +
|
||||
payloads.items.len - case_table_end +
|
||||
(case_table_end - case_table_start) * @typeInfo(Zir.Inst.As).Struct.fields.len);
|
||||
|
||||
const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.SwitchBlockErrUnion{
|
||||
.operand = raw_operand,
|
||||
.bits = Zir.Inst.SwitchBlockErrUnion.Bits{
|
||||
.has_multi_cases = multi_cases_len != 0,
|
||||
.has_else = has_else,
|
||||
.scalar_cases_len = @intCast(scalar_cases_len),
|
||||
.any_uses_err_capture = any_uses_err_capture,
|
||||
.payload_is_ref = payload_is_ref,
|
||||
},
|
||||
.main_src_node_offset = parent_gz.nodeIndexToRelative(catch_or_if_node),
|
||||
});
|
||||
|
||||
if (multi_cases_len != 0) {
|
||||
astgen.extra.appendAssumeCapacity(multi_cases_len);
|
||||
}
|
||||
|
||||
if (any_uses_err_capture) {
|
||||
astgen.extra.appendAssumeCapacity(@intFromEnum(err_inst));
|
||||
}
|
||||
|
||||
const zir_datas = astgen.instructions.items(.data);
|
||||
zir_datas[@intFromEnum(switch_block)].pl_node.payload_index = payload_index;
|
||||
|
||||
for (payloads.items[case_table_start..case_table_end], 0..) |start_index, i| {
|
||||
var body_len_index = start_index;
|
||||
var end_index = start_index;
|
||||
const table_index = case_table_start + i;
|
||||
if (table_index < scalar_case_table) {
|
||||
end_index += 1;
|
||||
} else if (table_index < multi_case_table) {
|
||||
body_len_index += 1;
|
||||
end_index += 2;
|
||||
} else {
|
||||
body_len_index += 2;
|
||||
const items_len = payloads.items[start_index];
|
||||
const ranges_len = payloads.items[start_index + 1];
|
||||
end_index += 3 + items_len + 2 * ranges_len;
|
||||
}
|
||||
const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(payloads.items[body_len_index]);
|
||||
end_index += prong_info.body_len;
|
||||
astgen.extra.appendSliceAssumeCapacity(payloads.items[start_index..end_index]);
|
||||
}
|
||||
|
||||
if (need_result_rvalue) {
|
||||
return rvalue(parent_gz, ri, switch_block.toRef(), switch_node);
|
||||
} else {
|
||||
return switch_block.toRef();
|
||||
}
|
||||
}
|
||||
|
||||
fn switchExpr(
|
||||
parent_gz: *GenZir,
|
||||
scope: *Scope,
|
||||
|
||||
1005
src/Sema.zig
1005
src/Sema.zig
File diff suppressed because it is too large
Load Diff
38
src/Zir.zig
38
src/Zir.zig
@@ -100,6 +100,7 @@ pub fn extraData(code: Zir, comptime T: type, index: usize) ExtraData(T) {
|
||||
Inst.Call.Flags,
|
||||
Inst.BuiltinCall.Flags,
|
||||
Inst.SwitchBlock.Bits,
|
||||
Inst.SwitchBlockErrUnion.Bits,
|
||||
Inst.FuncFancy.Bits,
|
||||
=> @bitCast(code.extra[i]),
|
||||
|
||||
@@ -277,9 +278,6 @@ pub const Inst = struct {
|
||||
/// Create a `anyframe->T` type.
|
||||
/// Uses the `un_node` field.
|
||||
anyframe_type,
|
||||
/// Type coercion. No source location attached.
|
||||
/// Uses the `bin` field.
|
||||
as,
|
||||
/// Type coercion to the function's return type.
|
||||
/// Uses the `pl_node` field. Payload is `As`. AST node could be many things.
|
||||
as_node,
|
||||
@@ -688,6 +686,9 @@ pub const Inst = struct {
|
||||
/// A switch expression. Uses the `pl_node` union field.
|
||||
/// AST node is the switch, payload is `SwitchBlock`. Operand is a pointer.
|
||||
switch_block_ref,
|
||||
/// A switch on an error union `a catch |err| switch (err) {...}`.
|
||||
/// Uses the `pl_node` union field. AST node is the `catch`, payload is `SwitchBlockErrUnion`.
|
||||
switch_block_err_union,
|
||||
/// Check that operand type supports the dereference operand (.*).
|
||||
/// Uses the `un_node` field.
|
||||
validate_deref,
|
||||
@@ -1083,7 +1084,6 @@ pub const Inst = struct {
|
||||
.vector_elem_type,
|
||||
.indexable_ptr_len,
|
||||
.anyframe_type,
|
||||
.as,
|
||||
.as_node,
|
||||
.as_shift_operand,
|
||||
.bit_and,
|
||||
@@ -1190,6 +1190,7 @@ pub const Inst = struct {
|
||||
.set_eval_branch_quota,
|
||||
.switch_block,
|
||||
.switch_block_ref,
|
||||
.switch_block_err_union,
|
||||
.validate_deref,
|
||||
.validate_destructure,
|
||||
.union_init,
|
||||
@@ -1396,7 +1397,6 @@ pub const Inst = struct {
|
||||
.vector_elem_type,
|
||||
.indexable_ptr_len,
|
||||
.anyframe_type,
|
||||
.as,
|
||||
.as_node,
|
||||
.as_shift_operand,
|
||||
.bit_and,
|
||||
@@ -1488,6 +1488,7 @@ pub const Inst = struct {
|
||||
.typeof_log2_int_type,
|
||||
.switch_block,
|
||||
.switch_block_ref,
|
||||
.switch_block_err_union,
|
||||
.union_init,
|
||||
.field_type_ref,
|
||||
.enum_from_int,
|
||||
@@ -1629,7 +1630,6 @@ pub const Inst = struct {
|
||||
.vector_elem_type = .un_node,
|
||||
.indexable_ptr_len = .un_node,
|
||||
.anyframe_type = .un_node,
|
||||
.as = .bin,
|
||||
.as_node = .pl_node,
|
||||
.as_shift_operand = .pl_node,
|
||||
.bit_and = .pl_node,
|
||||
@@ -1741,6 +1741,7 @@ pub const Inst = struct {
|
||||
.enum_literal = .str_tok,
|
||||
.switch_block = .pl_node,
|
||||
.switch_block_ref = .pl_node,
|
||||
.switch_block_err_union = .pl_node,
|
||||
.validate_deref = .un_node,
|
||||
.validate_destructure = .pl_node,
|
||||
.field_type_ref = .pl_node,
|
||||
@@ -2782,6 +2783,29 @@ pub const Inst = struct {
|
||||
index: u32,
|
||||
};
|
||||
|
||||
pub const SwitchBlockErrUnion = struct {
|
||||
operand: Ref,
|
||||
bits: Bits,
|
||||
main_src_node_offset: i32,
|
||||
|
||||
pub const Bits = packed struct(u32) {
|
||||
/// If true, one or more prongs have multiple items.
|
||||
has_multi_cases: bool,
|
||||
/// If true, there is an else prong. This is mutually exclusive with `has_under`.
|
||||
has_else: bool,
|
||||
any_uses_err_capture: bool,
|
||||
payload_is_ref: bool,
|
||||
scalar_cases_len: ScalarCasesLen,
|
||||
|
||||
pub const ScalarCasesLen = u28;
|
||||
};
|
||||
|
||||
pub const MultiProng = struct {
|
||||
items: []const Ref,
|
||||
body: []const Index,
|
||||
};
|
||||
};
|
||||
|
||||
/// 0. multi_cases_len: u32 // If has_multi_cases is set.
|
||||
/// 1. tag_capture_inst: u32 // If any_has_tag_capture is set. Index of instruction prongs use to refer to the inline tag capture.
|
||||
/// 2. else_body { // If has_else or has_under is set.
|
||||
@@ -2830,7 +2854,7 @@ pub const Inst = struct {
|
||||
};
|
||||
};
|
||||
|
||||
pub const Bits = packed struct {
|
||||
pub const Bits = packed struct(u32) {
|
||||
/// If true, one or more prongs have multiple items.
|
||||
has_multi_cases: bool,
|
||||
/// If true, there is an else prong. This is mutually exclusive with `has_under`.
|
||||
|
||||
@@ -199,7 +199,6 @@ const Writer = struct {
|
||||
const tag = tags[@intFromEnum(inst)];
|
||||
try stream.print("= {s}(", .{@tagName(tags[@intFromEnum(inst)])});
|
||||
switch (tag) {
|
||||
.as,
|
||||
.store,
|
||||
.store_to_inferred_ptr,
|
||||
=> try self.writeBin(stream, inst),
|
||||
@@ -465,6 +464,8 @@ const Writer = struct {
|
||||
.switch_block_ref,
|
||||
=> try self.writeSwitchBlock(stream, inst),
|
||||
|
||||
.switch_block_err_union => try self.writeSwitchBlockErrUnion(stream, inst),
|
||||
|
||||
.field_val,
|
||||
.field_ptr,
|
||||
=> try self.writePlNodeField(stream, inst),
|
||||
@@ -2027,6 +2028,143 @@ const Writer = struct {
|
||||
try self.writeSrc(stream, inst_data.src());
|
||||
}
|
||||
|
||||
fn writeSwitchBlockErrUnion(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
|
||||
const inst_data = self.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
|
||||
const extra = self.code.extraData(Zir.Inst.SwitchBlockErrUnion, inst_data.payload_index);
|
||||
|
||||
var extra_index: usize = extra.end;
|
||||
|
||||
const multi_cases_len = if (extra.data.bits.has_multi_cases) blk: {
|
||||
const multi_cases_len = self.code.extra[extra_index];
|
||||
extra_index += 1;
|
||||
break :blk multi_cases_len;
|
||||
} else 0;
|
||||
|
||||
const err_capture_inst: Zir.Inst.Index = if (extra.data.bits.any_uses_err_capture) blk: {
|
||||
const tag_capture_inst = self.code.extra[extra_index];
|
||||
extra_index += 1;
|
||||
break :blk @enumFromInt(tag_capture_inst);
|
||||
} else undefined;
|
||||
|
||||
try self.writeInstRef(stream, extra.data.operand);
|
||||
|
||||
if (extra.data.bits.any_uses_err_capture) {
|
||||
try stream.writeAll(", err_capture=");
|
||||
try self.writeInstIndex(stream, err_capture_inst);
|
||||
}
|
||||
|
||||
self.indent += 2;
|
||||
|
||||
{
|
||||
const info = @as(Zir.Inst.SwitchBlock.ProngInfo, @bitCast(self.code.extra[extra_index]));
|
||||
extra_index += 1;
|
||||
|
||||
assert(!info.is_inline);
|
||||
const body = self.code.bodySlice(extra_index, info.body_len);
|
||||
extra_index += body.len;
|
||||
|
||||
try stream.writeAll(",\n");
|
||||
try stream.writeByteNTimes(' ', self.indent);
|
||||
try stream.writeAll("non_err => ");
|
||||
try self.writeBracedBody(stream, body);
|
||||
}
|
||||
|
||||
if (extra.data.bits.has_else) {
|
||||
const info = @as(Zir.Inst.SwitchBlock.ProngInfo, @bitCast(self.code.extra[extra_index]));
|
||||
extra_index += 1;
|
||||
const capture_text = switch (info.capture) {
|
||||
.none => "",
|
||||
.by_val => "by_val ",
|
||||
.by_ref => "by_ref ",
|
||||
};
|
||||
const inline_text = if (info.is_inline) "inline " else "";
|
||||
const body = self.code.bodySlice(extra_index, info.body_len);
|
||||
extra_index += body.len;
|
||||
|
||||
try stream.writeAll(",\n");
|
||||
try stream.writeByteNTimes(' ', self.indent);
|
||||
try stream.print("{s}{s}else => ", .{ capture_text, inline_text });
|
||||
try self.writeBracedBody(stream, body);
|
||||
}
|
||||
|
||||
{
|
||||
const scalar_cases_len = extra.data.bits.scalar_cases_len;
|
||||
var scalar_i: usize = 0;
|
||||
while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
|
||||
const item_ref = @as(Zir.Inst.Ref, @enumFromInt(self.code.extra[extra_index]));
|
||||
extra_index += 1;
|
||||
const info = @as(Zir.Inst.SwitchBlock.ProngInfo, @bitCast(self.code.extra[extra_index]));
|
||||
extra_index += 1;
|
||||
const body = self.code.bodySlice(extra_index, info.body_len);
|
||||
extra_index += info.body_len;
|
||||
|
||||
try stream.writeAll(",\n");
|
||||
try stream.writeByteNTimes(' ', self.indent);
|
||||
switch (info.capture) {
|
||||
.none => {},
|
||||
.by_val => try stream.writeAll("by_val "),
|
||||
.by_ref => try stream.writeAll("by_ref "),
|
||||
}
|
||||
if (info.is_inline) try stream.writeAll("inline ");
|
||||
try self.writeInstRef(stream, item_ref);
|
||||
try stream.writeAll(" => ");
|
||||
try self.writeBracedBody(stream, body);
|
||||
}
|
||||
}
|
||||
{
|
||||
var multi_i: usize = 0;
|
||||
while (multi_i < multi_cases_len) : (multi_i += 1) {
|
||||
const items_len = self.code.extra[extra_index];
|
||||
extra_index += 1;
|
||||
const ranges_len = self.code.extra[extra_index];
|
||||
extra_index += 1;
|
||||
const info = @as(Zir.Inst.SwitchBlock.ProngInfo, @bitCast(self.code.extra[extra_index]));
|
||||
extra_index += 1;
|
||||
const items = self.code.refSlice(extra_index, items_len);
|
||||
extra_index += items_len;
|
||||
|
||||
try stream.writeAll(",\n");
|
||||
try stream.writeByteNTimes(' ', self.indent);
|
||||
switch (info.capture) {
|
||||
.none => {},
|
||||
.by_val => try stream.writeAll("by_val "),
|
||||
.by_ref => try stream.writeAll("by_ref "),
|
||||
}
|
||||
if (info.is_inline) try stream.writeAll("inline ");
|
||||
|
||||
for (items, 0..) |item_ref, item_i| {
|
||||
if (item_i != 0) try stream.writeAll(", ");
|
||||
try self.writeInstRef(stream, item_ref);
|
||||
}
|
||||
|
||||
var range_i: usize = 0;
|
||||
while (range_i < ranges_len) : (range_i += 1) {
|
||||
const item_first = @as(Zir.Inst.Ref, @enumFromInt(self.code.extra[extra_index]));
|
||||
extra_index += 1;
|
||||
const item_last = @as(Zir.Inst.Ref, @enumFromInt(self.code.extra[extra_index]));
|
||||
extra_index += 1;
|
||||
|
||||
if (range_i != 0 or items.len != 0) {
|
||||
try stream.writeAll(", ");
|
||||
}
|
||||
try self.writeInstRef(stream, item_first);
|
||||
try stream.writeAll("...");
|
||||
try self.writeInstRef(stream, item_last);
|
||||
}
|
||||
|
||||
const body = self.code.bodySlice(extra_index, info.body_len);
|
||||
extra_index += info.body_len;
|
||||
try stream.writeAll(" => ");
|
||||
try self.writeBracedBody(stream, body);
|
||||
}
|
||||
}
|
||||
|
||||
self.indent -= 2;
|
||||
|
||||
try stream.writeAll(") ");
|
||||
try self.writeSrc(stream, inst_data.src());
|
||||
}
|
||||
|
||||
fn writeSwitchBlock(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
|
||||
const inst_data = self.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
|
||||
const extra = self.code.extraData(Zir.Inst.SwitchBlock, inst_data.payload_index);
|
||||
|
||||
750
test/behavior/switch_on_captured_error.zig
Normal file
750
test/behavior/switch_on_captured_error.zig
Normal file
@@ -0,0 +1,750 @@
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const expect = std.testing.expect;
|
||||
const expectError = std.testing.expectError;
|
||||
const expectEqual = std.testing.expectEqual;
|
||||
|
||||
test "switch on error union catch capture" {
|
||||
const S = struct {
|
||||
const Error = error{ A, B, C };
|
||||
fn doTheTest() !void {
|
||||
try testScalar();
|
||||
try testMulti();
|
||||
try testElse();
|
||||
try testCapture();
|
||||
try testInline();
|
||||
try testEmptyErrSet();
|
||||
}
|
||||
|
||||
fn testScalar() !void {
|
||||
{
|
||||
var a: Error!u64 = 3;
|
||||
_ = &a;
|
||||
const b: u64 = a catch |err| switch (err) {
|
||||
error.A => 0,
|
||||
error.B => 1,
|
||||
error.C => 2,
|
||||
};
|
||||
try expectEqual(@as(u64, 3), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = 3;
|
||||
_ = &a;
|
||||
const b: u64 = a catch |err| switch (err) {
|
||||
error.A => 0,
|
||||
error.B => @intFromError(err) + 4,
|
||||
error.C => @intFromError(err) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, 3), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = error.A;
|
||||
_ = &a;
|
||||
const b: u64 = a catch |err| switch (err) {
|
||||
error.A => 0,
|
||||
error.B => @intFromError(err) + 4,
|
||||
error.C => @intFromError(err) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, 0), b);
|
||||
}
|
||||
}
|
||||
|
||||
fn testMulti() !void {
|
||||
{
|
||||
var a: Error!u64 = 3;
|
||||
_ = &a;
|
||||
const b: u64 = a catch |err| switch (err) {
|
||||
error.A, error.B => 0,
|
||||
error.C => @intFromError(err) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, 3), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = 3;
|
||||
_ = &a;
|
||||
const b: u64 = a catch |err| switch (err) {
|
||||
error.A => 0,
|
||||
error.B, error.C => @intFromError(err) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, 3), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = error.A;
|
||||
_ = &a;
|
||||
const b: u64 = a catch |err| switch (err) {
|
||||
error.A, error.B => 0,
|
||||
error.C => @intFromError(err) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, 0), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = error.A;
|
||||
_ = &a;
|
||||
const b: u64 = a catch |err| switch (err) {
|
||||
error.A => 0,
|
||||
error.B, error.C => @intFromError(err) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, 0), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = error.B;
|
||||
_ = &a;
|
||||
const b: u64 = a catch |err| switch (err) {
|
||||
error.A => 0,
|
||||
error.B, error.C => @intFromError(err) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, @intFromError(error.B) + 4), b);
|
||||
}
|
||||
}
|
||||
|
||||
fn testElse() !void {
|
||||
{
|
||||
var a: Error!u64 = 3;
|
||||
_ = &a;
|
||||
const b: u64 = a catch |err| switch (err) {
|
||||
error.A => 0,
|
||||
else => 1,
|
||||
};
|
||||
try expectEqual(@as(u64, 3), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = 3;
|
||||
_ = &a;
|
||||
const b: u64 = a catch |err| switch (err) {
|
||||
error.A => 0,
|
||||
else => @intFromError(err) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, 3), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = error.A;
|
||||
_ = &a;
|
||||
const b: u64 = a catch |err| switch (err) {
|
||||
error.A => 1,
|
||||
else => @intFromError(err) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, 1), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = error.B;
|
||||
_ = &a;
|
||||
const b: u64 = a catch |err| switch (err) {
|
||||
error.A => 0,
|
||||
else => 1,
|
||||
};
|
||||
try expectEqual(@as(u64, 1), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = error.B;
|
||||
_ = &a;
|
||||
const b: u64 = a catch |err| switch (err) {
|
||||
error.A => 0,
|
||||
else => @intFromError(err) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, @intFromError(error.B) + 4), b);
|
||||
}
|
||||
}
|
||||
|
||||
fn testCapture() !void {
|
||||
{
|
||||
var a: Error!u64 = error.A;
|
||||
_ = &a;
|
||||
const b: u64 = a catch |err| switch (err) {
|
||||
error.A => |e| @intFromError(e) + 4,
|
||||
else => 0,
|
||||
};
|
||||
try expectEqual(@as(u64, @intFromError(error.A) + 4), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = error.A;
|
||||
_ = &a;
|
||||
const b: u64 = a catch |err| switch (err) {
|
||||
error.A => 0,
|
||||
else => |e| @intFromError(e) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, 0), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = error.B;
|
||||
_ = &a;
|
||||
const b: u64 = a catch |err| switch (err) {
|
||||
error.A => 0,
|
||||
else => |e| @intFromError(e) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, @intFromError(error.B) + 4), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = error.B;
|
||||
_ = &a;
|
||||
const b: u64 = a catch |err| switch (err) {
|
||||
error.A => |e| @intFromError(e) + 4,
|
||||
else => |e| @intFromError(e) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, @intFromError(error.B) + 4), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = error.B;
|
||||
_ = &a;
|
||||
const b: u64 = a catch |err| switch (err) {
|
||||
error.A => 0,
|
||||
error.B, error.C => |e| @intFromError(e) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, @intFromError(error.B) + 4), b);
|
||||
}
|
||||
}
|
||||
|
||||
fn testInline() !void {
|
||||
{
|
||||
var a: Error!u64 = error.B;
|
||||
_ = &a;
|
||||
const b: u64 = a catch |err| switch (err) {
|
||||
error.A => 0,
|
||||
inline else => @intFromError(err) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, @intFromError(error.B) + 4), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = error.B;
|
||||
_ = &a;
|
||||
const b: u64 = a catch |err| switch (err) {
|
||||
error.A => |e| @intFromError(e) + 4,
|
||||
inline else => @intFromError(err) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, @intFromError(error.B) + 4), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = error.B;
|
||||
_ = &a;
|
||||
const b: u64 = a catch |err| switch (err) {
|
||||
inline else => |e| @intFromError(e) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, @intFromError(error.B) + 4), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = error.B;
|
||||
_ = &a;
|
||||
const b: u64 = a catch |err| switch (err) {
|
||||
error.A => 0,
|
||||
inline error.B, error.C => |e| @intFromError(e) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, @intFromError(error.B) + 4), b);
|
||||
}
|
||||
}
|
||||
|
||||
fn testEmptyErrSet() !void {
|
||||
{
|
||||
var a: error{}!u64 = 0;
|
||||
_ = &a;
|
||||
const b: u64 = a catch |err| switch (err) {
|
||||
else => |e| return e,
|
||||
};
|
||||
try expectEqual(@as(u64, 0), b);
|
||||
}
|
||||
{
|
||||
var a: error{}!u64 = 0;
|
||||
_ = &a;
|
||||
const b: u64 = a catch |err| switch (err) {
|
||||
error.UnknownError => return error.Fail,
|
||||
else => |e| return e,
|
||||
};
|
||||
try expectEqual(@as(u64, 0), b);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try comptime S.doTheTest();
|
||||
try S.doTheTest();
|
||||
}
|
||||
|
||||
test "switch on error union if else capture" {
|
||||
const S = struct {
|
||||
const Error = error{ A, B, C };
|
||||
fn doTheTest() !void {
|
||||
try testScalar();
|
||||
try testScalarPtr();
|
||||
try testMulti();
|
||||
try testMultiPtr();
|
||||
try testElse();
|
||||
try testElsePtr();
|
||||
try testCapture();
|
||||
try testCapturePtr();
|
||||
try testInline();
|
||||
try testInlinePtr();
|
||||
try testEmptyErrSet();
|
||||
try testEmptyErrSetPtr();
|
||||
}
|
||||
|
||||
fn testScalar() !void {
|
||||
{
|
||||
var a: Error!u64 = 3;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |x| x else |err| switch (err) {
|
||||
error.A => 0,
|
||||
error.B => 1,
|
||||
error.C => 2,
|
||||
};
|
||||
try expectEqual(@as(u64, 3), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = 3;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |x| x else |err| switch (err) {
|
||||
error.A => 0,
|
||||
error.B => @intFromError(err) + 4,
|
||||
error.C => @intFromError(err) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, 3), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = error.A;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |x| x else |err| switch (err) {
|
||||
error.A => 0,
|
||||
error.B => @intFromError(err) + 4,
|
||||
error.C => @intFromError(err) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, 0), b);
|
||||
}
|
||||
}
|
||||
|
||||
fn testScalarPtr() !void {
|
||||
{
|
||||
var a: Error!u64 = 3;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |*x| x.* else |err| switch (err) {
|
||||
error.A => 0,
|
||||
error.B => 1,
|
||||
error.C => 2,
|
||||
};
|
||||
try expectEqual(@as(u64, 3), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = 3;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |*x| x.* else |err| switch (err) {
|
||||
error.A => 0,
|
||||
error.B => @intFromError(err) + 4,
|
||||
error.C => @intFromError(err) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, 3), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = error.A;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |*x| x.* else |err| switch (err) {
|
||||
error.A => 0,
|
||||
error.B => @intFromError(err) + 4,
|
||||
error.C => @intFromError(err) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, 0), b);
|
||||
}
|
||||
}
|
||||
|
||||
fn testMulti() !void {
|
||||
{
|
||||
var a: Error!u64 = 3;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |x| x else |err| switch (err) {
|
||||
error.A, error.B => 0,
|
||||
error.C => @intFromError(err) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, 3), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = 3;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |x| x else |err| switch (err) {
|
||||
error.A => 0,
|
||||
error.B, error.C => @intFromError(err) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, 3), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = error.A;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |x| x else |err| switch (err) {
|
||||
error.A, error.B => 0,
|
||||
error.C => @intFromError(err) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, 0), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = error.A;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |x| x else |err| switch (err) {
|
||||
error.A => 0,
|
||||
error.B, error.C => @intFromError(err) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, 0), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = error.B;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |x| x else |err| switch (err) {
|
||||
error.A => 0,
|
||||
error.B, error.C => @intFromError(err) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, @intFromError(error.B) + 4), b);
|
||||
}
|
||||
}
|
||||
|
||||
fn testMultiPtr() !void {
|
||||
{
|
||||
var a: Error!u64 = 3;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |*x| x.* else |err| switch (err) {
|
||||
error.A, error.B => 0,
|
||||
error.C => @intFromError(err) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, 3), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = 3;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |*x| x.* else |err| switch (err) {
|
||||
error.A => 0,
|
||||
error.B, error.C => @intFromError(err) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, 3), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = error.A;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |*x| x.* else |err| switch (err) {
|
||||
error.A, error.B => 0,
|
||||
error.C => @intFromError(err) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, 0), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = error.A;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |*x| x.* else |err| switch (err) {
|
||||
error.A => 0,
|
||||
error.B, error.C => @intFromError(err) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, 0), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = error.B;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |*x| x.* else |err| switch (err) {
|
||||
error.A => 0,
|
||||
error.B, error.C => @intFromError(err) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, @intFromError(error.B) + 4), b);
|
||||
}
|
||||
}
|
||||
|
||||
fn testElse() !void {
|
||||
{
|
||||
var a: Error!u64 = 3;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |x| x else |err| switch (err) {
|
||||
error.A => 0,
|
||||
else => 1,
|
||||
};
|
||||
try expectEqual(@as(u64, 3), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = 3;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |x| x else |err| switch (err) {
|
||||
error.A => 0,
|
||||
else => @intFromError(err) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, 3), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = error.A;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |x| x else |err| switch (err) {
|
||||
error.A => 1,
|
||||
else => @intFromError(err) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, 1), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = error.B;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |x| x else |err| switch (err) {
|
||||
error.A => 0,
|
||||
else => 1,
|
||||
};
|
||||
try expectEqual(@as(u64, 1), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = error.B;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |x| x else |err| switch (err) {
|
||||
error.A => 0,
|
||||
else => @intFromError(err) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, @intFromError(error.B) + 4), b);
|
||||
}
|
||||
}
|
||||
|
||||
fn testElsePtr() !void {
|
||||
{
|
||||
var a: Error!u64 = 3;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |*x| x.* else |err| switch (err) {
|
||||
error.A => 0,
|
||||
else => 1,
|
||||
};
|
||||
try expectEqual(@as(u64, 3), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = 3;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |*x| x.* else |err| switch (err) {
|
||||
error.A => 0,
|
||||
else => @intFromError(err) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, 3), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = error.A;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |*x| x.* else |err| switch (err) {
|
||||
error.A => 1,
|
||||
else => @intFromError(err) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, 1), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = error.B;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |*x| x.* else |err| switch (err) {
|
||||
error.A => 0,
|
||||
else => 1,
|
||||
};
|
||||
try expectEqual(@as(u64, 1), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = error.B;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |*x| x.* else |err| switch (err) {
|
||||
error.A => 0,
|
||||
else => @intFromError(err) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, @intFromError(error.B) + 4), b);
|
||||
}
|
||||
}
|
||||
|
||||
fn testCapture() !void {
|
||||
{
|
||||
var a: Error!u64 = error.A;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |x| x else |err| switch (err) {
|
||||
error.A => |e| @intFromError(e) + 4,
|
||||
else => 0,
|
||||
};
|
||||
try expectEqual(@as(u64, @intFromError(error.A) + 4), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = error.A;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |x| x else |err| switch (err) {
|
||||
error.A => 0,
|
||||
else => |e| @intFromError(e) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, 0), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = error.B;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |x| x else |err| switch (err) {
|
||||
error.A => 0,
|
||||
else => |e| @intFromError(e) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, @intFromError(error.B) + 4), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = error.B;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |x| x else |err| switch (err) {
|
||||
error.A => |e| @intFromError(e) + 4,
|
||||
else => |e| @intFromError(e) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, @intFromError(error.B) + 4), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = error.B;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |x| x else |err| switch (err) {
|
||||
error.A => 0,
|
||||
error.B, error.C => |e| @intFromError(e) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, @intFromError(error.B) + 4), b);
|
||||
}
|
||||
}
|
||||
|
||||
fn testCapturePtr() !void {
|
||||
{
|
||||
var a: Error!u64 = error.A;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |*x| x.* else |err| switch (err) {
|
||||
error.A => |e| @intFromError(e) + 4,
|
||||
else => 0,
|
||||
};
|
||||
try expectEqual(@as(u64, @intFromError(error.A) + 4), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = error.A;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |*x| x.* else |err| switch (err) {
|
||||
error.A => 0,
|
||||
else => |e| @intFromError(e) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, 0), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = error.B;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |*x| x.* else |err| switch (err) {
|
||||
error.A => 0,
|
||||
else => |e| @intFromError(e) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, @intFromError(error.B) + 4), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = error.B;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |*x| x.* else |err| switch (err) {
|
||||
error.A => |e| @intFromError(e) + 4,
|
||||
else => |e| @intFromError(e) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, @intFromError(error.B) + 4), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = error.B;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |*x| x.* else |err| switch (err) {
|
||||
error.A => 0,
|
||||
error.B, error.C => |e| @intFromError(e) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, @intFromError(error.B) + 4), b);
|
||||
}
|
||||
}
|
||||
|
||||
fn testInline() !void {
|
||||
{
|
||||
var a: Error!u64 = error.B;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |x| x else |err| switch (err) {
|
||||
error.A => 0,
|
||||
inline else => @intFromError(err) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, @intFromError(error.B) + 4), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = error.B;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |x| x else |err| switch (err) {
|
||||
error.A => |e| @intFromError(e) + 4,
|
||||
inline else => @intFromError(err) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, @intFromError(error.B) + 4), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = error.B;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |x| x else |err| switch (err) {
|
||||
inline else => |e| @intFromError(e) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, @intFromError(error.B) + 4), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = error.B;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |x| x else |err| switch (err) {
|
||||
error.A => 0,
|
||||
inline error.B, error.C => |e| @intFromError(e) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, @intFromError(error.B) + 4), b);
|
||||
}
|
||||
}
|
||||
|
||||
fn testInlinePtr() !void {
|
||||
{
|
||||
var a: Error!u64 = error.B;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |*x| x.* else |err| switch (err) {
|
||||
error.A => 0,
|
||||
inline else => @intFromError(err) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, @intFromError(error.B) + 4), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = error.B;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |*x| x.* else |err| switch (err) {
|
||||
error.A => |e| @intFromError(e) + 4,
|
||||
inline else => @intFromError(err) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, @intFromError(error.B) + 4), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = error.B;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |*x| x.* else |err| switch (err) {
|
||||
inline else => |e| @intFromError(e) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, @intFromError(error.B) + 4), b);
|
||||
}
|
||||
{
|
||||
var a: Error!u64 = error.B;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |*x| x.* else |err| switch (err) {
|
||||
error.A => 0,
|
||||
inline error.B, error.C => |e| @intFromError(e) + 4,
|
||||
};
|
||||
try expectEqual(@as(u64, @intFromError(error.B) + 4), b);
|
||||
}
|
||||
}
|
||||
|
||||
fn testEmptyErrSet() !void {
|
||||
{
|
||||
var a: error{}!u64 = 0;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |x| x else |err| switch (err) {
|
||||
else => |e| return e,
|
||||
};
|
||||
try expectEqual(@as(u64, 0), b);
|
||||
}
|
||||
{
|
||||
var a: error{}!u64 = 0;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |x| x else |err| switch (err) {
|
||||
error.UnknownError => return error.Fail,
|
||||
else => |e| return e,
|
||||
};
|
||||
try expectEqual(@as(u64, 0), b);
|
||||
}
|
||||
}
|
||||
|
||||
fn testEmptyErrSetPtr() !void {
|
||||
{
|
||||
var a: error{}!u64 = 0;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |*x| x.* else |err| switch (err) {
|
||||
else => |e| return e,
|
||||
};
|
||||
try expectEqual(@as(u64, 0), b);
|
||||
}
|
||||
{
|
||||
var a: error{}!u64 = 0;
|
||||
_ = &a;
|
||||
const b: u64 = if (a) |*x| x.* else |err| switch (err) {
|
||||
error.UnknownError => return error.Fail,
|
||||
else => |e| return e,
|
||||
};
|
||||
try expectEqual(@as(u64, 0), b);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try comptime S.doTheTest();
|
||||
try S.doTheTest();
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
fn f(n: Error!i32) i32 {
|
||||
if (n) |x|
|
||||
_ = x
|
||||
else |e| switch (e) {
|
||||
error.Foo => 1,
|
||||
error.Bar => 2,
|
||||
error.Baz => 3,
|
||||
error.Foo => 2,
|
||||
}
|
||||
}
|
||||
fn g(n: Error!i32) i32 {
|
||||
n catch |e| switch (e) {
|
||||
error.Foo => 1,
|
||||
error.Bar => 2,
|
||||
error.Baz => 3,
|
||||
error.Foo => 2,
|
||||
};
|
||||
}
|
||||
|
||||
const Error = error{ Foo, Bar, Baz };
|
||||
|
||||
export fn entry() usize {
|
||||
return @sizeOf(@TypeOf(&f)) + @sizeOf(@TypeOf(&g));
|
||||
}
|
||||
|
||||
// error
|
||||
// backend=stage2
|
||||
// target=native
|
||||
//
|
||||
// :8:9: error: duplicate switch value
|
||||
// :5:9: note: previous value here
|
||||
// :16:9: error: duplicate switch value
|
||||
// :13:9: note: previous value here
|
||||
@@ -0,0 +1,35 @@
|
||||
fn f(n: Error!i32) i32 {
|
||||
if (n) |x|
|
||||
_ = x
|
||||
else |e| switch (e) {
|
||||
error.Foo => 1,
|
||||
error.Bar => 2,
|
||||
error.Baz => 3,
|
||||
error.Foo => 2,
|
||||
else => 10,
|
||||
}
|
||||
}
|
||||
fn g(n: Error!i32) i32 {
|
||||
n catch |e| switch (e) {
|
||||
error.Foo => 1,
|
||||
error.Bar => 2,
|
||||
error.Baz => 3,
|
||||
error.Foo => 2,
|
||||
else => 10,
|
||||
};
|
||||
}
|
||||
|
||||
const Error = error{ Foo, Bar, Baz };
|
||||
|
||||
export fn entry() usize {
|
||||
return @sizeOf(@TypeOf(&f)) + @sizeOf(@TypeOf(&g));
|
||||
}
|
||||
|
||||
// error
|
||||
// backend=stage2
|
||||
// target=native
|
||||
//
|
||||
// :8:9: error: duplicate switch value
|
||||
// :5:9: note: previous value here
|
||||
// :17:9: error: duplicate switch value
|
||||
// :14:9: note: previous value here
|
||||
@@ -0,0 +1,33 @@
|
||||
const Error = error {
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
};
|
||||
fn f(n: Error!i32) i32 {
|
||||
if (n) |x| x else |e| switch (e) {
|
||||
error.One => 1,
|
||||
error.Two => 2,
|
||||
error.Three => 3,
|
||||
}
|
||||
}
|
||||
fn h(n: Error!i32) i32 {
|
||||
n catch |e| switch (e) {
|
||||
error.One => 1,
|
||||
error.Two => 2,
|
||||
error.Three => 3,
|
||||
};
|
||||
}
|
||||
|
||||
export fn entry() usize {
|
||||
return @sizeOf(@TypeOf(&f)) + @sizeOf(@TypeOf(&h));
|
||||
}
|
||||
|
||||
// error
|
||||
// backend=stage2
|
||||
// target=native
|
||||
//
|
||||
// :8:27: error: switch must handle all possibilities
|
||||
// :8:27: note: unhandled error value: 'error.Four'
|
||||
// :15:17: error: switch must handle all possibilities
|
||||
// :15:17: note: unhandled error value: 'error.Four'
|
||||
@@ -5,8 +5,24 @@ fn f(x: u32) void {
|
||||
else => true,
|
||||
};
|
||||
}
|
||||
fn g(x: error{Foo, Bar, Baz}!u32) void {
|
||||
const value: bool = if (x) |_| true else |e| switch (e) {
|
||||
error.Foo => false,
|
||||
else => true,
|
||||
else => true,
|
||||
};
|
||||
}
|
||||
fn h(x: error{Foo, Bar, Baz}!u32) void {
|
||||
const value: u32 = x catch |e| switch (e) {
|
||||
error.Foo => 1,
|
||||
else => 2,
|
||||
else => 3,
|
||||
};
|
||||
}
|
||||
export fn entry() void {
|
||||
f(1234);
|
||||
g(1234);
|
||||
h(1234);
|
||||
}
|
||||
|
||||
// error
|
||||
@@ -15,3 +31,7 @@ export fn entry() void {
|
||||
//
|
||||
// :5:9: error: multiple else prongs in switch expression
|
||||
// :4:9: note: previous else prong here
|
||||
// :12:9: error: multiple else prongs in switch expression
|
||||
// :11:9: note: previous else prong here
|
||||
// :19:9: error: multiple else prongs in switch expression
|
||||
// :18:9: note: previous else prong here
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
fn foo(x: u2) void {
|
||||
const y: Error!u2 = x;
|
||||
if (y) |_| {} else |e| switch (e) {
|
||||
error.Foo => {},
|
||||
error.Bar => {},
|
||||
error.Baz => {},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn bar(x: u2) void {
|
||||
const y: Error!u2 = x;
|
||||
y catch |e| switch (e) {
|
||||
error.Foo => {},
|
||||
error.Bar => {},
|
||||
error.Baz => {},
|
||||
else => {},
|
||||
};
|
||||
}
|
||||
|
||||
const Error = error{ Foo, Bar, Baz };
|
||||
|
||||
export fn entry() usize {
|
||||
return @sizeOf(@TypeOf(&foo)) + @sizeOf(@TypeOf(&bar));
|
||||
}
|
||||
|
||||
// error
|
||||
// backend=stage2
|
||||
// target=native
|
||||
//
|
||||
// :7:14: error: unreachable else prong; all cases already handled
|
||||
// :17:14: error: unreachable else prong; all cases already handled
|
||||
12
test/cases/compile_errors/switch_on_error_union_discard.zig
Normal file
12
test/cases/compile_errors/switch_on_error_union_discard.zig
Normal file
@@ -0,0 +1,12 @@
|
||||
export fn entry() void {
|
||||
const x: error{}!u32 = 0;
|
||||
if (x) |v| v else |_| switch (_) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// error
|
||||
// backend=stage2
|
||||
// target=native
|
||||
//
|
||||
// :3:24: error: discard of error capture; omit it instead
|
||||
@@ -0,0 +1,20 @@
|
||||
const Error = error{M};
|
||||
|
||||
export fn entry() void {
|
||||
const f: Error!void = void{};
|
||||
if (f) {} else |e| switch (e) {}
|
||||
}
|
||||
|
||||
export fn entry2() void {
|
||||
const f: Error!void = void{};
|
||||
f catch |e| switch (e) {};
|
||||
}
|
||||
|
||||
// error
|
||||
// backend=stage2
|
||||
// target=native
|
||||
//
|
||||
// :5:24: error: switch must handle all possibilities
|
||||
// :5:24: note: unhandled error value: 'error.M'
|
||||
// :10:17: error: switch must handle all possibilities
|
||||
// :10:17: note: unhandled error value: 'error.M'
|
||||
@@ -23,6 +23,13 @@ pub export fn entry() usize {
|
||||
u += 1;
|
||||
},
|
||||
}
|
||||
if (@as(error{}!usize, u)) |_| {
|
||||
u += 1;
|
||||
} else |e| switch (e) {
|
||||
else => {
|
||||
u += 1;
|
||||
}
|
||||
}
|
||||
return u;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user