feature. See also
. The project being documented here (as the example) is the Zig library itself.
AstGen.switchExpr
fn switchExpr(
parent_gz: *GenZir,
scope: *Scope,
ri: ResultInfo,
node: Ast.Node.Index,
switch_full: Ast.full.Switch,
non_err: SwitchNonErr,
) InnerError!Zir.Inst.Ref
File
Code
fn switchExpr(
parent_gz: *GenZir,
scope: *Scope,
ri: ResultInfo,
node: Ast.Node.Index,
switch_full: Ast.full.Switch,
non_err: SwitchNonErr,
) InnerError!Zir.Inst.Ref {
const astgen = parent_gz.astgen;
const gpa = astgen.gpa;
const tree = astgen.tree;
const switch_node, const operand_node, const err_token = switch (non_err) {
.none, .peer_break_target => .{
node,
switch_full.ast.condition,
undefined,
},
.@"catch" => .{
tree.nodeData(node).node_and_node[1],
tree.nodeData(node).node_and_node[0],
tree.nodeMainToken(node) + 2,
},
.@"if" => |if_full| .{
if_full.ast.else_expr.unwrap().?,
if_full.ast.cond_expr,
if_full.error_token.?,
},
};
const case_nodes = switch_full.ast.cases;
const is_err_switch = non_err != .none;
const needs_non_err_handling = switch (non_err) {
.none => false,
.peer_break_target => false,
.@"catch", .@"if" => true,
};
const need_rl = astgen.nodes_need_rl.contains(node);
const block_ri: ResultInfo = if (need_rl) ri else .{
.rl = switch (ri.rl) {
.ptr => .{ .ty = (try ri.rl.resultType(parent_gz, node)).? },
.inferred_ptr => .none,
else => ri.rl,
},
.ctx = ri.ctx,
};
// 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);
const catch_or_if_node = if (needs_non_err_handling) node else undefined;
const do_err_trace = needs_non_err_handling and astgen.fn_block != null;
const non_err_is_ref: enum { no, yes, yes_const } = switch (non_err) {
.none, .peer_break_target => undefined,
.@"catch" => switch (ri.rl) {
.ref, .ref_coerced_ty => .yes,
.ref_const => .yes_const,
else => .no,
},
.@"if" => |if_full| if (if_full.payload_token != null and
tree.tokenTag(if_full.payload_token.?) == .asterisk) .yes else .no,
};
if (switch_full.label_token) |label_token| {
try astgen.checkLabelRedefinition(scope, label_token);
}
const err_capture_name: Zir.NullTerminatedString = if (needs_non_err_handling) blk: {
const err_str = tree.tokenSlice(err_token);
if (mem.eql(u8, err_str, "_")) {
return astgen.failTok(err_token, "discard of error capture; omit it instead", .{});
}
const err_name = try astgen.identAsString(err_token);
try astgen.detectLocalShadowing(scope, err_name, err_token, err_str, .capture);
break :blk err_name;
} else undefined;
// for the following variables, make note of the special prong AST node indices,
// and bail out with a compile error if there are incompatible special prongs present.
var any_payload_is_ref = false;
var any_has_payload_capture = false;
var any_has_tag_capture = false;
var any_maybe_runtime_capture = false;
var scalar_cases_len: u32 = 0;
var multi_cases_len: u32 = 0;
var total_items_len: usize = 0;
var total_ranges_len: usize = 0;
var else_case_node: Ast.Node.OptionalIndex = .none;
var underscore_node: Ast.Node.OptionalIndex = .none;
for (case_nodes) |case_node| {
const case = tree.fullSwitchCase(case_node).?;
if (case.payload_token) |payload_token| {
const ident = if (tree.tokenTag(payload_token) == .asterisk) blk: {
// check for this again later we will fail as late as possible.
any_payload_is_ref = true;
break :blk payload_token + 1;
} else payload_token;
if (!mem.eql(u8, tree.tokenSlice(ident), "_")) {
any_has_payload_capture = true;
// comptime-known, even if its prong is inlined as inlining only
// affects its enum tag.
// This check isn't perfect, because for things like enums, the
// entire capture *is* comptime-known for inline prongs! But such
// knowledge requires semantic analysis.
any_maybe_runtime_capture = true;
}
if (tree.tokenTag(ident + 1) == .comma) {
any_has_tag_capture = true;
if (case.inline_token == null) {
any_maybe_runtime_capture = true;
}
}
}
if (case.ast.values.len == 0) {
if (else_case_node.unwrap()) |prev_case_node| {
const prev_else_tok = tree.fullSwitchCase(prev_case_node).?.ast.arrow_token - 1;
const else_tok = case.ast.arrow_token - 1;
return astgen.failTokNotes(
else_tok,
"multiple else prongs in switch expression",
.{},
&.{try astgen.errNoteTok(prev_else_tok, "previous else prong here", .{})},
);
}
else_case_node = case_node.toOptional();
continue;
}
var case_has_ranges = false;
for (case.ast.values) |val| {
switch (tree.nodeTag(val)) {
.switch_range => {
total_ranges_len += 1;
case_has_ranges = true;
},
.string_literal => return astgen.failNode(val, "cannot switch on strings", .{}),
else => |tag| {
total_items_len += 1;
if (tag == .identifier and
mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(val)), "_"))
{
if (is_err_switch) {
const case_src = case.ast.arrow_token - 1;
return astgen.failTokNotes(
case_src,
"'_' prong is not allowed when switching on errors",
.{},
&.{
try astgen.errNoteTok(
case_src,
"consider using 'else'",
.{},
),
},
);
}
if (underscore_node.unwrap()) |prev_src| {
return astgen.failNodeNotes(
val,
"multiple '_' prongs in switch expression",
.{},
&.{try astgen.errNoteNode(prev_src, "previous '_' prong here", .{})},
);
}
if (case.inline_token != null) {
return astgen.failNode(val, "cannot inline '_' prong", .{});
}
underscore_node = val.toOptional();
}
},
}
}
const case_len = case.ast.values.len;
if (case_len == 1 and !case_has_ranges) {
scalar_cases_len += 1;
} else if (case_len >= 1) {
multi_cases_len += 1;
}
}
const has_else = else_case_node != .none;
const has_under = underscore_node != .none;
if (is_err_switch) assert(!has_under);
const any_ranges = total_ranges_len > 0;
// the bodies they belong to that go into the `extra` array later, except the
// first item_table_end slots are a table that indexes the item bodies (and
// also indirectly the prong bodies, as they are always trailing after their
// item bodies).
const payloads = &astgen.scratch;
const scratch_top = astgen.scratch.items.len;
var payloads_end = scratch_top;
// have to keep track of the position of the second body. However handling
// all of the several indices and offsets is complicated enough as it is,
// so for the sake of keeping this function a little bit more simple we do
// it anyway.
const scalar_body_table = payloads_end;
payloads_end += scalar_cases_len;
const multi_item_body_table = payloads_end;
payloads_end += total_items_len + 2 * total_ranges_len - scalar_cases_len;
const multi_prong_body_table = payloads_end;
payloads_end += multi_cases_len;
const body_table_end = payloads_end;
const scalar_prong_infos_start = payloads_end;
payloads_end += scalar_cases_len;
const multi_prong_infos_start = payloads_end;
payloads_end += multi_cases_len;
const multi_case_items_lens_start = payloads_end;
payloads_end += multi_cases_len;
const multi_case_ranges_lens_start = if (any_ranges) blk: {
const multi_case_ranges_lens_start = payloads_end;
payloads_end += multi_cases_len;
break :blk multi_case_ranges_lens_start;
} else undefined;
const scalar_item_infos_start = payloads_end;
payloads_end += scalar_cases_len;
const multi_items_infos_start = payloads_end;
payloads_end += total_items_len - scalar_cases_len + 2 * total_ranges_len;
const bodies_start = payloads_end;
try payloads.resize(gpa, bodies_start);
defer astgen.scratch.items.len = scratch_top;
var non_err_prong_body_start: u32 = undefined;
var else_prong_body_start: u32 = undefined;
var non_err_info: Zir.Inst.SwitchBlock.ProngInfo.NonErr = undefined;
var else_info: Zir.Inst.SwitchBlock.ProngInfo.Else = undefined;
var block_scope = parent_gz.makeSubBlock(scope);
block_scope.instructions_top = GenZir.unstacked_top;
const operand_ri: ResultInfo = .{
.rl = loc: {
if (any_payload_is_ref) break :loc .ref;
if (needs_non_err_handling and non_err_is_ref == .yes) break :loc .ref;
if (needs_non_err_handling and non_err_is_ref == .yes_const) break :loc .ref_const;
break :loc .none;
},
.ctx = if (do_err_trace) .error_handling_expr else .none,
};
astgen.advanceSourceCursorToNode(operand_node);
const operand_lc: LineColumn = .{ astgen.source_line - parent_gz.decl_line, astgen.source_column };
const raw_operand: Zir.Inst.Ref = if (needs_non_err_handling)
try reachableExpr(parent_gz, scope, operand_ri, operand_node, switch_node)
else
try expr(parent_gz, scope, operand_ri, operand_node);
try emitDbgStmtForceCurrentIndex(parent_gz, operand_lc);
const switch_tag: Zir.Inst.Tag = switch (non_err) {
.none, .peer_break_target => if (any_payload_is_ref) .switch_block_ref else .switch_block,
.@"if", .@"catch" => .switch_block_err_union,
};
const switch_block = try parent_gz.makeBlockInst(switch_tag, switch_node);
switch (non_err) {
.none => {
if (switch_full.label_token != null) {
block_scope.break_target = switch_block;
}
block_scope.setBreakResultInfo(block_ri);
},
.@"catch", .@"if" => {
assert(switch_full.label_token == null);
block_scope.setBreakResultInfo(block_ri);
},
.peer_break_target => |peer_break_target| {
// want to generate this:
// ```
// %1 = block({
// %2 = is_non_err(%operand)
// %3 = condbr(%2, {
// %4 = err_union_payload_unsafe(%operand)
// %5 = break(%1, result) // targets enclosing `block`
// }, {
// %6 = err_union_code(%operand)
// %7 = switch_block(%6,
// { ... } => {
// %8 = break(%1, result) // targets enclosing `block`
// },
// { ... } => {
// %9 = switch_continue(%7, result) // targets `switch_block`
// },
// )
// %10 = break(%1, @void_value)
// })
// })
// ```
// to ensure that the non-err case and the switch are only peers when
// breaking from either, but not when continuing the switch. We use
// this lowering to avoiding a rather complex special case in Sema.
assert(switch_full.label_token != null);
assert(.block == astgen.instructions.items(.tag)[@backingInt(peer_break_target.block_inst)]);
block_scope.break_target = peer_break_target.block_inst;
block_scope.setBreakResultInfo(peer_break_target.block_ri);
},
}
// `... |err| switch (err) { else => |e| { ... } }` // `err` and `e`
// `... => |payload, tag| { ... }` // `payload` and `tag`
// and result types:
// `foo => { ... }` // `foo` needs a result type
// `... => continue :sw val` // `val` needs a result type
// Some observations:
// - If we just use the switch inst itself we don't need a placeholder!
// - We can always tell for sure whether a capture exists. We also know
// that its existence implies that it has to be used.
// - We can't know whether there are any `continue`s before analyzing all
// prong bodies. At that point we already need a result location. We do
// know whether there even *could* be any though by looking for a label.
// - Sema wants a result location in `zirSwitchContinue`. If that's the
// switch inst itself, there's no need to look at the switch inst data.
// Some conclusions:
// - We should use the switch inst as the continue result location if needed.
// - If we need more insts for captures and our switch inst is already used
// for something else, we start creating placeholder insts.
// Prong items use the switch block instruction as their result type.
// No other components of the switch statement are in scope while they are
// being resolved, so this is never a problem.
const item_ri: ResultInfo = .{ .rl = .{ .coerced_ty = switch_block.toRef() } };
var switch_block_inst_is_occupied: bool = false;
if (switch_full.label_token) |label_token| {
block_scope.label = .{ .token = label_token };
block_scope.continue_target = .{ .switch_continue = switch_block };
block_scope.continue_result_info = .{
.rl = if (any_payload_is_ref)
.{ .ref_coerced_ty = switch_block.toRef() }
else
.{ .coerced_ty = switch_block.toRef() },
};
switch_block_inst_is_occupied = true;
}
if (needs_non_err_handling) {
// switch operand. This is always ok as its switch can never have a label.
assert(!switch_block_inst_is_occupied);
switch_block_inst_is_occupied = true;
}
const payload_capture_inst, const payload_capture_inst_is_placeholder = inst: {
if (!any_has_payload_capture) break :inst .{ undefined, false };
if (!switch_block_inst_is_occupied) {
switch_block_inst_is_occupied = true;
break :inst .{ switch_block, false };
}
break :inst .{ try astgen.appendPlaceholder(), true };
};
const tag_capture_inst, const tag_capture_inst_is_placeholder = inst: {
if (!any_has_tag_capture) break :inst .{ undefined, false };
if (!switch_block_inst_is_occupied) {
switch_block_inst_is_occupied = true;
break :inst .{ switch_block, false };
}
break :inst .{ try astgen.appendPlaceholder(), true };
};
var prong_body_extra_insts_buf: [3]Zir.Inst.Index = undefined;
const prong_body_extra_insts: []const Zir.Inst.Index = extra_insts: {
var extra_insts: std.ArrayList(Zir.Inst.Index) = .initBuffer(&prong_body_extra_insts_buf);
if (switch_block_inst_is_occupied) extra_insts.appendAssumeCapacity(switch_block);
if (payload_capture_inst_is_placeholder) extra_insts.appendAssumeCapacity(payload_capture_inst);
if (tag_capture_inst_is_placeholder) extra_insts.appendAssumeCapacity(tag_capture_inst);
break :extra_insts extra_insts.items;
};
const switch_operand, const catch_or_if_operand = if (needs_non_err_handling)
.{ switch_block.toRef(), raw_operand }
else
.{ raw_operand, undefined };
var scratch_scope = parent_gz.makeSubBlock(&block_scope.base);
scratch_scope.instructions_top = GenZir.unstacked_top;
non_err_body: {
if (!needs_non_err_handling) break :non_err_body;
scratch_scope.instructions_top = parent_gz.instructions.items.len;
defer scratch_scope.unstack();
// payload as the actual switch statement isn't even in scope yet.
const non_err_payload_inst = switch_block;
var non_err_capture: Zir.Inst.SwitchBlock.ProngInfo.Capture = .none;
switch (non_err) {
.none, .peer_break_target => unreachable,
.@"catch" => {
// it to `break` from the entire `switch_block_err_union`.
non_err_capture = if (non_err_is_ref != .no) .by_ref else .by_val;
const then_result = switch (ri.rl) {
.ref, .ref_const, .ref_coerced_ty => non_err_payload_inst.toRef(),
else => try rvalue(
&scratch_scope,
block_scope.break_result_info,
non_err_payload_inst.toRef(),
catch_or_if_node,
),
};
_ = try scratch_scope.addBreakWithSrcNode(
.@"break",
switch_block,
then_result,
catch_or_if_node,
);
},
.@"if" => |if_full| {
var payload_val_scope: Scope.LocalVal = undefined;
const then_node = if_full.ast.then_expr;
const then_sub_scope: *Scope = scope: {
if (if_full.payload_token) |payload_token| {
const ident_token = payload_token + @intFromBool(non_err_is_ref != .no);
const ident_name = try astgen.identAsString(ident_token);
const ident_name_str = tree.tokenSlice(ident_token);
if (mem.eql(u8, "_", ident_name_str)) {
if (non_err_is_ref != .no) return astgen.failTok(payload_token, "pointer modifier invalid on discard", .{});
break :scope &scratch_scope.base;
}
non_err_capture = if (non_err_is_ref != .no) .by_ref else .by_val;
try astgen.detectLocalShadowing(&scratch_scope.base, ident_name, ident_token, ident_name_str, .capture);
payload_val_scope = .{
.parent = &scratch_scope.base,
.gen_zir = &scratch_scope,
.name = ident_name,
.inst = non_err_payload_inst.toRef(),
.token_src = ident_token,
.id_cat = .capture,
};
try scratch_scope.addDbgVar(.dbg_var_val, ident_name, non_err_payload_inst.toRef());
break :scope &payload_val_scope.base;
} else {
_ = try scratch_scope.addUnNode(
.ensure_err_union_payload_void,
catch_or_if_operand,
catch_or_if_node,
);
break :scope &scratch_scope.base;
}
};
const then_result = try fullBodyExpr(&scratch_scope, then_sub_scope, block_scope.break_result_info, then_node, .allow_branch_hint);
try checkUsed(parent_gz, &scratch_scope.base, then_sub_scope);
if (!scratch_scope.endsWithNoReturn()) {
_ = try scratch_scope.addBreakWithSrcNode(.@"break", switch_block, then_result, then_node);
}
},
}
const body_slice = scratch_scope.instructionsSlice();
const body_start: u32 = @intCast(payloads.items.len);
const body_len = astgen.countBodyLenAfterFixupsExtraRefs(body_slice, &.{non_err_payload_inst});
try payloads.ensureUnusedCapacity(gpa, body_len);
astgen.appendBodyWithFixupsExtraRefsArrayList(payloads, body_slice, &.{non_err_payload_inst});
non_err_prong_body_start = body_start;
non_err_info = .{
.body_len = @intCast(body_len),
.capture = non_err_capture,
.operand_is_ref = non_err_is_ref != .no,
};
}
var multi_case_index: u32 = 0;
var scalar_case_index: u32 = 0;
var multi_item_offset: usize = 0;
for (case_nodes) |case_node| {
const case = tree.fullSwitchCase(case_node).?;
const ranges_len: u32 = if (any_ranges) blk: {
var ranges_len: u32 = 0;
for (case.ast.values) |value| {
ranges_len += @intFromBool(tree.nodeTag(value) == .switch_range);
}
break :blk ranges_len;
} else 0;
const items_len: u32 = @intCast(case.ast.values.len - ranges_len);
const is_multi_case = items_len > 1 or ranges_len > 0;
var item_i: usize = 0;
var range_i: usize = 0;
for (case.ast.values) |value| {
const is_range = tree.nodeTag(value) == .switch_range;
const range: [2]Ast.Node.Index = if (is_range) tree.nodeData(value).node_and_node else undefined;
const nodes: []const Ast.Node.Index = if (is_range) &range else &.{value};
for (nodes) |item| {
// manually to save space since they are very commonly used as
// switch case items.
const body_start: u32 = @intCast(payloads.items.len);
const item_info: Zir.Inst.SwitchBlock.ItemInfo = blk: switch (tree.nodeTag(item)) {
.enum_literal => {
const str_index = try astgen.identAsString(tree.nodeMainToken(item));
break :blk .wrap(.{ .enum_literal = str_index });
},
.error_value => {
const ident_token = tree.nodeMainToken(item) + 2;
const str_index = try astgen.identAsString(ident_token);
break :blk .wrap(.{ .error_value = str_index });
},
else => if (value.toOptional() == underscore_node) {
break :blk .wrap(.under);
} else {
scratch_scope.instructions_top = parent_gz.instructions.items.len;
defer scratch_scope.unstack();
const item_result = try fullBodyExpr(&scratch_scope, scope, item_ri, item, .normal);
if (!scratch_scope.endsWithNoReturn()) {
_ = try scratch_scope.addBreakWithSrcNode(.break_inline, switch_block, item_result, item);
}
const item_slice = scratch_scope.instructionsSlice();
const body_len = astgen.countBodyLenAfterFixupsExtraRefs(item_slice, &.{switch_block});
try payloads.ensureUnusedCapacity(gpa, body_len);
astgen.appendBodyWithFixupsExtraRefsArrayList(payloads, item_slice, &.{switch_block});
break :blk .wrap(.{ .body_len = body_len });
},
};
if (is_multi_case) {
if (is_range) {
const offset = multi_item_offset + items_len + range_i;
payloads.items[multi_item_body_table + offset] = body_start;
payloads.items[multi_items_infos_start + offset] = @bitCast(item_info);
range_i += 1;
} else {
const offset = multi_item_offset + item_i;
payloads.items[multi_item_body_table + offset] = body_start;
payloads.items[multi_items_infos_start + offset] = @bitCast(item_info);
item_i += 1;
}
} else {
payloads.items[scalar_body_table + scalar_case_index] = body_start;
payloads.items[scalar_item_infos_start + scalar_case_index] = @bitCast(item_info);
}
}
}
if (is_multi_case) {
assert(item_i == items_len and range_i == 2 * ranges_len);
payloads.items[multi_case_items_lens_start + multi_case_index] = items_len;
if (any_ranges) {
payloads.items[multi_case_ranges_lens_start + multi_case_index] = ranges_len;
}
multi_item_offset += items_len + 2 * ranges_len;
}
var dbg_var_payload_name: Zir.NullTerminatedString = .empty;
var dbg_var_payload_inst: Zir.Inst.Ref = undefined;
var dbg_var_tag_name: Zir.NullTerminatedString = .empty;
var dbg_var_tag_inst: Zir.Inst.Ref = undefined;
var has_tag_capture = false;
var err_capture_scope: Scope.LocalVal = undefined;
var payload_capture_scope: Scope.LocalVal = undefined;
var tag_capture_scope: Scope.LocalVal = undefined;
var capture: Zir.Inst.SwitchBlock.ProngInfo.Capture = .none;
// Potential captures are:
// - for regular switch: payload and tag
// - for error switch: switch operand and payload
const prong_body_scope: *Scope = scope: {
const switch_scope: *Scope = if (needs_non_err_handling) blk: {
err_capture_scope = .{
.parent = &scratch_scope.base,
.gen_zir = &scratch_scope,
.name = err_capture_name,
.inst = switch_operand,
.token_src = err_token,
.id_cat = .capture,
};
break :blk &err_capture_scope.base;
} else &scratch_scope.base;
const payload_token = case.payload_token orelse break :scope switch_scope;
const capture_is_ref = tree.tokenTag(payload_token) == .asterisk;
const ident = payload_token + @intFromBool(capture_is_ref);
capture = if (capture_is_ref) .by_ref else .by_val;
const ident_slice = tree.tokenSlice(ident);
var payload_sub_scope: *Scope = undefined;
if (mem.eql(u8, ident_slice, "_")) {
if (capture_is_ref) {
return astgen.failTok(payload_token, "pointer modifier invalid on discard", .{});
}
capture = .none;
payload_sub_scope = switch_scope;
} else {
const capture_name = try astgen.identAsString(ident);
try astgen.detectLocalShadowing(switch_scope, capture_name, ident, ident_slice, .capture);
payload_capture_scope = .{
.parent = switch_scope,
.gen_zir = &scratch_scope,
.name = capture_name,
.inst = payload_capture_inst.toRef(),
.token_src = ident,
.id_cat = .capture,
};
dbg_var_payload_name = payload_capture_scope.name;
dbg_var_payload_inst = payload_capture_scope.inst;
payload_sub_scope = &payload_capture_scope.base;
}
if (is_err_switch and capture == .by_ref) {
return astgen.failTok(ident, "error set cannot be captured by reference", .{});
}
const tag_token = if (tree.tokenTag(ident + 1) == .comma) blk: {
break :blk ident + 2;
} else if (capture == .none) {
// whether the tag capture is discarded is handled below
return astgen.failTok(payload_token, "discard of capture; omit it instead", .{});
} else break :scope payload_sub_scope;
const tag_slice = tree.tokenSlice(tag_token);
if (mem.eql(u8, tag_slice, "_")) {
return astgen.failTok(tag_token, "discard of tag capture; omit it instead", .{});
}
const tag_name = try astgen.identAsString(tag_token);
try astgen.detectLocalShadowing(payload_sub_scope, tag_name, tag_token, tag_slice, .@"switch tag capture");
assert(any_has_tag_capture);
has_tag_capture = true;
if (is_err_switch) {
return astgen.failTok(tag_token, "cannot capture tag of error union", .{});
}
tag_capture_scope = .{
.parent = payload_sub_scope,
.gen_zir = &scratch_scope,
.name = tag_name,
.inst = tag_capture_inst.toRef(),
.token_src = tag_token,
.id_cat = .@"switch tag capture",
};
dbg_var_tag_name = tag_capture_scope.name;
dbg_var_tag_inst = tag_capture_scope.inst;
break :scope &tag_capture_scope.base;
};
if (capture != .none) assert(any_has_payload_capture);
if (is_err_switch) {
assert(!any_payload_is_ref);
assert(!any_has_tag_capture);
}
prong_body: {
scratch_scope.instructions_top = parent_gz.instructions.items.len;
defer scratch_scope.unstack();
if (dbg_var_payload_name != .empty) {
try scratch_scope.addDbgVar(.dbg_var_val, dbg_var_payload_name, dbg_var_payload_inst);
}
if (dbg_var_tag_name != .empty) {
try scratch_scope.addDbgVar(.dbg_var_val, dbg_var_tag_name, dbg_var_tag_inst);
}
if (do_err_trace and nodeMayAppendToErrorTrace(tree, operand_node)) {
_ = try scratch_scope.addSaveErrRetIndex(.always);
}
const target_expr_node = case.ast.target_expr;
const case_result = try fullBodyExpr(&scratch_scope, prong_body_scope, block_scope.break_result_info, target_expr_node, .allow_branch_hint);
if (needs_non_err_handling) {
// positive, that being the switch operand itself!
try checkUsed(parent_gz, &err_capture_scope.base, prong_body_scope);
} else {
try checkUsed(parent_gz, &scratch_scope.base, prong_body_scope);
}
if (!scratch_scope.endsWithNoReturn()) {
if (do_err_trace) {
try restoreErrRetIndex(
&scratch_scope,
.{ .block = switch_block },
block_scope.break_result_info,
target_expr_node,
case_result,
);
}
_ = try scratch_scope.addBreakWithSrcNode(.@"break", switch_block, case_result, target_expr_node);
}
const body_slice = scratch_scope.instructionsSlice();
const body_start: u32 = @intCast(payloads.items.len);
const body_len = astgen.countBodyLenAfterFixupsExtraRefs(body_slice, prong_body_extra_insts);
try payloads.ensureUnusedCapacity(gpa, body_len);
astgen.appendBodyWithFixupsExtraRefsArrayList(payloads, body_slice, prong_body_extra_insts);
if (case_node.toOptional() == else_case_node) {
assert(case.ast.values.len == 0);
// "unreachable else prong" error so that certain generic code
// patterns don't trigger it. We do that for these bodies:
// `else => unreachable,`
// `else => return,`
// `else => |e| return e,` (where `e` is any identifier)
const is_simple_noreturn = switch (tree.nodeTag(target_expr_node)) {
.unreachable_literal => true,
.@"return" => simple_noreturn: {
const retval_node = tree.nodeData(target_expr_node).opt_node.unwrap() orelse {
break :simple_noreturn true;
};
if (capture != .by_val) break :simple_noreturn false;
if (tree.nodeTag(retval_node) != .identifier) break :simple_noreturn false;
const payload_name = try astgen.identAsString(case.payload_token.?);
const retval_name = try astgen.identAsString(tree.nodeMainToken(retval_node));
break :simple_noreturn payload_name == retval_name;
},
else => false,
};
else_info = .{
.body_len = @intCast(body_len),
.capture = capture,
.is_inline = case.inline_token != null,
.has_tag_capture = has_tag_capture,
.is_simple_noreturn = is_simple_noreturn,
};
else_prong_body_start = body_start;
break :prong_body;
}
// being switched on if their body is `=> comptime unreachable,`.
const is_comptime_unreach = comptime_unreach: {
if (tree.nodeTag(target_expr_node) != .@"comptime") break :comptime_unreach false;
const comptime_node = tree.nodeData(target_expr_node).node;
break :comptime_unreach tree.nodeTag(comptime_node) == .unreachable_literal;
};
const prong_info: Zir.Inst.SwitchBlock.ProngInfo = .{
.body_len = @intCast(body_len),
.capture = capture,
.is_inline = case.inline_token != null,
.has_tag_capture = has_tag_capture,
.is_comptime_unreach = is_comptime_unreach,
};
if (is_multi_case) {
payloads.items[multi_prong_body_table + multi_case_index] = body_start;
payloads.items[multi_prong_infos_start + multi_case_index] = @bitCast(prong_info);
multi_case_index += 1;
} else {
payloads.items[scalar_prong_infos_start + scalar_case_index] = @bitCast(prong_info);
scalar_case_index += 1;
}
}
}
assert(scalar_case_index + multi_case_index + @intFromBool(has_else) == case_nodes.len);
assert(multi_items_infos_start + multi_item_offset == bodies_start);
if (switch_full.label_token) |label_token| if (!block_scope.label.?.used) {
try astgen.appendErrorTok(label_token, "unused switch label", .{});
};
try parent_gz.instructions.append(gpa, switch_block);
// by copying our bodies from `payloads` to `extra`, this time in the order
// expected by ZIR consumers.
try astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.SwitchBlock).@"struct".field_names.len +
@intFromBool(multi_cases_len > 0) +
@intFromBool(payload_capture_inst_is_placeholder) +
@intFromBool(tag_capture_inst_is_placeholder) +
@intFromBool(needs_non_err_handling) +
@intFromBool(needs_non_err_handling) +
@intFromBool(has_else) +
payloads.items.len - body_table_end);
// singular pieces of data
const zir_payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.SwitchBlock{
.raw_operand = raw_operand,
.bits = .{
.has_multi_cases = multi_cases_len > 0,
.any_ranges = any_ranges,
.has_else = has_else,
.has_under = has_under,
.has_continue = switch_full.label_token != null and block_scope.label.?.used_for_continue,
.any_maybe_runtime_capture = any_maybe_runtime_capture,
.payload_capture_inst_is_placeholder = payload_capture_inst_is_placeholder,
.tag_capture_inst_is_placeholder = tag_capture_inst_is_placeholder,
.scalar_cases_len = @intCast(scalar_cases_len),
},
});
astgen.instructions.items(.data)[@backingInt(switch_block)].pl_node.payload_index = zir_payload_index;
if (multi_cases_len > 0) astgen.extra.appendAssumeCapacity(multi_cases_len);
if (payload_capture_inst_is_placeholder) astgen.extra.appendAssumeCapacity(@backingInt(payload_capture_inst));
if (tag_capture_inst_is_placeholder) astgen.extra.appendAssumeCapacity(@backingInt(tag_capture_inst));
if (needs_non_err_handling) {
const catch_or_if_src_node_offset = parent_gz.nodeIndexToRelative(catch_or_if_node);
astgen.extra.appendAssumeCapacity(@bitCast(@backingInt(catch_or_if_src_node_offset)));
astgen.extra.appendAssumeCapacity(@bitCast(non_err_info));
}
if (has_else) astgen.extra.appendAssumeCapacity(@bitCast(else_info));
const extra_payloads_start = astgen.extra.items.len;
astgen.extra.appendSliceAssumeCapacity(payloads.items[body_table_end..bodies_start]);
if (needs_non_err_handling) {
const body = payloads.items[non_err_prong_body_start..][0..non_err_info.body_len];
astgen.extra.appendSliceAssumeCapacity(body);
}
if (has_else) {
const body = payloads.items[else_prong_body_start..][0..else_info.body_len];
astgen.extra.appendSliceAssumeCapacity(body);
}
for (0..scalar_cases_len) |scalar_i| {
const item_info: Zir.Inst.SwitchBlock.ItemInfo = @bitCast(payloads.items[scalar_item_infos_start + scalar_i]);
const item_body_start = payloads.items[scalar_body_table + scalar_i];
const item_body = payloads.items[item_body_start..][0 .. item_info.bodyLen() orelse 0];
const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(payloads.items[scalar_prong_infos_start + scalar_i]);
const prong_body_start = item_body_start + item_body.len;
const prong_body = payloads.items[prong_body_start..][0..prong_info.body_len];
astgen.extra.appendSliceAssumeCapacity(prong_body);
astgen.extra.appendSliceAssumeCapacity(item_body);
}
var multi_item_i: usize = 0;
for (0..multi_cases_len) |multi_i| {
const prong_body_start = payloads.items[multi_prong_body_table + multi_i];
const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(payloads.items[multi_prong_infos_start + multi_i]);
const prong_body = payloads.items[prong_body_start..][0..prong_info.body_len];
astgen.extra.appendSliceAssumeCapacity(prong_body);
const items_len = payloads.items[multi_case_items_lens_start + multi_i];
const ranges_len = if (any_ranges) ranges_len: {
break :ranges_len payloads.items[multi_case_ranges_lens_start + multi_i];
} else 0;
// don't have to differentiate between items and ranges here.
for (0..items_len + 2 * ranges_len) |_| {
const item_info: Zir.Inst.SwitchBlock.ItemInfo = @bitCast(payloads.items[multi_items_infos_start + multi_item_i]);
if (item_info.bodyLen()) |body_len| {
const body_start = payloads.items[multi_item_body_table + multi_item_i];
const body = payloads.items[body_start..][0..body_len];
astgen.extra.appendSliceAssumeCapacity(body);
}
multi_item_i += 1;
}
}
assert(multi_item_i == total_items_len + 2 * total_ranges_len - scalar_cases_len);
assert(astgen.extra.items.len - extra_payloads_start == payloads.items.len - body_table_end);
if (need_result_rvalue) {
return rvalue(parent_gz, ri, switch_block.toRef(), switch_node);
} else {
return switch_block.toRef();
}
}