authorgravatar for justus@klausecker.deJustus Klausecker <justus@klausecker.de> 2026-01-07 17:07:34+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-01-11 11:37:16+00:00
log5b00e24b6e28c24d7fa2417e9b7b88e13e75741e
tree2439a96732775042894542536da999eecd1878d7
parent00d4f3c00188e2b1fcb2669ba6346831787828c2
signaturelock-open Commit is signed but in an unrecognized format.

frontend: rework switch ZIR

Moved to a more linear layout which lends itself well to exposing an iterator. Consumers of this iterator now just have to keep track of an index into a homogenous sequence of bodies. The new ZIR layout also enables giving switch prong items result locations by storing the bodies of all items inside of the switch encoding itself. There are some deliberate exceptions to this: enum literals and error values are directly encoded as strings and number literals are resolved to comptime values outside of the switch block. These special encodings exist to save space and can easily be resolved during semantic analysis. This commit also re-implements `AstGen` and `print_zir` for switch based on the new layout and adds some additional information to the ZIR text repr. Notably `switchExprErrUnion` has been merged into `switchExpr` to reduce code duplication. The rules around allowing an unreachable `else` prong in error switches are also refined by this commit, and enforced properly based on the actual AST. The special cases are listed exhaustively below: `else => unreachable,` `else => return,` `else => |e| return e,` (where `e` is any identifier) Additionally `{...} => comptime unreachable,` prongs are marked to support future features (refer to next couple of commits). Also fixes 'value with comptime-only type depends on runtime control flow' error for labeled error switch statements by surrounding the entire expr with a common block to break to (see previous commits for details).

3 files changed, 1423 insertions(+), 1382 deletions(-)

lib/std/zig/AstGen.zig+830-807
......@@ -115,7 +115,6 @@ fn setExtra(astgen: *AstGen, index: usize, extra: anytype) void {
115115 Zir.Inst.Call.Flags,
116116 Zir.Inst.BuiltinCall.Flags,
117117 Zir.Inst.SwitchBlock.Bits,
118 Zir.Inst.SwitchBlockErrUnion.Bits,
119118 Zir.Inst.FuncFancy.Bits,
120119 Zir.Inst.Param.Type,
121120 Zir.Inst.Func.RetTy,
......@@ -858,11 +857,11 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE
858857 no_switch_on_err: {
859858 const error_token = if_full.error_token orelse break :no_switch_on_err;
860859 const else_node = if_full.ast.else_expr.unwrap() orelse break :no_switch_on_err;
861 const full_switch = tree.fullSwitch(else_node) orelse break :no_switch_on_err;
862 if (full_switch.label_token != null) break :no_switch_on_err;
863 if (tree.nodeTag(full_switch.ast.condition) != .identifier) break :no_switch_on_err;
864 if (!mem.eql(u8, tree.tokenSlice(error_token), tree.tokenSlice(tree.nodeMainToken(full_switch.ast.condition)))) break :no_switch_on_err;
865 return switchExprErrUnion(gz, scope, ri.br(), node, .@"if");
860 const switch_full = tree.fullSwitch(else_node) orelse break :no_switch_on_err;
861 if (switch_full.label_token != null) break :no_switch_on_err; // handled in `ifExpr`
862 if (tree.nodeTag(switch_full.ast.condition) != .identifier) break :no_switch_on_err;
863 if (!try astgen.tokenIdentEql(error_token, tree.nodeMainToken(switch_full.ast.condition))) break :no_switch_on_err;
864 return switchExpr(gz, scope, ri.br(), node, switch_full, .{ .@"if" = if_full });
866865 }
867866 return ifExpr(gz, scope, ri.br(), node, if_full);
868867 },
......@@ -1024,11 +1023,11 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE
10241023 null;
10251024 no_switch_on_err: {
10261025 const capture_token = payload_token orelse break :no_switch_on_err;
1027 const full_switch = tree.fullSwitch(tree.nodeData(node).node_and_node[1]) orelse break :no_switch_on_err;
1028 if (full_switch.label_token != null) break :no_switch_on_err;
1029 if (tree.nodeTag(full_switch.ast.condition) != .identifier) break :no_switch_on_err;
1030 if (!mem.eql(u8, tree.tokenSlice(capture_token), tree.tokenSlice(tree.nodeMainToken(full_switch.ast.condition)))) break :no_switch_on_err;
1031 return switchExprErrUnion(gz, scope, ri.br(), node, .@"catch");
1026 const switch_full = tree.fullSwitch(tree.nodeData(node).node_and_node[1]) orelse break :no_switch_on_err;
1027 if (switch_full.label_token != null) break :no_switch_on_err; // handled in `orelseCatchExpr`
1028 if (tree.nodeTag(switch_full.ast.condition) != .identifier) break :no_switch_on_err;
1029 if (!try astgen.tokenIdentEql(capture_token, tree.nodeMainToken(switch_full.ast.condition))) break :no_switch_on_err;
1030 return switchExpr(gz, scope, ri.br(), node, switch_full, .@"catch");
10321031 }
10331032 switch (ri.rl) {
10341033 .ref, .ref_coerced_ty => return orelseCatchExpr(
......@@ -1108,7 +1107,7 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE
11081107 .error_set_decl => return errorSetDecl(gz, ri, node),
11091108 .array_access => return arrayAccess(gz, scope, ri, node),
11101109 .@"comptime" => return comptimeExprAst(gz, scope, ri, node),
1111 .@"switch", .switch_comma => return switchExpr(gz, scope, ri.br(), node, tree.fullSwitch(node).?),
1110 .@"switch", .switch_comma => return switchExpr(gz, scope, ri.br(), node, tree.fullSwitch(node).?, .none),
11121111
11131112 .@"nosuspend" => return nosuspendExpr(gz, scope, ri, node),
11141113 .@"suspend" => return suspendExpr(gz, scope, node),
......@@ -3134,14 +3133,7 @@ fn deferStmt(
31343133 }
31353134 const remapped_err_code: Zir.Inst.Index = @enumFromInt(gz.astgen.instructions.len);
31363135 opt_remapped_err_code = remapped_err_code.toOptional();
3137 try gz.astgen.instructions.append(gz.astgen.gpa, .{
3138 .tag = .extended,
3139 .data = .{ .extended = .{
3140 .opcode = .value_placeholder,
3141 .small = undefined,
3142 .operand = undefined,
3143 } },
3144 });
3136 _ = try gz.astgen.appendPlaceholder();
31453137 const remapped_err_code_ref = remapped_err_code.toRef();
31463138 local_val_scope = .{
31473139 .parent = &defer_gen.base,
......@@ -6115,7 +6107,30 @@ fn orelseCatchExpr(
61156107 break :blk &err_val_scope.base;
61166108 };
61176109
6118 const else_result = try fullBodyExpr(&else_scope, else_sub_scope, block_scope.break_result_info, rhs, .allow_branch_hint);
6110 const else_result = else_result: {
6111 if (tree.fullSwitch(rhs)) |switch_full| no_switch_on_err: {
6112 if (tree.nodeTag(node) != .@"catch") break :no_switch_on_err;
6113 const catch_token = tree.nodeMainToken(node);
6114 const capture_token = if (tree.tokenTag(catch_token + 1) == .pipe) token: {
6115 break :token catch_token + 2;
6116 } else break :no_switch_on_err;
6117 if (switch_full.label_token == null) break :no_switch_on_err; // must use `switchExpr` with `non_err = .@"if"`
6118 if (tree.nodeTag(switch_full.ast.condition) != .identifier) break :no_switch_on_err;
6119 if (!try astgen.tokenIdentEql(capture_token, tree.nodeMainToken(switch_full.ast.condition))) break :no_switch_on_err;
6120 break :else_result try switchExpr(
6121 &else_scope,
6122 else_sub_scope,
6123 block_scope.break_result_info,
6124 rhs,
6125 switch_full,
6126 .{ .peer_break_target = .{
6127 .block_inst = block,
6128 .block_ri = block_ri,
6129 } },
6130 );
6131 }
6132 break :else_result try fullBodyExpr(&else_scope, else_sub_scope, block_scope.break_result_info, rhs, .allow_branch_hint);
6133 };
61196134 if (!else_scope.endsWithNoReturn()) {
61206135 // As our last action before the break, "pop" the error trace if needed
61216136 if (do_err_trace)
......@@ -6468,7 +6483,26 @@ fn ifExpr(
64686483 break :s &else_scope.base;
64696484 }
64706485 };
6471 const else_result = try fullBodyExpr(&else_scope, sub_scope, block_scope.break_result_info, else_node, .allow_branch_hint);
6486 const else_result = else_result: {
6487 if (tree.fullSwitch(else_node)) |switch_full| no_switch_on_err: {
6488 const error_token = if_full.error_token orelse break :no_switch_on_err;
6489 if (switch_full.label_token == null) break :no_switch_on_err; // must use `switchExpr` with `non_err = .@"if"`
6490 if (tree.nodeTag(switch_full.ast.condition) != .identifier) break :no_switch_on_err;
6491 if (!try astgen.tokenIdentEql(error_token, tree.nodeMainToken(switch_full.ast.condition))) break :no_switch_on_err;
6492 break :else_result try switchExpr(
6493 &else_scope,
6494 sub_scope,
6495 block_scope.break_result_info,
6496 else_node,
6497 switch_full,
6498 .{ .peer_break_target = .{
6499 .block_inst = block,
6500 .block_ri = block_ri,
6501 } },
6502 );
6503 }
6504 break :else_result try fullBodyExpr(&else_scope, sub_scope, block_scope.break_result_info, else_node, .allow_branch_hint);
6505 };
64726506 if (!else_scope.endsWithNoReturn()) {
64736507 // As our last action before the break, "pop" the error trace if needed
64746508 if (do_err_trace)
......@@ -7117,568 +7151,156 @@ fn forExpr(
71177151 return result;
71187152}
71197153
7120fn switchExprErrUnion(
7154const SwitchNonErr = union(enum) {
7155 /// A regular switch expression.
7156 /// Emits `switch_block[_ref]`.
7157 none,
7158 /// `eu catch |err| switch (err) { ... }`
7159 ///
7160 /// `switch` must not be labeled.
7161 /// Emits `switch_block_err_union`.
7162 @"catch",
7163 /// `if (eu) |payload| { ... } else |err| switch (err) { ... }`
7164 ///
7165 /// `switch` must not be labeled.
7166 /// Emits `switch_block_err_union`.
7167 @"if": Ast.full.If,
7168 /// `eu catch |err| label: switch (err) { ... }`
7169 /// `if (eu) |payload| { ... } else |err| label: switch (err) { ... }`
7170 ///
7171 /// `switch` must be labeled.
7172 /// Emits a `condbr` on the non-error body and a regular switch, though the
7173 /// non-error prong and all `break`s from switch prongs are peers.
7174 /// Exists to avoid a rather complex special case of `switch_block_err_union`.
7175 peer_break_target: struct {
7176 /// Refers to the enclosing block of the entire switch-on-err expression.
7177 block_inst: Zir.Inst.Index,
7178 /// Belongs to `block_inst`.
7179 block_ri: ResultInfo,
7180 },
7181};
7182
7183fn switchExpr(
71217184 parent_gz: *GenZir,
71227185 scope: *Scope,
71237186 ri: ResultInfo,
7124 catch_or_if_node: Ast.Node.Index,
7125 node_ty: enum { @"catch", @"if" },
7187 node: Ast.Node.Index,
7188 switch_full: Ast.full.Switch,
7189 non_err: SwitchNonErr,
71267190) InnerError!Zir.Inst.Ref {
71277191 const astgen = parent_gz.astgen;
71287192 const gpa = astgen.gpa;
71297193 const tree = astgen.tree;
71307194
7131 const if_full = switch (node_ty) {
7132 .@"catch" => undefined,
7133 .@"if" => tree.fullIf(catch_or_if_node).?,
7134 };
7135
7136 const switch_node, const operand_node, const error_payload = switch (node_ty) {
7195 const switch_node, const operand_node, const err_token = switch (non_err) {
7196 .none, .peer_break_target => .{
7197 node,
7198 switch_full.ast.condition,
7199 undefined,
7200 },
71377201 .@"catch" => .{
7138 tree.nodeData(catch_or_if_node).node_and_node[1],
7139 tree.nodeData(catch_or_if_node).node_and_node[0],
7140 tree.nodeMainToken(catch_or_if_node) + 2,
7202 tree.nodeData(node).node_and_node[1],
7203 tree.nodeData(node).node_and_node[0],
7204 tree.nodeMainToken(node) + 2,
71417205 },
7142 .@"if" => .{
7206 .@"if" => |if_full| .{
71437207 if_full.ast.else_expr.unwrap().?,
71447208 if_full.ast.cond_expr,
71457209 if_full.error_token.?,
71467210 },
71477211 };
7148 const switch_full = tree.fullSwitch(switch_node).?;
7212 const case_nodes = switch_full.ast.cases;
7213
7214 const is_err_switch = non_err != .none;
7215 const needs_non_err_handling = switch (non_err) {
7216 .none => false,
7217 .peer_break_target => false, // handled by parent expression
7218 .@"catch", .@"if" => true,
7219 };
71497220
7150 const do_err_trace = astgen.fn_block != null;
7151 const need_rl = astgen.nodes_need_rl.contains(catch_or_if_node);
7221 const need_rl = astgen.nodes_need_rl.contains(node);
71527222 const block_ri: ResultInfo = if (need_rl) ri else .{
71537223 .rl = switch (ri.rl) {
7154 .ptr => .{ .ty = (try ri.rl.resultType(parent_gz, catch_or_if_node)).? },
7224 .ptr => .{ .ty = (try ri.rl.resultType(parent_gz, node)).? },
71557225 .inferred_ptr => .none,
71567226 else => ri.rl,
71577227 },
71587228 .ctx = ri.ctx,
71597229 };
71607230
7161 const payload_is_ref = switch (node_ty) {
7162 .@"if" => if_full.payload_token != null and tree.tokenTag(if_full.payload_token.?) == .asterisk,
7163 .@"catch" => ri.rl == .ref or ri.rl == .ref_coerced_ty,
7164 };
7165
71667231 // We need to call `rvalue` to write through to the pointer only if we had a
71677232 // result pointer and aren't forwarding it.
71687233 const LocTag = @typeInfo(ResultInfo.Loc).@"union".tag_type.?;
71697234 const need_result_rvalue = @as(LocTag, block_ri.rl) != @as(LocTag, ri.rl);
7170 var scalar_cases_len: u32 = 0;
7171 var multi_cases_len: u32 = 0;
7172 var inline_cases_len: u32 = 0;
7173 var has_else = false;
7174 var else_node: Ast.Node.OptionalIndex = .none;
7175 var else_src: ?Ast.TokenIndex = null;
7176 for (switch_full.ast.cases) |case_node| {
7177 const case = tree.fullSwitchCase(case_node).?;
71787235
7179 if (case.ast.values.len == 0) {
7180 const case_src = case.ast.arrow_token - 1;
7181 if (else_src) |src| {
7182 return astgen.failTokNotes(
7183 case_src,
7184 "multiple else prongs in switch expression",
7185 .{},
7186 &[_]u32{
7187 try astgen.errNoteTok(
7188 src,
7189 "previous else prong here",
7190 .{},
7191 ),
7192 },
7193 );
7194 }
7195 has_else = true;
7196 else_node = case_node.toOptional();
7197 else_src = case_src;
7198 continue;
7199 } else if (case.ast.values.len == 1 and
7200 tree.nodeTag(case.ast.values[0]) == .identifier and
7201 mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(case.ast.values[0])), "_"))
7202 {
7203 const case_src = case.ast.arrow_token - 1;
7204 return astgen.failTokNotes(
7205 case_src,
7206 "'_' prong is not allowed when switching on errors",
7207 .{},
7208 &[_]u32{
7209 try astgen.errNoteTok(
7210 case_src,
7211 "consider using 'else'",
7212 .{},
7213 ),
7214 },
7215 );
7216 }
7217
7218 for (case.ast.values) |val| {
7219 if (tree.nodeTag(val) == .string_literal)
7220 return astgen.failNode(val, "cannot switch on strings", .{});
7221 }
7222
7223 if (case.ast.values.len == 1 and tree.nodeTag(case.ast.values[0]) != .switch_range) {
7224 scalar_cases_len += 1;
7225 } else {
7226 multi_cases_len += 1;
7227 }
7228 if (case.inline_token != null) {
7229 inline_cases_len += 1;
7230 }
7231 }
7232
7233 const operand_ri: ResultInfo = .{
7234 .rl = if (payload_is_ref) .ref else .none,
7235 .ctx = .error_handling_expr,
7236 const catch_or_if_node = if (needs_non_err_handling) node else undefined;
7237 const do_err_trace = needs_non_err_handling and astgen.fn_block != null;
7238 const non_err_is_ref: bool = switch (non_err) {
7239 .none, .peer_break_target => undefined,
7240 .@"catch" => ri.rl == .ref or ri.rl == .ref_coerced_ty,
7241 .@"if" => |if_full| if_full.payload_token != null and
7242 tree.tokenTag(if_full.payload_token.?) == .asterisk,
72367243 };
72377244
7238 astgen.advanceSourceCursorToNode(operand_node);
7239 const operand_lc: LineColumn = .{ astgen.source_line - parent_gz.decl_line, astgen.source_column };
7240
7241 const raw_operand = try reachableExpr(parent_gz, scope, operand_ri, operand_node, switch_node);
7242 const item_ri: ResultInfo = .{ .rl = .none };
7243
7244 // This contains the data that goes into the `extra` array for the SwitchBlockErrUnion, except
7245 // the first cases_nodes.len slots are a table that indexes payloads later in the array,
7246 // with the non-error and else case indices coming first, then scalar_cases_len indexes, then
7247 // multi_cases_len indexes
7248 const payloads = &astgen.scratch;
7249 const scratch_top = astgen.scratch.items.len;
7250 const case_table_start = scratch_top;
7251 const scalar_case_table = case_table_start + 1 + @intFromBool(has_else);
7252 const multi_case_table = scalar_case_table + scalar_cases_len;
7253 const case_table_end = multi_case_table + multi_cases_len;
7254
7255 try astgen.scratch.resize(gpa, case_table_end);
7256 defer astgen.scratch.items.len = scratch_top;
7257
7258 var block_scope = parent_gz.makeSubBlock(scope);
7259 // block_scope not used for collecting instructions
7260 block_scope.instructions_top = GenZir.unstacked_top;
7261 block_scope.setBreakResultInfo(block_ri);
7262
7263 // Sema expects a dbg_stmt immediately before switch_block_err_union
7264 try emitDbgStmtForceCurrentIndex(parent_gz, operand_lc);
7265 // This gets added to the parent block later, after the item expressions.
7266 const switch_block = try parent_gz.makeBlockInst(.switch_block_err_union, switch_node);
7267
7268 // We re-use this same scope for all cases, including the special prong, if any.
7269 var case_scope = parent_gz.makeSubBlock(&block_scope.base);
7270 case_scope.instructions_top = GenZir.unstacked_top;
7271
7272 {
7273 const body_len_index: u32 = @intCast(payloads.items.len);
7274 payloads.items[case_table_start] = body_len_index;
7275 try payloads.resize(gpa, body_len_index + 1); // body_len
7276
7277 case_scope.instructions_top = parent_gz.instructions.items.len;
7278 defer case_scope.unstack();
7279
7280 const unwrap_payload_tag: Zir.Inst.Tag = if (payload_is_ref)
7281 .err_union_payload_unsafe_ptr
7282 else
7283 .err_union_payload_unsafe;
7284
7285 const unwrapped_payload = try case_scope.addUnNode(
7286 unwrap_payload_tag,
7287 raw_operand,
7288 catch_or_if_node,
7289 );
7290
7291 switch (node_ty) {
7292 .@"catch" => {
7293 const case_result = switch (ri.rl) {
7294 .ref, .ref_coerced_ty => unwrapped_payload,
7295 else => try rvalue(
7296 &case_scope,
7297 block_scope.break_result_info,
7298 unwrapped_payload,
7299 catch_or_if_node,
7300 ),
7301 };
7302 _ = try case_scope.addBreakWithSrcNode(
7303 .@"break",
7304 switch_block,
7305 case_result,
7306 catch_or_if_node,
7307 );
7308 },
7309 .@"if" => {
7310 var payload_val_scope: Scope.LocalVal = undefined;
7311
7312 const then_node = if_full.ast.then_expr;
7313 const then_sub_scope = s: {
7314 assert(if_full.error_token != null);
7315 if (if_full.payload_token) |payload_token| {
7316 const token_name_index = payload_token + @intFromBool(payload_is_ref);
7317 const ident_name = try astgen.identAsString(token_name_index);
7318 const token_name_str = tree.tokenSlice(token_name_index);
7319 if (mem.eql(u8, "_", token_name_str))
7320 break :s &case_scope.base;
7321 try astgen.detectLocalShadowing(
7322 &case_scope.base,
7323 ident_name,
7324 token_name_index,
7325 token_name_str,
7326 .capture,
7327 );
7328 payload_val_scope = .{
7329 .parent = &case_scope.base,
7330 .gen_zir = &case_scope,
7331 .name = ident_name,
7332 .inst = unwrapped_payload,
7333 .token_src = token_name_index,
7334 .id_cat = .capture,
7335 };
7336 try case_scope.addDbgVar(.dbg_var_val, ident_name, unwrapped_payload);
7337 break :s &payload_val_scope.base;
7338 } else {
7339 _ = try case_scope.addUnNode(
7340 .ensure_err_union_payload_void,
7341 raw_operand,
7342 catch_or_if_node,
7343 );
7344 break :s &case_scope.base;
7345 }
7346 };
7347 const then_result = try expr(
7348 &case_scope,
7349 then_sub_scope,
7350 block_scope.break_result_info,
7351 then_node,
7352 );
7353 try checkUsed(parent_gz, &case_scope.base, then_sub_scope);
7354 if (!case_scope.endsWithNoReturn()) {
7355 _ = try case_scope.addBreakWithSrcNode(
7356 .@"break",
7357 switch_block,
7358 then_result,
7359 then_node,
7360 );
7361 }
7362 },
7363 }
7364
7365 const case_slice = case_scope.instructionsSlice();
7366 const body_len = astgen.countBodyLenAfterFixupsExtraRefs(case_slice, &.{switch_block});
7367 try payloads.ensureUnusedCapacity(gpa, body_len);
7368 const capture: Zir.Inst.SwitchBlock.ProngInfo.Capture = switch (node_ty) {
7369 .@"catch" => .none,
7370 .@"if" => if (if_full.payload_token == null)
7371 .none
7372 else if (payload_is_ref)
7373 .by_ref
7374 else
7375 .by_val,
7376 };
7377 payloads.items[body_len_index] = @bitCast(Zir.Inst.SwitchBlock.ProngInfo{
7378 .body_len = @intCast(body_len),
7379 .capture = capture,
7380 .is_inline = false,
7381 .has_tag_capture = false,
7382 });
7383 appendBodyWithFixupsExtraRefsArrayList(astgen, payloads, case_slice, &.{switch_block});
7245 if (switch_full.label_token) |label_token| {
7246 try astgen.checkLabelRedefinition(scope, label_token);
73847247 }
73857248
7386 const err_name = blk: {
7387 const err_str = tree.tokenSlice(error_payload);
7249 const err_capture_name: Zir.NullTerminatedString = if (needs_non_err_handling) blk: {
7250 const err_str = tree.tokenSlice(err_token);
73887251 if (mem.eql(u8, err_str, "_")) {
73897252 // This is fatal because we already know we're switching on the captured error.
7390 return astgen.failTok(error_payload, "discard of error capture; omit it instead", .{});
7253 return astgen.failTok(err_token, "discard of error capture; omit it instead", .{});
73917254 }
7392 const err_name = try astgen.identAsString(error_payload);
7393 try astgen.detectLocalShadowing(scope, err_name, error_payload, err_str, .capture);
7394
7255 const err_name = try astgen.identAsString(err_token);
7256 try astgen.detectLocalShadowing(scope, err_name, err_token, err_str, .capture);
73957257 break :blk err_name;
7396 };
7397
7398 // allocate a shared dummy instruction for the error capture
7399 const err_inst = err_inst: {
7400 const inst: Zir.Inst.Index = @enumFromInt(astgen.instructions.len);
7401 try astgen.instructions.append(astgen.gpa, .{
7402 .tag = .extended,
7403 .data = .{ .extended = .{
7404 .opcode = .value_placeholder,
7405 .small = undefined,
7406 .operand = undefined,
7407 } },
7408 });
7409 break :err_inst inst;
7410 };
7411
7412 // In this pass we generate all the item and prong expressions for error cases.
7413 var multi_case_index: u32 = 0;
7414 var scalar_case_index: u32 = 0;
7415 var any_uses_err_capture = false;
7416 for (switch_full.ast.cases) |case_node| {
7417 const case = tree.fullSwitchCase(case_node).?;
7418
7419 const is_multi_case = case.ast.values.len > 1 or
7420 (case.ast.values.len == 1 and tree.nodeTag(case.ast.values[0]) == .switch_range);
7421
7422 var dbg_var_name: Zir.NullTerminatedString = .empty;
7423 var dbg_var_inst: Zir.Inst.Ref = undefined;
7424 var err_scope: Scope.LocalVal = undefined;
7425 var capture_scope: Scope.LocalVal = undefined;
7426
7427 const sub_scope = blk: {
7428 err_scope = .{
7429 .parent = &case_scope.base,
7430 .gen_zir = &case_scope,
7431 .name = err_name,
7432 .inst = err_inst.toRef(),
7433 .token_src = error_payload,
7434 .id_cat = .capture,
7435 };
7436
7437 const capture_token = case.payload_token orelse break :blk &err_scope.base;
7438 if (tree.tokenTag(capture_token) != .identifier) {
7439 return astgen.failTok(capture_token + 1, "error set cannot be captured by reference", .{});
7440 }
7441
7442 const capture_slice = tree.tokenSlice(capture_token);
7443 if (mem.eql(u8, capture_slice, "_")) {
7444 try astgen.appendErrorTok(capture_token, "discard of error capture; omit it instead", .{});
7445 }
7446 const tag_name = try astgen.identAsString(capture_token);
7447 try astgen.detectLocalShadowing(&case_scope.base, tag_name, capture_token, capture_slice, .capture);
7448
7449 capture_scope = .{
7450 .parent = &case_scope.base,
7451 .gen_zir = &case_scope,
7452 .name = tag_name,
7453 .inst = switch_block.toRef(),
7454 .token_src = capture_token,
7455 .id_cat = .capture,
7456 };
7457 dbg_var_name = tag_name;
7458 dbg_var_inst = switch_block.toRef();
7459
7460 err_scope.parent = &capture_scope.base;
7461
7462 break :blk &err_scope.base;
7463 };
7464
7465 const header_index: u32 = @intCast(payloads.items.len);
7466 const body_len_index = if (is_multi_case) blk: {
7467 payloads.items[multi_case_table + multi_case_index] = header_index;
7468 multi_case_index += 1;
7469 try payloads.resize(gpa, header_index + 3); // items_len, ranges_len, body_len
7470
7471 // items
7472 var items_len: u32 = 0;
7473 for (case.ast.values) |item_node| {
7474 if (tree.nodeTag(item_node) == .switch_range) continue;
7475 items_len += 1;
7476
7477 const item_inst = try comptimeExpr(parent_gz, scope, item_ri, item_node, .switch_item);
7478 try payloads.append(gpa, @intFromEnum(item_inst));
7479 }
7480
7481 // ranges
7482 var ranges_len: u32 = 0;
7483 for (case.ast.values) |range| {
7484 if (tree.nodeTag(range) != .switch_range) continue;
7485 ranges_len += 1;
7486
7487 const first_node, const last_node = tree.nodeData(range).node_and_node;
7488 const first = try comptimeExpr(parent_gz, scope, item_ri, first_node, .switch_item);
7489 const last = try comptimeExpr(parent_gz, scope, item_ri, last_node, .switch_item);
7490 try payloads.appendSlice(gpa, &[_]u32{
7491 @intFromEnum(first), @intFromEnum(last),
7492 });
7493 }
7494
7495 payloads.items[header_index] = items_len;
7496 payloads.items[header_index + 1] = ranges_len;
7497 break :blk header_index + 2;
7498 } else if (case_node.toOptional() == else_node) blk: {
7499 payloads.items[case_table_start + 1] = header_index;
7500 try payloads.resize(gpa, header_index + 1); // body_len
7501 break :blk header_index;
7502 } else blk: {
7503 payloads.items[scalar_case_table + scalar_case_index] = header_index;
7504 scalar_case_index += 1;
7505 try payloads.resize(gpa, header_index + 2); // item, body_len
7506 const item_node = case.ast.values[0];
7507 const item_inst = try comptimeExpr(parent_gz, scope, item_ri, item_node, .switch_item);
7508 payloads.items[header_index] = @intFromEnum(item_inst);
7509 break :blk header_index + 1;
7510 };
7511
7512 {
7513 // temporarily stack case_scope on parent_gz
7514 case_scope.instructions_top = parent_gz.instructions.items.len;
7515 defer case_scope.unstack();
7516
7517 if (do_err_trace and nodeMayAppendToErrorTrace(tree, operand_node))
7518 _ = try case_scope.addSaveErrRetIndex(.always);
7519
7520 if (dbg_var_name != .empty) {
7521 try case_scope.addDbgVar(.dbg_var_val, dbg_var_name, dbg_var_inst);
7522 }
7523
7524 const target_expr_node = case.ast.target_expr;
7525 const case_result = try fullBodyExpr(&case_scope, sub_scope, block_scope.break_result_info, target_expr_node, .allow_branch_hint);
7526 // check capture_scope, not err_scope to avoid false positive unused error capture
7527 try checkUsed(parent_gz, &case_scope.base, err_scope.parent);
7528 const uses_err = err_scope.used != .none or err_scope.discarded != .none;
7529 if (uses_err) {
7530 try case_scope.addDbgVar(.dbg_var_val, err_name, err_inst.toRef());
7531 any_uses_err_capture = true;
7532 }
7533
7534 if (!parent_gz.refIsNoReturn(case_result)) {
7535 if (do_err_trace)
7536 try restoreErrRetIndex(
7537 &case_scope,
7538 .{ .block = switch_block },
7539 block_scope.break_result_info,
7540 target_expr_node,
7541 case_result,
7542 );
7543
7544 _ = try case_scope.addBreakWithSrcNode(.@"break", switch_block, case_result, target_expr_node);
7545 }
7546
7547 const case_slice = case_scope.instructionsSlice();
7548 const extra_insts: []const Zir.Inst.Index = if (uses_err) &.{ switch_block, err_inst } else &.{switch_block};
7549 const body_len = astgen.countBodyLenAfterFixupsExtraRefs(case_slice, extra_insts);
7550 try payloads.ensureUnusedCapacity(gpa, body_len);
7551 payloads.items[body_len_index] = @bitCast(Zir.Inst.SwitchBlock.ProngInfo{
7552 .body_len = @intCast(body_len),
7553 .capture = if (case.payload_token != null) .by_val else .none,
7554 .is_inline = case.inline_token != null,
7555 .has_tag_capture = false,
7556 });
7557 appendBodyWithFixupsExtraRefsArrayList(astgen, payloads, case_slice, extra_insts);
7558 }
7559 }
7560 // Now that the item expressions are generated we can add this.
7561 try parent_gz.instructions.append(gpa, switch_block);
7562
7563 try astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.SwitchBlockErrUnion).@"struct".fields.len +
7564 @intFromBool(multi_cases_len != 0) +
7565 payloads.items.len - case_table_end +
7566 (case_table_end - case_table_start) * @typeInfo(Zir.Inst.As).@"struct".fields.len);
7567
7568 const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.SwitchBlockErrUnion{
7569 .operand = raw_operand,
7570 .bits = Zir.Inst.SwitchBlockErrUnion.Bits{
7571 .has_multi_cases = multi_cases_len != 0,
7572 .has_else = has_else,
7573 .scalar_cases_len = @intCast(scalar_cases_len),
7574 .any_uses_err_capture = any_uses_err_capture,
7575 .payload_is_ref = payload_is_ref,
7576 },
7577 .main_src_node_offset = parent_gz.nodeIndexToRelative(catch_or_if_node),
7578 });
7579
7580 if (multi_cases_len != 0) {
7581 astgen.extra.appendAssumeCapacity(multi_cases_len);
7582 }
7583
7584 if (any_uses_err_capture) {
7585 astgen.extra.appendAssumeCapacity(@intFromEnum(err_inst));
7586 }
7587
7588 const zir_datas = astgen.instructions.items(.data);
7589 zir_datas[@intFromEnum(switch_block)].pl_node.payload_index = payload_index;
7590
7591 for (payloads.items[case_table_start..case_table_end], 0..) |start_index, i| {
7592 var body_len_index = start_index;
7593 var end_index = start_index;
7594 const table_index = case_table_start + i;
7595 if (table_index < scalar_case_table) {
7596 end_index += 1;
7597 } else if (table_index < multi_case_table) {
7598 body_len_index += 1;
7599 end_index += 2;
7600 } else {
7601 body_len_index += 2;
7602 const items_len = payloads.items[start_index];
7603 const ranges_len = payloads.items[start_index + 1];
7604 end_index += 3 + items_len + 2 * ranges_len;
7605 }
7606 const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(payloads.items[body_len_index]);
7607 end_index += prong_info.body_len;
7608 astgen.extra.appendSliceAssumeCapacity(payloads.items[start_index..end_index]);
7609 }
7610
7611 if (need_result_rvalue) {
7612 return rvalue(parent_gz, ri, switch_block.toRef(), switch_node);
7613 } else {
7614 return switch_block.toRef();
7615 }
7616}
7617
7618fn switchExpr(
7619 parent_gz: *GenZir,
7620 scope: *Scope,
7621 ri: ResultInfo,
7622 node: Ast.Node.Index,
7623 switch_full: Ast.full.Switch,
7624) InnerError!Zir.Inst.Ref {
7625 const astgen = parent_gz.astgen;
7626 const gpa = astgen.gpa;
7627 const tree = astgen.tree;
7628 const operand_node = switch_full.ast.condition;
7629 const case_nodes = switch_full.ast.cases;
7630
7631 const need_rl = astgen.nodes_need_rl.contains(node);
7632 const block_ri: ResultInfo = if (need_rl) ri else .{
7633 .rl = switch (ri.rl) {
7634 .ptr => .{ .ty = (try ri.rl.resultType(parent_gz, node)).? },
7635 .inferred_ptr => .none,
7636 else => ri.rl,
7637 },
7638 .ctx = ri.ctx,
7639 };
7640 // We need to call `rvalue` to write through to the pointer only if we had a
7641 // result pointer and aren't forwarding it.
7642 const LocTag = @typeInfo(ResultInfo.Loc).@"union".tag_type.?;
7643 const need_result_rvalue = @as(LocTag, block_ri.rl) != @as(LocTag, ri.rl);
7644
7645 if (switch_full.label_token) |label_token| {
7646 try astgen.checkLabelRedefinition(scope, label_token);
7647 }
7258 } else undefined;
76487259
76497260 // We perform two passes over the AST. This first pass is to collect information
7650 // for the following variables, make note of the special prong AST node index,
7651 // and bail out with a compile error if there are multiple special prongs present.
7261 // for the following variables, make note of the special prong AST node indices,
7262 // and bail out with a compile error if there are incompatible special prongs present.
76527263 var any_payload_is_ref = false;
7264 var any_has_payload_capture = false;
76537265 var any_has_tag_capture = false;
7654 var any_non_inline_capture = false;
7266 var any_maybe_runtime_capture = false;
76557267 var scalar_cases_len: u32 = 0;
76567268 var multi_cases_len: u32 = 0;
7657 var inline_cases_len: u32 = 0;
7269 var total_items_len: usize = 0;
7270 var total_ranges_len: usize = 0;
76587271 var else_case_node: Ast.Node.OptionalIndex = .none;
76597272 var else_src: ?Ast.TokenIndex = null;
7660 var underscore_case_node: Ast.Node.OptionalIndex = .none;
7273 var under_case_node: Ast.Node.OptionalIndex = .none;
76617274 var underscore_node: Ast.Node.OptionalIndex = .none;
76627275 var underscore_src: ?Ast.TokenIndex = null;
7663 var underscore_additional_items: Zir.SpecialProngs.AdditionalItems = .none;
7276 var under_is_bare = false;
76647277 for (case_nodes) |case_node| {
76657278 const case = tree.fullSwitchCase(case_node).?;
76667279 if (case.payload_token) |payload_token| {
76677280 const ident = if (tree.tokenTag(payload_token) == .asterisk) blk: {
7281 // Capturing errors by reference is never allowed, but as we will
7282 // check for this again later we will fail as late as possible.
76687283 any_payload_is_ref = true;
76697284 break :blk payload_token + 1;
76707285 } else payload_token;
7286
7287 if (!mem.eql(u8, tree.tokenSlice(ident), "_")) {
7288 any_has_payload_capture = true;
7289
7290 // If we're capturing a union, its payload value cannot always be
7291 // comptime-known, even if its prong is inlined as inlining only
7292 // affects its enum tag.
7293 // This check isn't perfect, because for things like enums, the
7294 // entire capture *is* comptime-known for inline prongs! But such
7295 // knowledge requires semantic analysis.
7296 any_maybe_runtime_capture = true;
7297 }
76717298 if (tree.tokenTag(ident + 1) == .comma) {
76727299 any_has_tag_capture = true;
7673 }
76747300
7675 // If the first capture is ignored, then there is no runtime-known
7676 // capture, as the tag capture must be for an inline prong.
7677 // This check isn't perfect, because for things like enums, the
7678 // first prong *is* comptime-known for inline prongs! But such
7679 // knowledge requires semantic analysis.
7680 if (!mem.eql(u8, tree.tokenSlice(ident), "_")) {
7681 any_non_inline_capture = true;
7301 if (case.inline_token == null) {
7302 any_maybe_runtime_capture = true;
7303 }
76827304 }
76837305 }
76847306
......@@ -7690,13 +7312,7 @@ fn switchExpr(
76907312 case_src,
76917313 "multiple else prongs in switch expression",
76927314 .{},
7693 &[_]u32{
7694 try astgen.errNoteTok(
7695 src,
7696 "previous else prong here",
7697 .{},
7698 ),
7699 },
7315 &.{try astgen.errNoteTok(src, "previous else prong here", .{})},
77007316 );
77017317 }
77027318 else_case_node = case_node.toOptional();
......@@ -7704,156 +7320,492 @@ fn switchExpr(
77047320 continue;
77057321 }
77067322
7707 // Check for '_' prong.
7323 // Check for '_' prong and ranges.
77087324 var case_has_underscore = false;
7325 var case_has_ranges = false;
77097326 for (case.ast.values) |val| {
77107327 switch (tree.nodeTag(val)) {
7711 .identifier => if (mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(val)), "_")) {
7712 const val_src = tree.nodeMainToken(val);
7713 if (underscore_src) |src| {
7714 return astgen.failTokNotes(
7715 val_src,
7716 "multiple '_' prongs in switch expression",
7717 .{},
7718 &[_]u32{
7719 try astgen.errNoteTok(
7720 src,
7721 "previous '_' prong here",
7722 .{},
7723 ),
7724 },
7725 );
7726 }
7727 if (case.inline_token != null) {
7728 return astgen.failTok(val_src, "cannot inline '_' prong", .{});
7729 }
7730 underscore_case_node = case_node.toOptional();
7731 underscore_src = val_src;
7732 underscore_node = val.toOptional();
7733 underscore_additional_items = switch (case.ast.values.len) {
7734 0 => unreachable,
7735 1 => .none,
7736 2 => .one,
7737 else => .many,
7738 };
7739 case_has_underscore = true;
7328 .switch_range => {
7329 total_ranges_len += 1;
7330 case_has_ranges = true;
77407331 },
77417332 .string_literal => return astgen.failNode(val, "cannot switch on strings", .{}),
7742 else => {},
7333 else => |tag| {
7334 if (tag == .identifier and
7335 mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(val)), "_"))
7336 {
7337 const val_src = tree.nodeMainToken(val);
7338 if (is_err_switch) {
7339 const case_src = case.ast.arrow_token - 1;
7340 return astgen.failTokNotes(
7341 case_src,
7342 "'_' prong is not allowed when switching on errors",
7343 .{},
7344 &.{
7345 try astgen.errNoteTok(
7346 case_src,
7347 "consider using 'else'",
7348 .{},
7349 ),
7350 },
7351 );
7352 }
7353 if (underscore_src) |src| {
7354 return astgen.failTokNotes(
7355 val_src,
7356 "multiple '_' prongs in switch expression",
7357 .{},
7358 &.{try astgen.errNoteTok(src, "previous '_' prong here", .{})},
7359 );
7360 }
7361 if (case.inline_token != null) {
7362 return astgen.failTok(val_src, "cannot inline '_' prong", .{});
7363 }
7364 under_case_node = case_node.toOptional();
7365 underscore_src = val_src;
7366 underscore_node = val.toOptional();
7367 under_is_bare = case.ast.values.len == 1;
7368 case_has_underscore = true;
7369 } else {
7370 total_items_len += 1;
7371 }
7372 },
77437373 }
77447374 }
7745 if (case_has_underscore) continue;
77467375
7747 if (case.ast.values.len == 1 and tree.nodeTag(case.ast.values[0]) != .switch_range) {
7376 const case_len = case.ast.values.len - @intFromBool(case_has_underscore);
7377 if (case_len == 1 and !case_has_ranges) {
77487378 scalar_cases_len += 1;
7749 } else {
7379 } else if (case_len >= 1) {
77507380 multi_cases_len += 1;
77517381 }
7752 if (case.inline_token != null) {
7753 inline_cases_len += 1;
7754 }
77557382 }
77567383
7757 const special_prongs: Zir.SpecialProngs = .init(
7758 else_src != null,
7759 underscore_src != null,
7760 underscore_additional_items,
7761 );
7762 const has_else = special_prongs.hasElse();
7763 const has_under = special_prongs.hasUnder();
7764
7765 const operand_ri: ResultInfo = .{ .rl = if (any_payload_is_ref) .ref else .none };
7766
7767 astgen.advanceSourceCursorToNode(operand_node);
7768 const operand_lc: LineColumn = .{ astgen.source_line - parent_gz.decl_line, astgen.source_column };
7769
7770 const raw_operand = try expr(parent_gz, scope, operand_ri, operand_node);
7771 const item_ri: ResultInfo = .{ .rl = .none };
7384 const has_else = else_src != null;
7385 const has_under = underscore_src != null;
7386 if (under_is_bare) assert(has_under); // make sure that the former implies the latter
7387 if (is_err_switch) assert(!has_under); // should have failed by now
7388 const any_ranges = total_ranges_len > 0;
77727389
7773 // If this switch is labeled, it may have `continue`s targeting it, and thus we need the operand type
7774 // to provide a result type.
7775 const raw_operand_ty_ref = if (switch_full.label_token != null) t: {
7776 break :t try parent_gz.addUnNode(.typeof, raw_operand, operand_node);
7777 } else undefined;
7778
7779 // This contains the data that goes into the `extra` array for the SwitchBlock/SwitchBlockMulti,
7780 // except the first cases_nodes.len slots are a table that indexes payloads later in the array, with
7781 // the special case index coming first, then scalar_case_len indexes, then multi_cases_len indexes
7390 // This contains all of the body lengths (already in the correct order) and
7391 // the bodies they belong to that go into the `extra` array later, except the
7392 // first item_table_end slots are a table that indexes the item bodies (and
7393 // also indirectly the prong bodies, as they are always trailing after their
7394 // item bodies).
77827395 const payloads = &astgen.scratch;
77837396 const scratch_top = astgen.scratch.items.len;
7784 const case_table_start = scratch_top;
7785 const else_case_index = if (has_else) case_table_start else undefined;
7786 const under_case_index = if (has_under) case_table_start + @intFromBool(has_else) else undefined;
7787 const scalar_case_table = case_table_start + @intFromBool(has_else) + @intFromBool(has_under);
7788 const multi_case_table = scalar_case_table + scalar_cases_len;
7789 const case_table_end = multi_case_table + multi_cases_len;
7790 try astgen.scratch.resize(gpa, case_table_end);
7397 var payloads_end = scratch_top;
7398
7399 // Since range item body pairs are always contiguous we don't technically
7400 // have to keep track of the position of the second body. However handling
7401 // all of the several indices and offsets is complicated enough as it is,
7402 // so for the sake of keeping this function a little bit more simple we do
7403 // it anyway.
7404
7405 const scalar_body_table = payloads_end;
7406 payloads_end += scalar_cases_len;
7407 const multi_item_body_table = payloads_end;
7408 payloads_end += total_items_len + 2 * total_ranges_len - scalar_cases_len;
7409 const multi_prong_body_table = payloads_end;
7410 payloads_end += multi_cases_len;
7411 const body_table_end = payloads_end;
7412
7413 const scalar_prong_infos_start = payloads_end;
7414 payloads_end += scalar_cases_len;
7415 const multi_prong_infos_start = payloads_end;
7416 payloads_end += multi_cases_len;
7417 const multi_case_items_lens_start = payloads_end;
7418 payloads_end += multi_cases_len;
7419 const multi_case_ranges_lens_start = if (any_ranges) blk: {
7420 const multi_case_ranges_lens_start = payloads_end;
7421 payloads_end += multi_cases_len;
7422 break :blk multi_case_ranges_lens_start;
7423 } else undefined;
7424 const scalar_item_infos_start = payloads_end;
7425 payloads_end += scalar_cases_len;
7426 const multi_items_infos_start = payloads_end;
7427 payloads_end += total_items_len - scalar_cases_len + 2 * total_ranges_len;
7428 const bodies_start = payloads_end;
7429
7430 try payloads.resize(gpa, bodies_start);
77917431 defer astgen.scratch.items.len = scratch_top;
77927432
7433 var non_err_prong_body_start: u32 = undefined;
7434 var else_prong_body_start: u32 = undefined;
7435 var bare_under_prong_body_start: u32 = undefined;
7436 var non_err_info: Zir.Inst.SwitchBlock.ProngInfo.NonErr = undefined;
7437 var else_info: Zir.Inst.SwitchBlock.ProngInfo.Else = undefined;
7438 var under_extra: u32 = undefined;
7439
77937440 var block_scope = parent_gz.makeSubBlock(scope);
77947441 // block_scope not used for collecting instructions
77957442 block_scope.instructions_top = GenZir.unstacked_top;
7796 block_scope.setBreakResultInfo(block_ri);
77977443
7798 // Sema expects a dbg_stmt immediately before switch_block(_ref)
7444 const operand_ri: ResultInfo = .{
7445 .rl = if (any_payload_is_ref or
7446 (needs_non_err_handling and non_err_is_ref)) .ref else .none,
7447 .ctx = if (do_err_trace) .error_handling_expr else .none,
7448 };
7449
7450 astgen.advanceSourceCursorToNode(operand_node);
7451 const operand_lc: LineColumn = .{ astgen.source_line - parent_gz.decl_line, astgen.source_column };
7452
7453 const raw_operand: Zir.Inst.Ref = if (needs_non_err_handling)
7454 try reachableExpr(parent_gz, scope, operand_ri, operand_node, switch_node)
7455 else
7456 try expr(parent_gz, scope, operand_ri, operand_node);
7457
7458 // Sema expects a dbg_stmt immediately before any kind of switch_block inst.
77997459 try emitDbgStmtForceCurrentIndex(parent_gz, operand_lc);
78007460 // This gets added to the parent block later, after the item expressions.
7801 const switch_tag: Zir.Inst.Tag = if (any_payload_is_ref) .switch_block_ref else .switch_block;
7802 const switch_block = try parent_gz.makeBlockInst(switch_tag, node);
7461 const switch_tag: Zir.Inst.Tag = switch (non_err) {
7462 .none, .peer_break_target => if (any_payload_is_ref) .switch_block_ref else .switch_block,
7463 .@"if", .@"catch" => .switch_block_err_union,
7464 };
7465 const switch_block = try parent_gz.makeBlockInst(switch_tag, switch_node);
7466
7467 // Set `break` target if applicable; `continue` target may differ!
7468 switch (non_err) {
7469 .none => {
7470 if (switch_full.label_token != null) {
7471 block_scope.break_target = switch_block;
7472 }
7473 block_scope.setBreakResultInfo(block_ri);
7474 },
7475 .@"catch", .@"if" => {
7476 assert(switch_full.label_token == null); // use `peer_break_target` code path instead!
7477 block_scope.setBreakResultInfo(block_ri);
7478 },
7479 .peer_break_target => |peer_break_target| {
7480
7481 // Special case; we have an error switch + label situation and we
7482 // want to generate this:
7483 // ```
7484 // %1 = block({
7485 // %2 = is_non_err(%operand)
7486 // %3 = condbr(%2, {
7487 // %4 = err_union_payload_unsafe(%operand)
7488 // %5 = break(%1, result) // targets enclosing `block`
7489 // }, {
7490 // %6 = err_union_code(%operand)
7491 // %7 = switch_block(%6,
7492 // { ... } => {
7493 // %8 = break(%1, result) // targets enclosing `block`
7494 // },
7495 // { ... } => {
7496 // %9 = switch_continue(%7, result) // targets `switch_block`
7497 // },
7498 // )
7499 // %10 = break(%1, @void_value)
7500 // })
7501 // })
7502 // ```
7503 // to ensure that the non-err case and the switch are only peers when
7504 // breaking from either, but not when continuing the switch. We use
7505 // this lowering to avoiding a rather complex special case in Sema.
7506
7507 assert(switch_full.label_token != null); // use `switch_block_err_union` code path instead!
7508 assert(.block == astgen.instructions.items(.tag)[@intFromEnum(peer_break_target.block_inst)]);
7509 block_scope.break_target = peer_break_target.block_inst;
7510 block_scope.setBreakResultInfo(peer_break_target.block_ri);
7511 },
7512 }
7513
7514 // We need a bunch of separate locations to store several capture values:
7515 // `... |err| switch (err) { else => |e| { ... } }` // `err` and `e`
7516 // `... => |payload, tag| { ... }` // `payload` and `tag`
7517 // and result types:
7518 // `foo => { ... }` // `foo` needs a result type
7519 // `... => continue :sw val` // `val` needs a result type
7520 // Some observations:
7521 // - If we just use the switch inst itself we don't need a placeholder!
7522 // - We can always tell for sure whether a capture exists. We also know
7523 // that its existence implies that it has to be used.
7524 // - We can't know whether there are any `continue`s before analyzing all
7525 // prong bodies. At that point we already need a result location. We do
7526 // know whether there even *could* be any though by looking for a label.
7527 // - Sema wants a result location in `zirSwitchContinue`. If that's the
7528 // switch inst itself, there's no need to look at the switch inst data.
7529 // Some conclusions:
7530 // - We should use the switch inst as the continue result location if needed.
7531 // - If we need more insts for captures and our switch inst is already used
7532 // for something else, we start creating placeholder insts.
7533
7534 // Prong items use the switch block instruction as their result type.
7535 // No other components of the switch statement are in scope while they are
7536 // being resolved, so this is never a problem.
7537 const item_ri: ResultInfo = .{ .rl = .{ .coerced_ty = switch_block.toRef() } };
7538
7539 var switch_block_inst_is_occupied: bool = false;
78037540
78047541 if (switch_full.label_token) |label_token| {
78057542 block_scope.label = .{ .token = label_token };
7806 block_scope.break_target = switch_block;
78077543 block_scope.continue_target = .{ .switch_continue = switch_block };
78087544 block_scope.continue_result_info = .{
78097545 .rl = if (any_payload_is_ref)
7810 .{ .ref_coerced_ty = raw_operand_ty_ref }
7546 .{ .ref_coerced_ty = switch_block.toRef() }
78117547 else
7812 .{ .coerced_ty = raw_operand_ty_ref },
7548 .{ .coerced_ty = switch_block.toRef() },
78137549 };
7550 switch_block_inst_is_occupied = true;
78147551
7815 // `break_result_info` already set by `setBreakResultInfo` above.
7552 // `break_target` and `break_result_info` already set above.
78167553 }
7554 if (needs_non_err_handling) {
7555 // `switch_block_err_union` uses the switch block inst as its err capture/
7556 // switch operand. This is always ok as its switch can never have a label.
7557 assert(!switch_block_inst_is_occupied);
7558 switch_block_inst_is_occupied = true;
7559 }
7560 // `... => |payload| { ... }`
7561 const payload_capture_inst, const payload_capture_inst_is_placeholder = inst: {
7562 if (!any_has_payload_capture) break :inst .{ undefined, false };
7563 if (!switch_block_inst_is_occupied) {
7564 switch_block_inst_is_occupied = true;
7565 break :inst .{ switch_block, false };
7566 }
7567 break :inst .{ try astgen.appendPlaceholder(), true };
7568 };
7569 // `... => |_, tag| { ... }`
7570 const tag_capture_inst, const tag_capture_inst_is_placeholder = inst: {
7571 if (!any_has_tag_capture) break :inst .{ undefined, false };
7572 if (!switch_block_inst_is_occupied) {
7573 switch_block_inst_is_occupied = true;
7574 break :inst .{ switch_block, false };
7575 }
7576 break :inst .{ try astgen.appendPlaceholder(), true };
7577 };
78177578
7818 // We re-use this same scope for all cases, including the special prong, if any.
7819 var case_scope = parent_gz.makeSubBlock(&block_scope.base);
7820 case_scope.instructions_top = GenZir.unstacked_top;
7579 var prong_body_extra_insts_buf: [3]Zir.Inst.Index = undefined;
7580 const prong_body_extra_insts: []const Zir.Inst.Index = extra_insts: {
7581 var extra_insts: std.ArrayList(Zir.Inst.Index) = .initBuffer(&prong_body_extra_insts_buf);
7582 if (switch_block_inst_is_occupied) extra_insts.appendAssumeCapacity(switch_block);
7583 if (payload_capture_inst_is_placeholder) extra_insts.appendAssumeCapacity(payload_capture_inst);
7584 if (tag_capture_inst_is_placeholder) extra_insts.appendAssumeCapacity(tag_capture_inst);
7585 break :extra_insts extra_insts.items;
7586 };
78217587
7822 // If any prong has an inline tag capture, allocate a shared dummy instruction for it
7823 const tag_inst = if (any_has_tag_capture) tag_inst: {
7824 const inst: Zir.Inst.Index = @enumFromInt(astgen.instructions.len);
7825 try astgen.instructions.append(astgen.gpa, .{
7826 .tag = .extended,
7827 .data = .{ .extended = .{
7828 .opcode = .value_placeholder,
7829 .small = undefined,
7830 .operand = undefined,
7831 } },
7832 });
7833 break :tag_inst inst;
7834 } else undefined;
7588 const switch_operand, const catch_or_if_operand = if (needs_non_err_handling)
7589 .{ switch_block.toRef(), raw_operand }
7590 else
7591 .{ raw_operand, undefined };
7592
7593 // We re-use this same scope for all case items and contents.
7594 var scratch_scope = parent_gz.makeSubBlock(&block_scope.base);
7595 scratch_scope.instructions_top = GenZir.unstacked_top;
7596
7597 // We have to take care of the non-error body first if there is one.
7598 non_err_body: {
7599 if (!needs_non_err_handling) break :non_err_body;
7600
7601 scratch_scope.instructions_top = parent_gz.instructions.items.len;
7602 defer scratch_scope.unstack();
7603
7604 // It's always ok to use the switch block inst to refer to the error union
7605 // payload as the actual switch statement isn't even in scope yet.
7606 const non_err_payload_inst = switch_block;
7607 var non_err_capture: Zir.Inst.SwitchBlock.ProngInfo.Capture = .none;
7608
7609 switch (non_err) {
7610 .none, .peer_break_target => unreachable,
7611 .@"catch" => {
7612 // We always effectively capture the error union payload; we use
7613 // it to `break` from the entire `switch_block_err_union`.
7614 non_err_capture = if (non_err_is_ref) .by_ref else .by_val;
7615
7616 const then_result = switch (ri.rl) {
7617 .ref, .ref_coerced_ty => non_err_payload_inst.toRef(),
7618 else => try rvalue(
7619 &scratch_scope,
7620 block_scope.break_result_info,
7621 non_err_payload_inst.toRef(),
7622 catch_or_if_node,
7623 ),
7624 };
7625 _ = try scratch_scope.addBreakWithSrcNode(
7626 .@"break",
7627 switch_block,
7628 then_result,
7629 catch_or_if_node,
7630 );
7631 },
7632 .@"if" => |if_full| {
7633 var payload_val_scope: Scope.LocalVal = undefined;
7634
7635 const then_node = if_full.ast.then_expr;
7636 const then_sub_scope: *Scope = scope: {
7637 if (if_full.payload_token) |payload_token| {
7638 const ident_token = payload_token + @intFromBool(non_err_is_ref);
7639 const ident_name = try astgen.identAsString(ident_token);
7640 const ident_name_str = tree.tokenSlice(ident_token);
7641 if (mem.eql(u8, "_", ident_name_str)) {
7642 break :scope &scratch_scope.base;
7643 }
7644 non_err_capture = if (non_err_is_ref) .by_ref else .by_val;
7645 try astgen.detectLocalShadowing(&scratch_scope.base, ident_name, ident_token, ident_name_str, .capture);
7646 payload_val_scope = .{
7647 .parent = &scratch_scope.base,
7648 .gen_zir = &scratch_scope,
7649 .name = ident_name,
7650 .inst = non_err_payload_inst.toRef(),
7651 .token_src = ident_token,
7652 .id_cat = .capture,
7653 };
7654 try scratch_scope.addDbgVar(.dbg_var_val, ident_name, non_err_payload_inst.toRef());
7655 break :scope &payload_val_scope.base;
7656 } else {
7657 _ = try scratch_scope.addUnNode(
7658 .ensure_err_union_payload_void,
7659 catch_or_if_operand,
7660 catch_or_if_node,
7661 );
7662 break :scope &scratch_scope.base;
7663 }
7664 };
7665 const then_result = try fullBodyExpr(&scratch_scope, then_sub_scope, block_scope.break_result_info, then_node, .allow_branch_hint);
7666 try checkUsed(parent_gz, &scratch_scope.base, then_sub_scope);
7667 if (!scratch_scope.endsWithNoReturn()) {
7668 _ = try scratch_scope.addBreakWithSrcNode(.@"break", switch_block, then_result, then_node);
7669 }
7670 },
7671 }
7672 const body_slice = scratch_scope.instructionsSlice();
7673 const body_start: u32 = @intCast(payloads.items.len);
7674 const body_len = astgen.countBodyLenAfterFixupsExtraRefs(body_slice, &.{non_err_payload_inst});
7675 try payloads.ensureUnusedCapacity(gpa, body_len);
7676 astgen.appendBodyWithFixupsExtraRefsArrayList(payloads, body_slice, &.{non_err_payload_inst});
7677
7678 non_err_prong_body_start = body_start;
7679 non_err_info = .{
7680 .body_len = @intCast(body_len),
7681 .capture = non_err_capture,
7682 .operand_is_ref = non_err_is_ref,
7683 };
7684 }
78357685
78367686 // In this pass we generate all the item and prong expressions.
78377687 var multi_case_index: u32 = 0;
78387688 var scalar_case_index: u32 = 0;
7689 var multi_item_offset: usize = 0;
78397690 for (case_nodes) |case_node| {
78407691 const case = tree.fullSwitchCase(case_node).?;
78417692
7842 const is_multi_case = case.ast.values.len > 1 or
7843 (case.ast.values.len == 1 and tree.nodeTag(case.ast.values[0]) == .switch_range);
7693 const case_has_under = case_node.toOptional() == under_case_node;
7694 const ranges_len: u32 = if (any_ranges) blk: {
7695 var ranges_len: u32 = 0;
7696 for (case.ast.values) |value| {
7697 ranges_len += @intFromBool(tree.nodeTag(value) == .switch_range);
7698 }
7699 break :blk ranges_len;
7700 } else 0;
7701 const items_len: u32 = @intCast(case.ast.values.len - ranges_len - @intFromBool(case_has_under));
7702 const is_multi_case = items_len > 1 or ranges_len > 0;
7703
7704 // item/range bodies in order of occurence
7705 var item_i: usize = 0;
7706 var range_i: usize = 0;
7707 for (case.ast.values) |value| {
7708 if (value.toOptional() == underscore_node) continue;
7709 const is_range = tree.nodeTag(value) == .switch_range;
7710 const range: [2]Ast.Node.Index = if (is_range) tree.nodeData(value).node_and_node else undefined;
7711 const nodes: []const Ast.Node.Index = if (is_range) &range else &.{value};
7712 for (nodes) |item| {
7713 // We lower enum literals, error values and number literals
7714 // manually to save space since they are very commonly used as
7715 // switch case items.
7716 const body_start: u32 = @intCast(payloads.items.len);
7717 const item_info: Zir.Inst.SwitchBlock.ItemInfo = blk: switch (tree.nodeTag(item)) {
7718 .enum_literal => {
7719 const str_index = try astgen.identAsString(tree.nodeMainToken(item));
7720 break :blk .wrap(.{ .enum_literal = str_index });
7721 },
7722 .error_value => {
7723 const ident_token = tree.nodeMainToken(item) + 2; // skip 'error', '.'
7724 const str_index = try astgen.identAsString(ident_token);
7725 break :blk .wrap(.{ .error_value = str_index });
7726 },
7727 .number_literal => {
7728 // We don't actually need a final result type for number
7729 // literals, they can just be turned into `comptime_int`
7730 // or `comptime_float` as usual and then be coerced to
7731 // the correct type later during semantic analysis.
7732 assert(scratch_scope.instructions_top == GenZir.unstacked_top); // important! we emit into `parent_gz` which `scratch_scope` is stacked on top of
7733 const zir_ref = try comptimeExpr(parent_gz, scope, .{ .rl = .none }, item, .switch_item);
7734 break :blk .wrap(.{ .number_literal = zir_ref });
7735 },
7736 else => {
7737 scratch_scope.instructions_top = parent_gz.instructions.items.len;
7738 defer scratch_scope.unstack();
7739 const item_result = try fullBodyExpr(&scratch_scope, scope, item_ri, item, .normal);
7740 if (!scratch_scope.endsWithNoReturn()) {
7741 _ = try scratch_scope.addBreakWithSrcNode(.break_inline, switch_block, item_result, item);
7742 }
7743 const item_slice = scratch_scope.instructionsSlice();
7744 const body_len = astgen.countBodyLenAfterFixupsExtraRefs(item_slice, &.{switch_block});
7745 try payloads.ensureUnusedCapacity(gpa, body_len);
7746 astgen.appendBodyWithFixupsExtraRefsArrayList(payloads, item_slice, &.{switch_block});
7747 break :blk .wrap(.{ .body_len = body_len });
7748 },
7749 };
7750 if (is_multi_case) {
7751 if (is_range) {
7752 const offset = multi_item_offset + items_len + range_i;
7753 payloads.items[multi_item_body_table + offset] = body_start;
7754 payloads.items[multi_items_infos_start + offset] = @bitCast(item_info);
7755 range_i += 1;
7756 } else {
7757 const offset = multi_item_offset + item_i;
7758 payloads.items[multi_item_body_table + offset] = body_start;
7759 payloads.items[multi_items_infos_start + offset] = @bitCast(item_info);
7760 item_i += 1;
7761 }
7762 } else {
7763 payloads.items[scalar_body_table + scalar_case_index] = body_start;
7764 payloads.items[scalar_item_infos_start + scalar_case_index] = @bitCast(item_info);
7765 }
7766 }
7767 }
7768 if (is_multi_case) {
7769 assert(item_i == items_len and range_i == 2 * ranges_len);
7770 payloads.items[multi_case_items_lens_start + multi_case_index] = items_len;
7771 if (any_ranges) {
7772 payloads.items[multi_case_ranges_lens_start + multi_case_index] = ranges_len;
7773 }
7774 multi_item_offset += items_len + 2 * ranges_len;
7775 }
78447776
7845 var dbg_var_name: Zir.NullTerminatedString = .empty;
7846 var dbg_var_inst: Zir.Inst.Ref = undefined;
7777 // Capture and prong body
7778
7779 var dbg_var_payload_name: Zir.NullTerminatedString = .empty;
7780 var dbg_var_payload_inst: Zir.Inst.Ref = undefined;
78477781 var dbg_var_tag_name: Zir.NullTerminatedString = .empty;
78487782 var dbg_var_tag_inst: Zir.Inst.Ref = undefined;
78497783 var has_tag_capture = false;
7850 var capture_val_scope: Scope.LocalVal = undefined;
7851 var tag_scope: Scope.LocalVal = undefined;
7784 var err_capture_scope: Scope.LocalVal = undefined;
7785 var payload_capture_scope: Scope.LocalVal = undefined;
7786 var tag_capture_scope: Scope.LocalVal = undefined;
78527787
78537788 var capture: Zir.Inst.SwitchBlock.ProngInfo.Capture = .none;
78547789
7855 const sub_scope = blk: {
7856 const payload_token = case.payload_token orelse break :blk &case_scope.base;
7790 // Check all captures and make them available to the prong body.
7791 // Potential captures are:
7792 // - for regular switch: payload and tag
7793 // - for error switch: switch operand and payload
7794 const prong_body_scope: *Scope = scope: {
7795 const switch_scope: *Scope = if (needs_non_err_handling) blk: {
7796 // We want to have the captured error we're switching on in scope!
7797 err_capture_scope = .{
7798 .parent = &scratch_scope.base,
7799 .gen_zir = &scratch_scope,
7800 .name = err_capture_name,
7801 .inst = switch_operand,
7802 .token_src = err_token,
7803 .id_cat = .capture,
7804 };
7805 break :blk &err_capture_scope.base;
7806 } else &scratch_scope.base;
7807
7808 const payload_token = case.payload_token orelse break :scope switch_scope;
78577809 const capture_is_ref = tree.tokenTag(payload_token) == .asterisk;
78587810 const ident = payload_token + @intFromBool(capture_is_ref);
78597811
......@@ -7867,36 +7819,38 @@ fn switchExpr(
78677819 return astgen.failTok(payload_token, "pointer modifier invalid on discard", .{});
78687820 }
78697821 capture = .none;
7870 payload_sub_scope = &case_scope.base;
7822 payload_sub_scope = switch_scope;
78717823 } else {
78727824 const capture_name = try astgen.identAsString(ident);
7873 try astgen.detectLocalShadowing(&case_scope.base, capture_name, ident, ident_slice, .capture);
7874 capture_val_scope = .{
7875 .parent = &case_scope.base,
7876 .gen_zir = &case_scope,
7825 try astgen.detectLocalShadowing(&scratch_scope.base, capture_name, ident, ident_slice, .capture);
7826 payload_capture_scope = .{
7827 .parent = switch_scope,
7828 .gen_zir = &scratch_scope,
78777829 .name = capture_name,
7878 .inst = switch_block.toRef(),
7830 .inst = payload_capture_inst.toRef(),
78797831 .token_src = ident,
78807832 .id_cat = .capture,
78817833 };
7882 dbg_var_name = capture_name;
7883 dbg_var_inst = switch_block.toRef();
7884 payload_sub_scope = &capture_val_scope.base;
7834 dbg_var_payload_name = payload_capture_scope.name;
7835 dbg_var_payload_inst = payload_capture_scope.inst;
7836 payload_sub_scope = &payload_capture_scope.base;
78857837 }
78867838
7887 const tag_token = if (tree.tokenTag(ident + 1) == .comma)
7888 ident + 2
7889 else if (capture == .none) {
7890 // discarding the capture is only valid iff the tag is captured
7839 if (is_err_switch and capture == .by_ref) {
7840 return astgen.failTok(ident, "error set cannot be captured by reference", .{});
7841 }
7842
7843 const tag_token = if (tree.tokenTag(ident + 1) == .comma) blk: {
7844 break :blk ident + 2;
7845 } else if (capture == .none) {
7846 // discarding the capture is only valid if the tag is captured
78917847 // whether the tag capture is discarded is handled below
78927848 return astgen.failTok(payload_token, "discard of capture; omit it instead", .{});
7893 } else break :blk payload_sub_scope;
7849 } else break :scope payload_sub_scope;
78947850
78957851 const tag_slice = tree.tokenSlice(tag_token);
78967852 if (mem.eql(u8, tag_slice, "_")) {
78977853 return astgen.failTok(tag_token, "discard of tag capture; omit it instead", .{});
7898 } else if (case.inline_token == null) {
7899 return astgen.failTok(tag_token, "tag capture on non-inline prong", .{});
79007854 }
79017855 const tag_name = try astgen.identAsString(tag_token);
79027856 try astgen.detectLocalShadowing(payload_sub_scope, tag_name, tag_token, tag_slice, .@"switch tag capture");
......@@ -7904,123 +7858,155 @@ fn switchExpr(
79047858 assert(any_has_tag_capture);
79057859 has_tag_capture = true;
79067860
7907 tag_scope = .{
7861 if (is_err_switch) {
7862 return astgen.failTok(tag_token, "cannot capture tag of error union", .{});
7863 }
7864
7865 tag_capture_scope = .{
79087866 .parent = payload_sub_scope,
7909 .gen_zir = &case_scope,
7867 .gen_zir = &scratch_scope,
79107868 .name = tag_name,
7911 .inst = tag_inst.toRef(),
7869 .inst = tag_capture_inst.toRef(),
79127870 .token_src = tag_token,
79137871 .id_cat = .@"switch tag capture",
79147872 };
7915 dbg_var_tag_name = tag_name;
7916 dbg_var_tag_inst = tag_inst.toRef();
7917 break :blk &tag_scope.base;
7873 dbg_var_tag_name = tag_capture_scope.name;
7874 dbg_var_tag_inst = tag_capture_scope.inst;
7875 break :scope &tag_capture_scope.base;
79187876 };
79197877
7920 const header_index: u32 = @intCast(payloads.items.len);
7921 const body_len_index = if (is_multi_case) blk: {
7922 if (case_node.toOptional() == underscore_case_node) {
7923 payloads.items[under_case_index] = header_index;
7924 if (special_prongs.hasOneAdditionalItem()) {
7925 try payloads.resize(gpa, header_index + 2); // item, body_len
7926 const maybe_item_node = case.ast.values[0];
7927 const item_node = if (maybe_item_node.toOptional() == underscore_node)
7928 case.ast.values[1]
7929 else
7930 maybe_item_node;
7931 const item_inst = try comptimeExpr(parent_gz, scope, item_ri, item_node, .switch_item);
7932 payloads.items[header_index] = @intFromEnum(item_inst);
7933 break :blk header_index + 1;
7934 }
7878 if (capture != .none) assert(any_has_payload_capture);
7879 if (is_err_switch) {
7880 assert(!any_payload_is_ref); // should have failed by now
7881 assert(!any_has_tag_capture); // should have failed by now
7882 }
7883
7884 prong_body: {
7885 scratch_scope.instructions_top = parent_gz.instructions.items.len;
7886 defer scratch_scope.unstack();
7887
7888 if (dbg_var_payload_name != .empty) {
7889 try scratch_scope.addDbgVar(.dbg_var_val, dbg_var_payload_name, dbg_var_payload_inst);
7890 }
7891 if (dbg_var_tag_name != .empty) {
7892 try scratch_scope.addDbgVar(.dbg_var_val, dbg_var_tag_name, dbg_var_tag_inst);
7893 }
7894 if (do_err_trace and nodeMayAppendToErrorTrace(tree, operand_node)) {
7895 _ = try scratch_scope.addSaveErrRetIndex(.always);
7896 }
7897 const target_expr_node = case.ast.target_expr;
7898 const case_result = try fullBodyExpr(&scratch_scope, prong_body_scope, block_scope.break_result_info, target_expr_node, .allow_branch_hint);
7899 if (needs_non_err_handling) {
7900 // If we would check `scratch_scope` here, we would get a false
7901 // positive, that being the switch operand itself!
7902 try checkUsed(parent_gz, &err_capture_scope.base, prong_body_scope);
79357903 } else {
7936 payloads.items[multi_case_table + multi_case_index] = header_index;
7937 multi_case_index += 1;
7904 try checkUsed(parent_gz, &scratch_scope.base, prong_body_scope);
79387905 }
7939 try payloads.resize(gpa, header_index + 3); // items_len, ranges_len, body_len
7940
7941 // items
7942 var items_len: u32 = 0;
7943 for (case.ast.values) |item_node| {
7944 if (item_node.toOptional() == underscore_node or
7945 tree.nodeTag(item_node) == .switch_range)
7946 {
7947 continue;
7906 if (!scratch_scope.endsWithNoReturn()) {
7907 // As our last action before the break, "pop" the error trace if needed
7908 if (do_err_trace) {
7909 try restoreErrRetIndex(
7910 &scratch_scope,
7911 .{ .block = switch_block },
7912 block_scope.break_result_info,
7913 target_expr_node,
7914 case_result,
7915 );
79487916 }
7949 items_len += 1;
7950
7951 const item_inst = try comptimeExpr(parent_gz, scope, item_ri, item_node, .switch_item);
7952 try payloads.append(gpa, @intFromEnum(item_inst));
7917 _ = try scratch_scope.addBreakWithSrcNode(.@"break", switch_block, case_result, target_expr_node);
79537918 }
79547919
7955 // ranges
7956 var ranges_len: u32 = 0;
7957 for (case.ast.values) |range| {
7958 if (tree.nodeTag(range) != .switch_range) {
7959 continue;
7960 }
7961 ranges_len += 1;
7920 const body_slice = scratch_scope.instructionsSlice();
7921 const body_start: u32 = @intCast(payloads.items.len);
7922 const body_len = astgen.countBodyLenAfterFixupsExtraRefs(body_slice, prong_body_extra_insts);
7923 try payloads.ensureUnusedCapacity(gpa, body_len);
7924 astgen.appendBodyWithFixupsExtraRefsArrayList(payloads, body_slice, prong_body_extra_insts);
7925
7926 if (case_node.toOptional() == else_case_node) {
7927 assert(case.ast.values.len == 0);
7928
7929 // Specific `else` bodies can cause Sema to omit the
7930 // "unreachable else prong" error so that certain generic code
7931 // patterns don't trigger it. We do that for these bodies:
7932 // `else => unreachable,`
7933 // `else => return,`
7934 // `else => |e| return e,` (where `e` is any identifier)
7935 const is_simple_noreturn = switch (tree.nodeTag(target_expr_node)) {
7936 .unreachable_literal => true, // `=> unreachable,`
7937 .@"return" => simple_noreturn: {
7938 const retval_node = tree.nodeData(target_expr_node).opt_node.unwrap() orelse {
7939 break :simple_noreturn true; // `=> return,`
7940 };
7941 // Check for `=> |e| return e,`
7942 if (capture != .by_val) break :simple_noreturn false;
7943 if (tree.nodeTag(retval_node) != .identifier) break :simple_noreturn false;
7944 const payload_name = try astgen.identAsString(case.payload_token.?);
7945 const retval_name = try astgen.identAsString(tree.nodeMainToken(retval_node));
7946 break :simple_noreturn payload_name == retval_name;
7947 },
7948 else => false,
7949 };
79627950
7963 const first_node, const last_node = tree.nodeData(range).node_and_node;
7964 const first = try comptimeExpr(parent_gz, scope, item_ri, first_node, .switch_item);
7965 const last = try comptimeExpr(parent_gz, scope, item_ri, last_node, .switch_item);
7966 try payloads.appendSlice(gpa, &[_]u32{
7967 @intFromEnum(first), @intFromEnum(last),
7968 });
7951 else_info = .{
7952 .body_len = @intCast(body_len),
7953 .capture = capture,
7954 .is_inline = case.inline_token != null,
7955 .has_tag_capture = has_tag_capture,
7956 .is_simple_noreturn = is_simple_noreturn,
7957 };
7958 else_prong_body_start = body_start;
7959 break :prong_body;
79697960 }
79707961
7971 payloads.items[header_index] = items_len;
7972 payloads.items[header_index + 1] = ranges_len;
7973 break :blk header_index + 2;
7974 } else if (case_node.toOptional() == else_case_node) blk: {
7975 payloads.items[else_case_index] = header_index;
7976 try payloads.resize(gpa, header_index + 1); // body_len
7977 break :blk header_index;
7978 } else if (case_node.toOptional() == underscore_case_node) blk: {
7979 assert(!special_prongs.hasAdditionalItems());
7980 payloads.items[under_case_index] = header_index;
7981 try payloads.resize(gpa, header_index + 1); // body_len
7982 break :blk header_index;
7983 } else blk: {
7984 payloads.items[scalar_case_table + scalar_case_index] = header_index;
7985 scalar_case_index += 1;
7986 try payloads.resize(gpa, header_index + 2); // item, body_len
7987 const item_node = case.ast.values[0];
7988 const item_inst = try comptimeExpr(parent_gz, scope, item_ri, item_node, .switch_item);
7989 payloads.items[header_index] = @intFromEnum(item_inst);
7990 break :blk header_index + 1;
7991 };
7992
7993 {
7994 // temporarily stack case_scope on parent_gz
7995 case_scope.instructions_top = parent_gz.instructions.items.len;
7996 defer case_scope.unstack();
7997
7998 if (dbg_var_name != .empty) {
7999 try case_scope.addDbgVar(.dbg_var_val, dbg_var_name, dbg_var_inst);
8000 }
8001 if (dbg_var_tag_name != .empty) {
8002 try case_scope.addDbgVar(.dbg_var_val, dbg_var_tag_name, dbg_var_tag_inst);
8003 }
8004 const target_expr_node = case.ast.target_expr;
8005 const case_result = try fullBodyExpr(&case_scope, sub_scope, block_scope.break_result_info, target_expr_node, .allow_branch_hint);
8006 try checkUsed(parent_gz, &case_scope.base, sub_scope);
8007 if (!parent_gz.refIsNoReturn(case_result)) {
8008 _ = try case_scope.addBreakWithSrcNode(.@"break", switch_block, case_result, target_expr_node);
7962 if (case_has_under) {
7963 // We're either writing under_prong_info or under_index here.
7964 if (under_is_bare) {
7965 assert(case.ast.values.len == 1); // only `_`
7966 const bare_under_info: Zir.Inst.SwitchBlock.ProngInfo.BareUnder = .{
7967 .body_len = @intCast(body_len),
7968 .capture = capture,
7969 .has_tag_capture = has_tag_capture,
7970 };
7971 under_extra = @bitCast(bare_under_info);
7972 bare_under_prong_body_start = body_start;
7973 break :prong_body;
7974 } else if (is_multi_case) {
7975 under_extra = scalar_cases_len + multi_case_index;
7976 } else {
7977 under_extra = scalar_case_index;
7978 }
80097979 }
80107980
8011 const case_slice = case_scope.instructionsSlice();
8012 const extra_insts: []const Zir.Inst.Index = if (has_tag_capture) &.{ switch_block, tag_inst } else &.{switch_block};
8013 const body_len = astgen.countBodyLenAfterFixupsExtraRefs(case_slice, extra_insts);
8014 try payloads.ensureUnusedCapacity(gpa, body_len);
8015 payloads.items[body_len_index] = @bitCast(Zir.Inst.SwitchBlock.ProngInfo{
7981 // We allow prongs with error items which are not inside the error set
7982 // being switched on if their body is `=> comptime unreachable,`.
7983 const is_comptime_unreach = comptime_unreach: {
7984 if (tree.nodeTag(target_expr_node) != .@"comptime") break :comptime_unreach false;
7985 const comptime_node = tree.nodeData(target_expr_node).node;
7986 break :comptime_unreach tree.nodeTag(comptime_node) == .unreachable_literal;
7987 };
7988
7989 const prong_info: Zir.Inst.SwitchBlock.ProngInfo = .{
80167990 .body_len = @intCast(body_len),
80177991 .capture = capture,
80187992 .is_inline = case.inline_token != null,
80197993 .has_tag_capture = has_tag_capture,
8020 });
8021 appendBodyWithFixupsExtraRefsArrayList(astgen, payloads, case_slice, extra_insts);
7994 .is_comptime_unreach = is_comptime_unreach,
7995 };
7996
7997 if (is_multi_case) {
7998 payloads.items[multi_prong_body_table + multi_case_index] = body_start;
7999 payloads.items[multi_prong_infos_start + multi_case_index] = @bitCast(prong_info);
8000 multi_case_index += 1;
8001 } else {
8002 // prong body start is implicit, it's right behind our only item.
8003 payloads.items[scalar_prong_infos_start + scalar_case_index] = @bitCast(prong_info);
8004 scalar_case_index += 1;
8005 }
80228006 }
80238007 }
8008 assert(scalar_case_index + multi_case_index + @intFromBool(has_else) + @intFromBool(under_is_bare) == case_nodes.len);
8009 assert(multi_items_infos_start + multi_item_offset == bodies_start);
80248010
80258011 if (switch_full.label_token) |label_token| if (!block_scope.label.?.used) {
80268012 try astgen.appendErrorTok(label_token, "unused switch label", .{});
......@@ -8029,84 +8015,108 @@ fn switchExpr(
80298015 // Now that the item expressions are generated we can add this.
80308016 try parent_gz.instructions.append(gpa, switch_block);
80318017
8018 // We've collected all of the data we need! Now we just have to finalize it
8019 // by copying our bodies from `payloads` to `extra`, this time in the order
8020 // expected by ZIR consumers.
8021
80328022 try astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.SwitchBlock).@"struct".fields.len +
8033 @intFromBool(multi_cases_len != 0) +
8034 @intFromBool(any_has_tag_capture) +
8035 payloads.items.len - scratch_top);
8036
8037 const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.SwitchBlock{
8038 .operand = raw_operand,
8039 .bits = Zir.Inst.SwitchBlock.Bits{
8040 .has_multi_cases = multi_cases_len != 0,
8041 .special_prongs = special_prongs,
8042 .any_has_tag_capture = any_has_tag_capture,
8043 .any_non_inline_capture = any_non_inline_capture,
8023 @intFromBool(multi_cases_len > 0) + // multi_cases_len
8024 @intFromBool(payload_capture_inst_is_placeholder) + // payload_capture_placeholder
8025 @intFromBool(tag_capture_inst_is_placeholder) + // tag_capture_placeholder
8026 @intFromBool(needs_non_err_handling) + // catch_or_if_src_node_offset
8027 @intFromBool(needs_non_err_handling) + // non_err_info
8028 @intFromBool(has_else) + // else_info
8029 @intFromBool(has_under) + // under_prong_info or under_index
8030 payloads.items.len - body_table_end); // item infos and bodies
8031
8032 // singular pieces of data
8033 const zir_payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.SwitchBlock{
8034 .raw_operand = raw_operand,
8035 .bits = .{
8036 .has_multi_cases = multi_cases_len > 0,
8037 .any_ranges = any_ranges,
8038 .has_else = has_else,
8039 .has_under = has_under,
8040 .under_is_bare = under_is_bare,
80448041 .has_continue = switch_full.label_token != null and block_scope.label.?.used_for_continue,
8042 .any_maybe_runtime_capture = any_maybe_runtime_capture,
8043 .payload_capture_inst_is_placeholder = payload_capture_inst_is_placeholder,
8044 .tag_capture_inst_is_placeholder = tag_capture_inst_is_placeholder,
80458045 .scalar_cases_len = @intCast(scalar_cases_len),
80468046 },
80478047 });
8048 astgen.instructions.items(.data)[@intFromEnum(switch_block)].pl_node.payload_index = zir_payload_index;
80488049
8049 if (multi_cases_len != 0) {
8050 astgen.extra.appendAssumeCapacity(multi_cases_len);
8050 if (multi_cases_len > 0) astgen.extra.appendAssumeCapacity(multi_cases_len);
8051 if (payload_capture_inst_is_placeholder) astgen.extra.appendAssumeCapacity(@intFromEnum(payload_capture_inst));
8052 if (tag_capture_inst_is_placeholder) astgen.extra.appendAssumeCapacity(@intFromEnum(tag_capture_inst));
8053 if (needs_non_err_handling) {
8054 const catch_or_if_src_node_offset = parent_gz.nodeIndexToRelative(catch_or_if_node);
8055 astgen.extra.appendAssumeCapacity(@bitCast(@intFromEnum(catch_or_if_src_node_offset)));
8056 astgen.extra.appendAssumeCapacity(@bitCast(non_err_info));
80518057 }
8058 if (has_else) astgen.extra.appendAssumeCapacity(@bitCast(else_info));
8059 if (has_under) astgen.extra.appendAssumeCapacity(under_extra);
80528060
8053 if (any_has_tag_capture) {
8054 astgen.extra.appendAssumeCapacity(@intFromEnum(tag_inst));
8055 }
8061 const extra_payloads_start = astgen.extra.items.len;
80568062
8057 const zir_datas = astgen.instructions.items(.data);
8058 zir_datas[@intFromEnum(switch_block)].pl_node.payload_index = payload_index;
8063 // body lens
8064 astgen.extra.appendSliceAssumeCapacity(payloads.items[body_table_end..bodies_start]);
80598065
8066 // bodies
8067 if (needs_non_err_handling) {
8068 const body = payloads.items[non_err_prong_body_start..][0..non_err_info.body_len];
8069 astgen.extra.appendSliceAssumeCapacity(body);
8070 }
80608071 if (has_else) {
8061 const start_index = payloads.items[else_case_index];
8062 var end_index = start_index + 1;
8063 const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(payloads.items[start_index]);
8064 end_index += prong_info.body_len;
8065 astgen.extra.appendSliceAssumeCapacity(payloads.items[start_index..end_index]);
8066 }
8067 if (has_under) {
8068 const start_index = payloads.items[under_case_index];
8069 var body_len_index = start_index;
8070 var end_index = start_index;
8071 switch (underscore_additional_items) {
8072 .none => {
8073 end_index += 1;
8074 },
8075 .one => {
8076 body_len_index += 1;
8077 end_index += 2;
8078 },
8079 .many => {
8080 body_len_index += 2;
8081 const items_len = payloads.items[start_index];
8082 const ranges_len = payloads.items[start_index + 1];
8083 end_index += 3 + items_len + 2 * ranges_len;
8084 },
8085 }
8086 const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(payloads.items[body_len_index]);
8087 end_index += prong_info.body_len;
8088 astgen.extra.appendSliceAssumeCapacity(payloads.items[start_index..end_index]);
8089 }
8090 for (payloads.items[scalar_case_table..case_table_end], 0..) |start_index, i| {
8091 var body_len_index = start_index;
8092 var end_index = start_index;
8093 const table_index = scalar_case_table + i;
8094 if (table_index < multi_case_table) {
8095 body_len_index += 1;
8096 end_index += 2;
8097 } else {
8098 body_len_index += 2;
8099 const items_len = payloads.items[start_index];
8100 const ranges_len = payloads.items[start_index + 1];
8101 end_index += 3 + items_len + 2 * ranges_len;
8072 const body = payloads.items[else_prong_body_start..][0..else_info.body_len];
8073 astgen.extra.appendSliceAssumeCapacity(body);
8074 }
8075 if (under_is_bare) {
8076 const under_prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(under_extra);
8077 const body = payloads.items[bare_under_prong_body_start..][0..under_prong_info.body_len];
8078 astgen.extra.appendSliceAssumeCapacity(body);
8079 }
8080 for (0..scalar_cases_len) |scalar_i| {
8081 const item_info: Zir.Inst.SwitchBlock.ItemInfo = @bitCast(payloads.items[scalar_item_infos_start + scalar_i]);
8082 const item_body_start = payloads.items[scalar_body_table + scalar_i];
8083 const item_body = payloads.items[item_body_start..][0 .. item_info.bodyLen() orelse 0];
8084 const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(payloads.items[scalar_prong_infos_start + scalar_i]);
8085 const prong_body_start = item_body_start + item_body.len;
8086 const prong_body = payloads.items[prong_body_start..][0..prong_info.body_len];
8087 astgen.extra.appendSliceAssumeCapacity(prong_body);
8088 astgen.extra.appendSliceAssumeCapacity(item_body);
8089 }
8090 var multi_item_i: usize = 0;
8091 for (0..multi_cases_len) |multi_i| {
8092 const prong_body_start = payloads.items[multi_prong_body_table + multi_i];
8093 const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(payloads.items[multi_prong_infos_start + multi_i]);
8094 const prong_body = payloads.items[prong_body_start..][0..prong_info.body_len];
8095 astgen.extra.appendSliceAssumeCapacity(prong_body);
8096
8097 const items_len = payloads.items[multi_case_items_lens_start + multi_i];
8098 const ranges_len = if (any_ranges) ranges_len: {
8099 break :ranges_len payloads.items[multi_case_ranges_lens_start + multi_i];
8100 } else 0;
8101 // The table entries and body lens are already in the correct order so we
8102 // don't have to differentiate between items and ranges here.
8103 for (0..items_len + 2 * ranges_len) |_| {
8104 const item_info: Zir.Inst.SwitchBlock.ItemInfo = @bitCast(payloads.items[multi_items_infos_start + multi_item_i]);
8105 if (item_info.bodyLen()) |body_len| {
8106 const body_start = payloads.items[multi_item_body_table + multi_item_i];
8107 const body = payloads.items[body_start..][0..body_len];
8108 astgen.extra.appendSliceAssumeCapacity(body);
8109 }
8110 multi_item_i += 1;
81028111 }
8103 const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(payloads.items[body_len_index]);
8104 end_index += prong_info.body_len;
8105 astgen.extra.appendSliceAssumeCapacity(payloads.items[start_index..end_index]);
81068112 }
81078113
8114 // Make sure we didn't forget anything...
8115 assert(multi_item_i == total_items_len + 2 * total_ranges_len - scalar_cases_len);
8116 assert(astgen.extra.items.len - extra_payloads_start == payloads.items.len - body_table_end);
8117
81088118 if (need_result_rvalue) {
8109 return rvalue(parent_gz, ri, switch_block.toRef(), node);
8119 return rvalue(parent_gz, ri, switch_block.toRef(), switch_node);
81108120 } else {
81118121 return switch_block.toRef();
81128122 }
......@@ -13786,6 +13796,19 @@ fn scanContainer(
1378613796 return error.AnalysisFail;
1378713797}
1378813798
13799fn appendPlaceholder(astgen: *AstGen) Allocator.Error!Zir.Inst.Index {
13800 const inst: Zir.Inst.Index = @enumFromInt(astgen.instructions.len);
13801 try astgen.instructions.append(astgen.gpa, .{
13802 .tag = .extended,
13803 .data = .{ .extended = .{
13804 .opcode = .value_placeholder,
13805 .small = undefined,
13806 .operand = undefined,
13807 } },
13808 });
13809 return inst;
13810}
13811
1378913812/// Assumes capacity for body has already been added. Needed capacity taking into
1379013813/// account fixups can be found with `countBodyLenAfterFixups`.
1379113814fn appendBodyWithFixups(astgen: *AstGen, body: []const Zir.Inst.Index) void {
lib/std/zig/Zir.zig+482-291
......@@ -95,7 +95,6 @@ pub fn extraData(code: Zir, comptime T: type, index: usize) ExtraData(T) {
9595 Inst.Call.Flags,
9696 Inst.BuiltinCall.Flags,
9797 Inst.SwitchBlock.Bits,
98 Inst.SwitchBlockErrUnion.Bits,
9998 Inst.FuncFancy.Bits,
10099 Inst.Declaration.Flags,
101100 Inst.Param.Type,
......@@ -350,7 +349,8 @@ pub const Inst = struct {
350349 /// Uses the `break` union field.
351350 break_inline,
352351 /// Branch from within a switch case to the case specified by the operand.
353 /// Uses the `break` union field. `block_inst` refers to a `switch_block` or `switch_block_ref`.
352 /// Uses the `break` union field. `block_inst` refers to a `switch_block`/
353 /// `switch_block_ref`/`switch_block_err_union`.
354354 switch_continue,
355355 /// Checks that comptime control flow does not happen inside a runtime block.
356356 /// Uses the `un_node` union field.
......@@ -722,8 +722,10 @@ pub const Inst = struct {
722722 /// A switch expression. Uses the `pl_node` union field.
723723 /// AST node is the switch, payload is `SwitchBlock`. Operand is a pointer.
724724 switch_block_ref,
725 /// A switch on an error union `a catch |err| switch (err) {...}`.
726 /// Uses the `pl_node` union field. AST node is the `catch`, payload is `SwitchBlockErrUnion`.
725 /// A switch on an error union:
726 /// - `eu catch |err| switch (err) {...}`, AST node is the `catch`.
727 /// - `if (eu) |payload| {...} else |err| {...}`, AST node is the `if`.
728 /// Uses the `pl_node` union field. Payload is `SwitchBlock`.
727729 switch_block_err_union,
728730 /// Check that operand type supports the dereference operand (.*).
729731 /// Uses the `un_node` field.
......@@ -3293,143 +3295,168 @@ pub const Inst = struct {
32933295 };
32943296
32953297 /// Trailing:
3296 /// 0. multi_cases_len: u32 // if `has_multi_cases`
3297 /// 1. err_capture_inst: u32 // if `any_uses_err_capture`
3298 /// 2. non_err_body {
3299 /// info: ProngInfo,
3300 /// inst: Index // for every `info.body_len`
3301 /// }
3302 /// 3. else_body { // if `has_else`
3303 /// info: ProngInfo,
3304 /// inst: Index // for every `info.body_len`
3305 /// }
3306 /// 4. scalar_cases: { // for every `scalar_cases_len`
3307 /// item: Ref,
3308 /// info: ProngInfo,
3309 /// inst: Index // for every `info.body_len`
3298 /// 0. multi_cases_len: u32, // If has_multi_cases is set.
3299 /// 1. payload_capture_placeholder: Inst.Index, // If payload_capture_inst_is_placeholder is set.
3300 /// // Index of instruction prongs use to refer to their payload capture.
3301 /// 2. tag_capture_placeholder: Inst.Index, // If tag_capture_inst_is_placeholder is set.
3302 /// // Index of instruction prongs use to refer to their tag capture.
3303 /// 3. catch_or_if_src_node_offset: Ast.Node.Offset, // If inst is switch_block_err_union.
3304 /// 4. non_err_info: ProngInfo.NonErr, // If inst is switch_block_err_union.
3305 /// 5. else_info: ProngInfo.Else, // If has_else is set.
3306 /// 6. under_info: ProngInfo.Under, // If has_under is set and
3307 /// // under_is_bare is set.
3308 /// 7. under_index: u32, // If has_under is set and
3309 /// // under_is_bare is *not* set.
3310 /// // Index into switch cases.
3311 /// 8. scalar_prong_info: ProngInfo, // for every scalar_cases_len
3312 /// 9. multi_prong_info: ProngInfo, // for every multi_cases_len
3313 /// 10. multi_case_items_len: u32, // for every multi_cases_len
3314 /// 11. multi_case_ranges_len: u32, // If has_ranges is set: for every multi_cases_len
3315 /// 12. scalar_item_info: ItemInfo, // for every scalar_cases_len
3316 /// 13. multi_items_info: { // for every multi_cases_len
3317 /// item_info: ItemInfo, // for each multi_case_items_len
3318 /// range_items_info: { // for each multi_case_ranges_len
3319 /// first_info: ItemInfo,
3320 /// last_info: ItemInfo,
3321 /// }
3322 /// }
3323 /// 14. non_err_body {
3324 /// body_inst: Index // for every non_err_info.body_len
33103325 /// }
3311 /// 5. multi_cases: { // for every `multi_cases_len`
3312 /// items_len: u32,
3313 /// ranges_len: u32,
3314 /// info: ProngInfo,
3315 /// item: Ref // for every `items_len`
3316 /// ranges: { // for every `ranges_len`
3317 /// item_first: Ref,
3318 /// item_last: Ref,
3326 /// 15. else_body: { // If has_else is set.
3327 /// body_inst: Inst.Index, // for every else_info.body_len
3328 /// }
3329 /// 16. under_body: { // If has_under is set and
3330 /// // under_is_bare is set.
3331 /// body_inst: Inst.Index, // for every under_info.body_len
3332 /// }
3333 /// 17. scalar_bodies: { // for every scalar_cases_len
3334 /// prong_body: { // for each body_len in scalar_prong_info
3335 /// body_inst: Inst.Index, // for every body_len
3336 /// }
3337 /// item_body: { // for each body_len in scalar_item_info
3338 /// body_inst: Inst.Index, // for every body_len
33193339 /// }
3320 /// inst: Index // for every `info.body_len`
33213340 /// }
3322 ///
3323 /// When analyzing a case body, the switch instruction itself refers to the
3324 /// captured error, or to the success value in `non_err_body`. Whether this
3325 /// is captured by reference or by value depends on whether the `byref` bit
3326 /// is set for the corresponding body. `err_capture_inst` refers to the error
3327 /// capture outside of the `switch`, i.e. `err` in
3328 /// `x catch |err| switch (err) { ... }`.
3329 pub const SwitchBlockErrUnion = struct {
3330 operand: Ref,
3341 /// 18. multi_bodies: { // for each multi_items_info
3342 /// prong_body: {
3343 /// body_inst: Inst.Index, // for each multi_prong_info.body_len
3344 /// }
3345 /// item_body: { // for each item_info
3346 /// body_inst: Inst.Index, // for every item_info.body_len
3347 /// }
3348 /// range_bodies: { // for each .{first_info, last_info} in range_items_info
3349 /// first_body_inst: Inst.Index, // for every first_info.body_len
3350 /// last_body_inst: Inst.Index, // for every last_info.body_len
3351 /// }
3352 /// }
3353 pub const SwitchBlock = struct {
3354 /// Either `catch`/`if` or `switch` operand.
3355 raw_operand: Ref,
33313356 bits: Bits,
3332 main_src_node_offset: Ast.Node.Offset,
33333357
33343358 pub const Bits = packed struct(u32) {
33353359 /// If true, one or more prongs have multiple items.
33363360 has_multi_cases: bool,
3337 /// If true, there is an else prong. This is mutually exclusive with `has_under`.
3361 /// If true, one or more prongs have ranges.
3362 /// Only valid if `has_multi_cases` is also set.
3363 any_ranges: bool,
33383364 has_else: bool,
3339 any_uses_err_capture: bool,
3340 payload_is_ref: bool,
3365 has_under: bool,
3366 /// Only valid if `has_under` is also set.
3367 under_is_bare: bool,
3368 /// If true, at least one prong contains a `continue`.
3369 /// Only valid if `has_label` is set.
3370 has_continue: bool,
3371 // If true, at least one prong has a non-inline payload/tag capture.
3372 any_maybe_runtime_capture: bool,
3373 payload_capture_inst_is_placeholder: bool,
3374 tag_capture_inst_is_placeholder: bool,
33413375 scalar_cases_len: ScalarCasesLen,
33423376
3343 pub const ScalarCasesLen = u28;
3377 // NOTE maybe don't steal any more bits from poor `scalar_cases_len`
3378 // and split `Bits` into two parts instead, `raw_operand` surely
3379 // wouldn't mind donating a couple of bits for that purpose...
3380 pub const ScalarCasesLen = u23;
33443381 };
33453382
3346 pub const MultiProng = struct {
3347 items: []const Ref,
3348 body: []const Index,
3349 };
3350 };
3351
3352 /// 0. multi_cases_len: u32 // If has_multi_cases is set.
3353 /// 1. tag_capture_inst: u32 // If any_has_tag_capture is set. Index of instruction prongs use to refer to the inline tag capture.
3354 /// 2. else_body { // If special_prong.hasElse() is set.
3355 /// info: ProngInfo,
3356 /// body member Index for every info.body_len
3357 /// }
3358 /// 3. under_body { // If special_prong.hasUnder() is set.
3359 /// item: Ref, // If special_prong.hasOneAdditionalItem() is set.
3360 /// items_len: u32, // If special_prong.hasManyAdditionalItems() is set.
3361 /// ranges_len: u32, // If special_prong.hasManyAdditionalItems() is set.
3362 /// info: ProngInfo,
3363 /// item: Ref, // for every items_len
3364 /// ranges: { // for every ranges_len
3365 /// item_first: Ref,
3366 /// item_last: Ref,
3367 /// }
3368 /// body member Index for every info.body_len
3369 /// }
3370 /// 4. scalar_cases: { // for every scalar_cases_len
3371 /// item: Ref,
3372 /// info: ProngInfo,
3373 /// body member Index for every info.body_len
3374 /// }
3375 /// 5. multi_cases: { // for every multi_cases_len
3376 /// items_len: u32,
3377 /// ranges_len: u32,
3378 /// info: ProngInfo,
3379 /// item: Ref, // for every items_len
3380 /// ranges: { // for every ranges_len
3381 /// item_first: Ref,
3382 /// item_last: Ref,
3383 /// }
3384 /// body member Index for every info.body_len
3385 /// }
3386 ///
3387 /// When analyzing a case body, the switch instruction itself refers to the
3388 /// captured payload. Whether this is captured by reference or by value
3389 /// depends on whether the `byref` bit is set for the corresponding body.
3390 pub const SwitchBlock = struct {
3391 /// The operand passed to the `switch` expression. If this is a
3392 /// `switch_block`, this is the operand value; if `switch_block_ref` it
3393 /// is a pointer to the operand. `switch_block_ref` is always used if
3394 /// any prong has a byref capture.
3395 operand: Ref,
3396 bits: Bits,
3397
3398 /// These are stored in trailing data in `extra` for each prong.
33993383 pub const ProngInfo = packed struct(u32) {
3400 body_len: u28,
3384 body_len: u27,
34013385 capture: ProngInfo.Capture,
34023386 is_inline: bool,
34033387 has_tag_capture: bool,
3388 is_comptime_unreach: bool,
34043389
34053390 pub const Capture = enum(u2) {
34063391 none,
34073392 by_val,
34083393 by_ref,
34093394 };
3410 };
34113395
3412 pub const Bits = packed struct(u32) {
3413 /// If true, one or more prongs have multiple items.
3414 has_multi_cases: bool,
3415 /// Information about the special prong.
3416 special_prongs: SpecialProngs,
3417 /// If true, at least one prong has an inline tag capture.
3418 any_has_tag_capture: bool,
3419 /// If true, at least one prong has a capture which may not
3420 /// be comptime-known via `inline`.
3421 any_non_inline_capture: bool,
3422 /// If true, at least one prong contains a `continue`.
3423 has_continue: bool,
3424 scalar_cases_len: ScalarCasesLen,
3396 pub const NonErr = packed struct(u32) {
3397 body_len: u29,
3398 capture: ProngInfo.Capture,
3399 operand_is_ref: bool,
3400 };
34253401
3426 pub const ScalarCasesLen = u25;
3402 pub const Else = packed struct(u32) {
3403 body_len: u27,
3404 capture: ProngInfo.Capture,
3405 is_inline: bool,
3406 has_tag_capture: bool,
3407 is_simple_noreturn: bool,
3408 };
3409
3410 pub const BareUnder = packed struct(u32) {
3411 body_len: u29,
3412 capture: ProngInfo.Capture,
3413 has_tag_capture: bool,
3414 };
34273415 };
34283416
3429 pub const MultiProng = struct {
3430 items: []const Ref,
3431 body: []const Index,
3417 pub const ItemInfo = packed struct(u32) {
3418 kind: ItemInfo.Kind,
3419 data: u30,
3420
3421 pub const Kind = enum(u2) {
3422 enum_literal,
3423 error_value,
3424 number_literal,
3425 body_len,
3426 };
3427
3428 pub const Unwrapped = union(ItemInfo.Kind) {
3429 enum_literal: Zir.NullTerminatedString,
3430 error_value: Zir.NullTerminatedString,
3431 number_literal: Inst.Ref,
3432 body_len: u32,
3433 };
3434
3435 pub fn wrap(unwrapped: ItemInfo.Unwrapped) ItemInfo {
3436 const data_uncasted: u32 = switch (unwrapped) {
3437 .enum_literal => |str_index| @intFromEnum(str_index),
3438 .error_value => |str_index| @intFromEnum(str_index),
3439 .number_literal => |zir_ref| @intFromEnum(zir_ref),
3440 .body_len => |body_len| body_len,
3441 };
3442 return .{ .kind = unwrapped, .data = @intCast(data_uncasted) };
3443 }
3444
3445 pub fn unwrap(item_info: ItemInfo) ItemInfo.Unwrapped {
3446 return switch (item_info.kind) {
3447 .enum_literal => .{ .enum_literal = @enumFromInt(item_info.data) },
3448 .error_value => .{ .error_value = @enumFromInt(item_info.data) },
3449 .number_literal => .{ .number_literal = @enumFromInt(item_info.data) },
3450 .body_len => .{ .body_len = item_info.data },
3451 };
3452 }
3453
3454 pub fn bodyLen(item_info: ItemInfo) ?u32 {
3455 return if (item_info.kind == .body_len) item_info.data else null;
3456 }
34323457 };
3458
3459 pub const Kind = enum { default, ref, err_union };
34333460 };
34343461
34353462 pub const ArrayInitRefTy = struct {
......@@ -4004,69 +4031,6 @@ pub const Inst = struct {
40044031 };
40054032};
40064033
4007pub const SpecialProngs = enum(u3) {
4008 none = 0b000,
4009 /// Simple `else` prong.
4010 /// `else => {},`
4011 @"else" = 0b001,
4012 /// Simple `_` prong.
4013 /// `_ => {},`
4014 under = 0b010,
4015 /// Both an `else` and a `_` prong.
4016 /// `else => {},`
4017 /// `_ => {},`
4018 under_and_else = 0b011,
4019 /// `_` prong with 1 additional item.
4020 /// `a, _ => {},`
4021 under_one_item = 0b100,
4022 /// Both an `else` and a `_` prong with 1 additional item.
4023 /// `else => {},`
4024 /// `a, _ => {},`
4025 under_one_item_and_else = 0b101,
4026 /// `_` prong with >1 additional items.
4027 /// `a, _, b => {},`
4028 under_many_items = 0b110,
4029 /// Both an `else` and a `_` prong with >1 additional items.
4030 /// `else => {},`
4031 /// `a, _, b => {},`
4032 under_many_items_and_else = 0b111,
4033
4034 pub const AdditionalItems = enum(u3) {
4035 none = @intFromEnum(SpecialProngs.under),
4036 one = @intFromEnum(SpecialProngs.under_one_item),
4037 many = @intFromEnum(SpecialProngs.under_many_items),
4038 };
4039
4040 pub fn init(has_else: bool, has_under: bool, additional_items: AdditionalItems) SpecialProngs {
4041 const else_bit: u3 = @intFromBool(has_else);
4042 const under_bits: u3 = if (has_under)
4043 @intFromEnum(additional_items)
4044 else
4045 @intFromEnum(SpecialProngs.none);
4046 return @enumFromInt(else_bit | under_bits);
4047 }
4048
4049 pub fn hasElse(special_prongs: SpecialProngs) bool {
4050 return (@intFromEnum(special_prongs) & 0b001) != 0;
4051 }
4052
4053 pub fn hasUnder(special_prongs: SpecialProngs) bool {
4054 return (@intFromEnum(special_prongs) & 0b110) != 0;
4055 }
4056
4057 pub fn hasAdditionalItems(special_prongs: SpecialProngs) bool {
4058 return (@intFromEnum(special_prongs) & 0b100) != 0;
4059 }
4060
4061 pub fn hasOneAdditionalItem(special_prongs: SpecialProngs) bool {
4062 return (@intFromEnum(special_prongs) & 0b110) == @intFromEnum(SpecialProngs.under_one_item);
4063 }
4064
4065 pub fn hasManyAdditionalItems(special_prongs: SpecialProngs) bool {
4066 return (@intFromEnum(special_prongs) & 0b110) == @intFromEnum(SpecialProngs.under_many_items);
4067 }
4068};
4069
40704034pub const DeclIterator = struct {
40714035 extra_index: u32,
40724036 decls_remaining: u32,
......@@ -4842,8 +4806,48 @@ fn findTrackableInner(
48424806 const body = zir.bodySlice(extra.end, extra.data.body_len);
48434807 try zir.findTrackableBody(gpa, contents, defers, body);
48444808 },
4845 .switch_block, .switch_block_ref => return zir.findTrackableSwitch(gpa, contents, defers, inst, .normal),
4846 .switch_block_err_union => return zir.findTrackableSwitch(gpa, contents, defers, inst, .err_union),
4809
4810 .switch_block,
4811 .switch_block_ref,
4812 .switch_block_err_union,
4813 => {
4814 const zir_switch = zir.getSwitchBlock(inst);
4815 if (zir_switch.non_err_case) |non_err_case| {
4816 try zir.findTrackableBody(gpa, contents, defers, non_err_case.body);
4817 }
4818 if (zir_switch.else_case) |else_case| {
4819 try zir.findTrackableBody(gpa, contents, defers, else_case.body);
4820 }
4821 if (zir_switch.under_case.resolve()) |under_case| {
4822 try zir.findTrackableBody(gpa, contents, defers, under_case.body);
4823 }
4824 var extra_index = zir_switch.end;
4825 var case_it = zir_switch.iterateCases();
4826 while (case_it.next()) |case| {
4827 const prong_body = zir.bodySlice(extra_index, case.prong_info.body_len);
4828 extra_index += prong_body.len;
4829 try zir.findTrackableBody(gpa, contents, defers, prong_body);
4830 for (case.item_infos) |item_info| {
4831 if (item_info.bodyLen()) |body_len| {
4832 const item_body = zir.bodySlice(extra_index, body_len);
4833 extra_index += item_body.len;
4834 try zir.findTrackableBody(gpa, contents, defers, item_body);
4835 }
4836 }
4837 for (case.range_infos) |range_info| {
4838 if (range_info[0].bodyLen()) |body_len| {
4839 const first_body = zir.bodySlice(extra_index, body_len);
4840 extra_index += first_body.len;
4841 try zir.findTrackableBody(gpa, contents, defers, first_body);
4842 }
4843 if (range_info[1].bodyLen()) |body_len| {
4844 const last_body = zir.bodySlice(extra_index, body_len);
4845 extra_index += last_body.len;
4846 try zir.findTrackableBody(gpa, contents, defers, last_body);
4847 }
4848 }
4849 }
4850 },
48474851
48484852 .suspend_block => @panic("TODO iterate suspend block"),
48494853
......@@ -4890,119 +4894,6 @@ fn findTrackableInner(
48904894 }
48914895}
48924896
4893fn findTrackableSwitch(
4894 zir: Zir,
4895 gpa: Allocator,
4896 contents: *DeclContents,
4897 defers: *std.AutoHashMapUnmanaged(u32, void),
4898 inst: Inst.Index,
4899 /// Distinguishes between `switch_block[_ref]` and `switch_block_err_union`.
4900 comptime kind: enum { normal, err_union },
4901) Allocator.Error!void {
4902 const inst_data = zir.instructions.items(.data)[@intFromEnum(inst)].pl_node;
4903 const extra = zir.extraData(switch (kind) {
4904 .normal => Inst.SwitchBlock,
4905 .err_union => Inst.SwitchBlockErrUnion,
4906 }, inst_data.payload_index);
4907
4908 var extra_index: usize = extra.end;
4909
4910 const multi_cases_len = if (extra.data.bits.has_multi_cases) blk: {
4911 const multi_cases_len = zir.extra[extra_index];
4912 extra_index += 1;
4913 break :blk multi_cases_len;
4914 } else 0;
4915
4916 if (switch (kind) {
4917 .normal => extra.data.bits.any_has_tag_capture,
4918 .err_union => extra.data.bits.any_uses_err_capture,
4919 }) {
4920 extra_index += 1;
4921 }
4922
4923 const has_special = switch (kind) {
4924 .normal => extra.data.bits.special_prongs != .none,
4925 .err_union => has_special: {
4926 // Handle `non_err_body` first.
4927 const prong_info: Inst.SwitchBlock.ProngInfo = @bitCast(zir.extra[extra_index]);
4928 extra_index += 1;
4929 const body = zir.bodySlice(extra_index, prong_info.body_len);
4930 extra_index += body.len;
4931
4932 try zir.findTrackableBody(gpa, contents, defers, body);
4933
4934 break :has_special extra.data.bits.has_else;
4935 },
4936 };
4937
4938 if (has_special) {
4939 const has_else = if (kind == .normal)
4940 extra.data.bits.special_prongs.hasElse()
4941 else
4942 true;
4943 if (has_else) {
4944 const prong_info: Inst.SwitchBlock.ProngInfo = @bitCast(zir.extra[extra_index]);
4945 extra_index += 1;
4946 const body = zir.bodySlice(extra_index, prong_info.body_len);
4947 extra_index += body.len;
4948
4949 try zir.findTrackableBody(gpa, contents, defers, body);
4950 }
4951 if (kind == .normal) {
4952 const special_prongs = extra.data.bits.special_prongs;
4953
4954 if (special_prongs.hasUnder()) {
4955 var trailing_items_len: u32 = 0;
4956 if (special_prongs.hasOneAdditionalItem()) {
4957 extra_index += 1;
4958 } else if (special_prongs.hasManyAdditionalItems()) {
4959 const items_len = zir.extra[extra_index];
4960 extra_index += 1;
4961 const ranges_len = zir.extra[extra_index];
4962 extra_index += 1;
4963 trailing_items_len = items_len + ranges_len * 2;
4964 }
4965 const prong_info: Inst.SwitchBlock.ProngInfo = @bitCast(zir.extra[extra_index]);
4966 extra_index += 1 + trailing_items_len;
4967 const body = zir.bodySlice(extra_index, prong_info.body_len);
4968 extra_index += body.len;
4969
4970 try zir.findTrackableBody(gpa, contents, defers, body);
4971 }
4972 }
4973 }
4974
4975 {
4976 const scalar_cases_len = extra.data.bits.scalar_cases_len;
4977 for (0..scalar_cases_len) |_| {
4978 extra_index += 1;
4979 const prong_info: Inst.SwitchBlock.ProngInfo = @bitCast(zir.extra[extra_index]);
4980 extra_index += 1;
4981 const body = zir.bodySlice(extra_index, prong_info.body_len);
4982 extra_index += body.len;
4983
4984 try zir.findTrackableBody(gpa, contents, defers, body);
4985 }
4986 }
4987 {
4988 for (0..multi_cases_len) |_| {
4989 const items_len = zir.extra[extra_index];
4990 extra_index += 1;
4991 const ranges_len = zir.extra[extra_index];
4992 extra_index += 1;
4993 const prong_info: Inst.SwitchBlock.ProngInfo = @bitCast(zir.extra[extra_index]);
4994 extra_index += 1;
4995
4996 extra_index += items_len + ranges_len * 2;
4997
4998 const body = zir.bodySlice(extra_index, prong_info.body_len);
4999 extra_index += body.len;
5000
5001 try zir.findTrackableBody(gpa, contents, defers, body);
5002 }
5003 }
5004}
5005
50064897fn findTrackableBody(
50074898 zir: Zir,
50084899 gpa: Allocator,
......@@ -5337,6 +5228,306 @@ pub fn getAssociatedSrcHash(zir: Zir, inst: Zir.Inst.Index) ?std.zig.SrcHash {
53375228 }
53385229}
53395230
5231pub fn getSwitchBlock(zir: *const Zir, switch_inst: Inst.Index) UnwrappedSwitchBlock {
5232 const has_non_err = switch (zir.instructions.items(.tag)[@intFromEnum(switch_inst)]) {
5233 .switch_block, .switch_block_ref => false,
5234 .switch_block_err_union => true,
5235 else => unreachable,
5236 };
5237 const inst_data = zir.instructions.items(.data)[@intFromEnum(switch_inst)].pl_node;
5238 const extra = zir.extraData(Inst.SwitchBlock, inst_data.payload_index);
5239 const bits = extra.data.bits;
5240 var extra_index = extra.end;
5241 const multi_cases_len = if (bits.has_multi_cases) len: {
5242 const multi_cases_len = zir.extra[extra_index];
5243 extra_index += 1;
5244 break :len multi_cases_len;
5245 } else 0;
5246 const payload_capture_placeholder: Inst.OptionalIndex = if (bits.payload_capture_inst_is_placeholder) inst: {
5247 const inst: Inst.Index = @enumFromInt(zir.extra[extra_index]);
5248 extra_index += 1;
5249 break :inst inst.toOptional();
5250 } else .none;
5251 const tag_capture_placeholder: Inst.OptionalIndex = if (bits.tag_capture_inst_is_placeholder) inst: {
5252 const inst: Inst.Index = @enumFromInt(zir.extra[extra_index]);
5253 extra_index += 1;
5254 break :inst inst.toOptional();
5255 } else .none;
5256 const catch_or_if_src_node_offset: Ast.Node.OptionalOffset = if (has_non_err) node_offset: {
5257 const node_offset: Ast.Node.Offset = @enumFromInt(@as(i32, @bitCast(zir.extra[extra_index])));
5258 extra_index += 1;
5259 break :node_offset node_offset.toOptional();
5260 } else .none;
5261 const non_err_info: Inst.SwitchBlock.ProngInfo.NonErr = if (has_non_err) non_err_info: {
5262 const non_err_info: Inst.SwitchBlock.ProngInfo.NonErr = @bitCast(zir.extra[extra_index]);
5263 extra_index += 1;
5264 break :non_err_info non_err_info;
5265 } else undefined;
5266 const else_info: Inst.SwitchBlock.ProngInfo.Else = if (bits.has_else) else_info: {
5267 const else_info: Inst.SwitchBlock.ProngInfo.Else = @bitCast(zir.extra[extra_index]);
5268 extra_index += 1;
5269 break :else_info else_info;
5270 } else undefined;
5271 const bare_under_info: Inst.SwitchBlock.ProngInfo.BareUnder = if (bits.has_under and bits.under_is_bare) bare_under_info: {
5272 const bare_under_info: Inst.SwitchBlock.ProngInfo.BareUnder = @bitCast(zir.extra[extra_index]);
5273 extra_index += 1;
5274 break :bare_under_info bare_under_info;
5275 } else undefined;
5276 const under_index: u32 = if (bits.has_under and !bits.under_is_bare) under_index: {
5277 const under_index = zir.extra[extra_index];
5278 extra_index += 1;
5279 break :under_index under_index;
5280 } else undefined;
5281 const scalar_cases_len: u32 = bits.scalar_cases_len;
5282 const prong_infos: []const Inst.SwitchBlock.ProngInfo =
5283 @ptrCast(zir.extra[extra_index..][0 .. scalar_cases_len + multi_cases_len]);
5284 extra_index += prong_infos.len;
5285 const multi_case_items_lens = zir.extra[extra_index..][0..multi_cases_len];
5286 extra_index += multi_case_items_lens.len;
5287 const multi_case_ranges_lens: ?[]const u32 = if (bits.any_ranges) lens: {
5288 const multi_case_ranges_lens = zir.extra[extra_index..][0..multi_cases_len];
5289 extra_index += multi_case_ranges_lens.len;
5290 break :lens multi_case_ranges_lens;
5291 } else null;
5292 var total_items_len: usize = scalar_cases_len;
5293 for (multi_case_items_lens) |items_len| {
5294 total_items_len += items_len;
5295 }
5296 if (multi_case_ranges_lens) |ranges_lens| for (ranges_lens) |ranges_len| {
5297 total_items_len += 2 * ranges_len;
5298 };
5299 const item_infos: []const Inst.SwitchBlock.ItemInfo =
5300 @ptrCast(zir.extra[extra_index..][0..total_items_len]);
5301 extra_index += item_infos.len;
5302 const non_err_case: ?UnwrappedSwitchBlock.Case.NonErr = if (has_non_err) non_err_case: {
5303 const body = zir.bodySlice(extra_index, non_err_info.body_len);
5304 extra_index += body.len;
5305 break :non_err_case .{
5306 .body = body,
5307 .capture = non_err_info.capture,
5308 .operand_is_ref = non_err_info.operand_is_ref,
5309 };
5310 } else null;
5311 const else_case: ?UnwrappedSwitchBlock.Case.Else = if (bits.has_else) else_case: {
5312 const body = zir.bodySlice(extra_index, else_info.body_len);
5313 extra_index += body.len;
5314 break :else_case .{
5315 .index = .@"else",
5316 .body = body,
5317 .capture = else_info.capture,
5318 .is_inline = else_info.is_inline,
5319 .has_tag_capture = else_info.has_tag_capture,
5320 .is_simple_noreturn = else_info.is_simple_noreturn,
5321 };
5322 } else null;
5323 const under_case: UnwrappedSwitchBlock.Case.Under = if (bits.has_under) under_case: {
5324 if (bits.under_is_bare) {
5325 const body = zir.bodySlice(extra_index, bare_under_info.body_len);
5326 extra_index += body.len;
5327 break :under_case .{ .bare = .{
5328 .index = .bare_under,
5329 .body = body,
5330 .capture = bare_under_info.capture,
5331 .has_tag_capture = bare_under_info.has_tag_capture,
5332 } };
5333 } else {
5334 break :under_case .{ .index = under_index };
5335 }
5336 } else .none;
5337 return .{
5338 .main_operand = extra.data.raw_operand,
5339 .switch_src_node_offset = inst_data.src_node,
5340 .catch_or_if_src_node_offset = catch_or_if_src_node_offset,
5341 .payload_capture_placeholder = payload_capture_placeholder,
5342 .tag_capture_placeholder = tag_capture_placeholder,
5343 .has_continue = bits.has_continue,
5344 .any_maybe_runtime_capture = bits.any_maybe_runtime_capture,
5345 .non_err_case = non_err_case,
5346 .else_case = else_case,
5347 .under_case = under_case,
5348 .prong_infos = prong_infos,
5349 .multi_case_items_lens = multi_case_items_lens,
5350 .multi_case_ranges_lens = multi_case_ranges_lens,
5351 .item_infos = item_infos,
5352 .end = extra_index,
5353 };
5354}
5355
5356/// Trailing (starting at `end`):
5357/// 0. case_bodies: { // for each case in Case.Iterator.next()
5358/// prong_body: {
5359/// body_inst: Inst.Index, // for every case.prong_info.body_len,
5360/// }
5361/// item_body: { // for each body_len in case.item_infos
5362/// body_inst: Inst.Index, // for every body_len
5363/// }
5364/// range_bodies: { // for each .{first_info, last_info} in case.range_infos
5365/// first_body_inst: Inst.Index, // for every first_info.body_len
5366/// last_body_inst: Inst.Index, // for every last_info.body_len
5367/// }
5368/// }
5369pub const UnwrappedSwitchBlock = struct {
5370 /// Either `catch`/`if` or `switch` operand.
5371 main_operand: Inst.Ref,
5372 switch_src_node_offset: Ast.Node.Offset,
5373 catch_or_if_src_node_offset: Ast.Node.OptionalOffset,
5374 payload_capture_placeholder: Inst.OptionalIndex,
5375 tag_capture_placeholder: Inst.OptionalIndex,
5376 has_continue: bool,
5377 any_maybe_runtime_capture: bool,
5378 non_err_case: ?Case.NonErr,
5379 else_case: ?Case.Else,
5380 under_case: Case.Under,
5381 // Refer to doc comment and `iterateCases` to access everything below correctly.
5382 prong_infos: []const Inst.SwitchBlock.ProngInfo,
5383 multi_case_items_lens: []const u32,
5384 multi_case_ranges_lens: ?[]const u32,
5385 item_infos: []const Inst.SwitchBlock.ItemInfo,
5386 end: usize,
5387
5388 pub fn anyRanges(unwrapped: *const UnwrappedSwitchBlock) bool {
5389 return unwrapped.multi_case_ranges_lens != null;
5390 }
5391
5392 pub fn scalarCasesLen(unwrapped: *const UnwrappedSwitchBlock) u32 {
5393 return @intCast(unwrapped.prong_infos.len - unwrapped.multi_case_items_lens.len);
5394 }
5395
5396 pub fn multiCasesLen(unwrapped: *const UnwrappedSwitchBlock) u32 {
5397 return @intCast(unwrapped.multi_case_items_lens.len);
5398 }
5399
5400 pub fn totalItemsLen(unwrapped: *const UnwrappedSwitchBlock) u32 {
5401 var total_items_len: u32 = @intCast(unwrapped.item_infos.len);
5402 if (unwrapped.multi_case_ranges_lens) |ranges_lens| {
5403 for (ranges_lens) |len| total_items_len -= len;
5404 }
5405 return total_items_len;
5406 }
5407
5408 pub const Case = struct {
5409 index: Case.Index,
5410 prong_info: Inst.SwitchBlock.ProngInfo,
5411 item_infos: []const Inst.SwitchBlock.ItemInfo,
5412 range_infos: []const [2]Inst.SwitchBlock.ItemInfo,
5413
5414 pub fn isUnder(case: *const Case) bool {
5415 return case.index.is_under;
5416 }
5417
5418 pub const Index = packed struct(u32) {
5419 kind: enum(u1) { scalar, multi },
5420 is_under: bool,
5421 value: u30,
5422
5423 pub const @"else": Case.Index = .{
5424 .kind = .scalar,
5425 .is_under = false,
5426 .value = std.math.maxInt(u30),
5427 };
5428
5429 pub const bare_under: Case.Index = .{
5430 .kind = .scalar,
5431 .is_under = true,
5432 .value = std.math.maxInt(u30),
5433 };
5434 };
5435
5436 pub const NonErr = struct {
5437 body: []const Inst.Index,
5438 capture: Inst.SwitchBlock.ProngInfo.Capture,
5439 operand_is_ref: bool,
5440 };
5441
5442 pub const Else = struct {
5443 index: Case.Index,
5444 body: []const Inst.Index,
5445 capture: Inst.SwitchBlock.ProngInfo.Capture,
5446 is_inline: bool,
5447 has_tag_capture: bool,
5448 is_simple_noreturn: bool,
5449 };
5450
5451 pub const Under = union(enum) {
5452 none,
5453 bare: Under.Resolved,
5454 index: u32,
5455
5456 pub const Resolved = struct {
5457 index: Case.Index,
5458 body: []const Inst.Index,
5459 capture: Inst.SwitchBlock.ProngInfo.Capture,
5460 has_tag_capture: bool,
5461 };
5462
5463 /// If this returns `null` and `under` is not `.none`, you'll have to
5464 /// find the under case by iterating all cases and using `isUnder`!
5465 pub fn resolve(under: Under) ?Under.Resolved {
5466 return switch (under) {
5467 .bare => |resolved| resolved,
5468 .none, .index => null,
5469 };
5470 }
5471 };
5472
5473 pub const Iterator = struct {
5474 next_idx: u32,
5475 under_idx: ?u32,
5476 prong_infos: []const Inst.SwitchBlock.ProngInfo,
5477 multi_case_items_lens: []const u32,
5478 multi_case_ranges_lens: ?[]const u32,
5479 item_infos: []const Inst.SwitchBlock.ItemInfo,
5480
5481 pub fn next(it: *Iterator) ?Case {
5482 const idx = it.next_idx;
5483 if (idx == it.prong_infos.len) return null;
5484 it.next_idx += 1;
5485 const scalar_cases_len = it.prong_infos.len - it.multi_case_items_lens.len;
5486 return if (idx < scalar_cases_len) .{
5487 .index = .{
5488 .kind = .scalar,
5489 .is_under = idx == it.under_idx,
5490 .value = @intCast(idx),
5491 },
5492 .prong_info = it.prong_infos[idx],
5493 .item_infos = it.itemInfos(1),
5494 .range_infos = &.{},
5495 } else .{
5496 .index = .{
5497 .kind = .multi,
5498 .is_under = idx == it.under_idx,
5499 .value = @intCast(idx - scalar_cases_len),
5500 },
5501 .prong_info = it.prong_infos[idx],
5502 .item_infos = it.itemInfos(it.multi_case_items_lens[idx - scalar_cases_len]),
5503 .range_infos = if (it.multi_case_ranges_lens) |ranges_lens| b: {
5504 break :b @ptrCast(it.itemInfos(2 * ranges_lens[idx - scalar_cases_len]));
5505 } else &.{},
5506 };
5507 }
5508 fn itemInfos(it: *Iterator, count: u32) []const Inst.SwitchBlock.ItemInfo {
5509 const lens = it.item_infos[0..count];
5510 it.item_infos = it.item_infos[count..];
5511 return lens;
5512 }
5513 };
5514 };
5515
5516 pub fn iterateCases(unwrapped: UnwrappedSwitchBlock) Case.Iterator {
5517 return .{
5518 .next_idx = 0,
5519 .under_idx = switch (unwrapped.under_case) {
5520 .none, .bare => null,
5521 .index => |index| index,
5522 },
5523 .prong_infos = unwrapped.prong_infos,
5524 .multi_case_items_lens = unwrapped.multi_case_items_lens,
5525 .multi_case_ranges_lens = unwrapped.multi_case_ranges_lens,
5526 .item_infos = unwrapped.item_infos,
5527 };
5528 }
5529};
5530
53405531/// When the ZIR update tracking logic must be modified to consider new instructions,
53415532/// change this constant to trigger compile errors at all relevant locations.
53425533pub const inst_tracking_version = 0;
src/print_zir.zig+111-284
......@@ -447,10 +447,9 @@ const Writer = struct {
447447
448448 .switch_block,
449449 .switch_block_ref,
450 .switch_block_err_union,
450451 => try self.writeSwitchBlock(stream, inst),
451452
452 .switch_block_err_union => try self.writeSwitchBlockErrUnion(stream, inst),
453
454453 .field_ptr_load,
455454 .field_ptr,
456455 .decl_literal,
......@@ -1987,322 +1986,150 @@ const Writer = struct {
19871986 try self.writeSrcNode(stream, inst_data.src_node);
19881987 }
19891988
1990 fn writeSwitchBlockErrUnion(self: *Writer, stream: *std.Io.Writer, inst: Zir.Inst.Index) !void {
1991 const inst_data = self.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
1992 const extra = self.code.extraData(Zir.Inst.SwitchBlockErrUnion, inst_data.payload_index);
1993
1994 var extra_index: usize = extra.end;
1995
1996 const multi_cases_len = if (extra.data.bits.has_multi_cases) blk: {
1997 const multi_cases_len = self.code.extra[extra_index];
1998 extra_index += 1;
1999 break :blk multi_cases_len;
2000 } else 0;
2001
2002 const err_capture_inst: Zir.Inst.Index = if (extra.data.bits.any_uses_err_capture) blk: {
2003 const tag_capture_inst = self.code.extra[extra_index];
2004 extra_index += 1;
2005 break :blk @enumFromInt(tag_capture_inst);
2006 } else undefined;
2007
2008 try self.writeInstRef(stream, extra.data.operand);
1989 fn writeSwitchBlock(
1990 self: *Writer,
1991 stream: *std.Io.Writer,
1992 inst: Zir.Inst.Index,
1993 ) !void {
1994 const zir_switch = self.code.getSwitchBlock(inst);
1995 var extra_index = zir_switch.end;
20091996
2010 if (extra.data.bits.any_uses_err_capture) {
2011 try stream.writeAll(", err_capture=");
2012 try self.writeInstIndex(stream, err_capture_inst);
2013 }
1997 try self.writeInstRef(stream, zir_switch.main_operand);
20141998
20151999 self.indent += 2;
20162000
2017 {
2018 const info = @as(Zir.Inst.SwitchBlock.ProngInfo, @bitCast(self.code.extra[extra_index]));
2019 extra_index += 1;
2020
2021 assert(!info.is_inline);
2022 const body = self.code.bodySlice(extra_index, info.body_len);
2023 extra_index += body.len;
2001 if (zir_switch.non_err_case) |non_err_case| {
2002 if (non_err_case.operand_is_ref) try stream.writeAll(" ref");
20242003
20252004 try stream.writeAll(",\n");
20262005 try stream.splatByteAll(' ', self.indent);
2027 try stream.writeAll("non_err => ");
2028 try self.writeBracedBody(stream, body);
2029 }
20302006
2031 if (extra.data.bits.has_else) {
2032 const info = @as(Zir.Inst.SwitchBlock.ProngInfo, @bitCast(self.code.extra[extra_index]));
2033 extra_index += 1;
2034 const capture_text = switch (info.capture) {
2035 .none => "",
2036 .by_val => "by_val ",
2037 .by_ref => "by_ref ",
2038 };
2039 const inline_text = if (info.is_inline) "inline " else "";
2040 const body = self.code.bodySlice(extra_index, info.body_len);
2041 extra_index += body.len;
2007 try self.writeSwitchCaptures(stream, non_err_case.capture, false, inst, &zir_switch);
20422008
2009 try stream.writeAll("non_err => ");
2010 try self.writeBracedBody(stream, non_err_case.body);
2011 try stream.writeAll(" ");
2012 try self.writeSrcNode(stream, zir_switch.catch_or_if_src_node_offset.unwrap().?);
2013 }
2014 if (zir_switch.else_case) |else_case| {
20432015 try stream.writeAll(",\n");
20442016 try stream.splatByteAll(' ', self.indent);
2045 try stream.print("{s}{s}else => ", .{ capture_text, inline_text });
2046 try self.writeBracedBody(stream, body);
2047 }
2048
2049 {
2050 const scalar_cases_len = extra.data.bits.scalar_cases_len;
2051 var scalar_i: usize = 0;
2052 while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
2053 const item_ref = @as(Zir.Inst.Ref, @enumFromInt(self.code.extra[extra_index]));
2054 extra_index += 1;
2055 const info = @as(Zir.Inst.SwitchBlock.ProngInfo, @bitCast(self.code.extra[extra_index]));
2056 extra_index += 1;
2057 const body = self.code.bodySlice(extra_index, info.body_len);
2058 extra_index += info.body_len;
2059
2060 try stream.writeAll(",\n");
2061 try stream.splatByteAll(' ', self.indent);
2062 switch (info.capture) {
2063 .none => {},
2064 .by_val => try stream.writeAll("by_val "),
2065 .by_ref => try stream.writeAll("by_ref "),
2066 }
2067 if (info.is_inline) try stream.writeAll("inline ");
2068 try self.writeInstRef(stream, item_ref);
2069 try stream.writeAll(" => ");
2070 try self.writeBracedBody(stream, body);
2071 }
2072 }
2073 {
2074 var multi_i: usize = 0;
2075 while (multi_i < multi_cases_len) : (multi_i += 1) {
2076 const items_len = self.code.extra[extra_index];
2077 extra_index += 1;
2078 const ranges_len = self.code.extra[extra_index];
2079 extra_index += 1;
2080 const info = @as(Zir.Inst.SwitchBlock.ProngInfo, @bitCast(self.code.extra[extra_index]));
2081 extra_index += 1;
2082 const items = self.code.refSlice(extra_index, items_len);
2083 extra_index += items_len;
2084
2085 try stream.writeAll(",\n");
2086 try stream.splatByteAll(' ', self.indent);
2087 switch (info.capture) {
2088 .none => {},
2089 .by_val => try stream.writeAll("by_val "),
2090 .by_ref => try stream.writeAll("by_ref "),
2091 }
2092 if (info.is_inline) try stream.writeAll("inline ");
2093
2094 for (items, 0..) |item_ref, item_i| {
2095 if (item_i != 0) try stream.writeAll(", ");
2096 try self.writeInstRef(stream, item_ref);
2097 }
2098
2099 var range_i: usize = 0;
2100 while (range_i < ranges_len) : (range_i += 1) {
2101 const item_first = @as(Zir.Inst.Ref, @enumFromInt(self.code.extra[extra_index]));
2102 extra_index += 1;
2103 const item_last = @as(Zir.Inst.Ref, @enumFromInt(self.code.extra[extra_index]));
2104 extra_index += 1;
2105
2106 if (range_i != 0 or items.len != 0) {
2107 try stream.writeAll(", ");
2108 }
2109 try self.writeInstRef(stream, item_first);
2110 try stream.writeAll("...");
2111 try self.writeInstRef(stream, item_last);
2112 }
2113
2114 const body = self.code.bodySlice(extra_index, info.body_len);
2115 extra_index += info.body_len;
2116 try stream.writeAll(" => ");
2117 try self.writeBracedBody(stream, body);
2118 }
2119 }
2120
2121 self.indent -= 2;
2122
2123 try stream.writeAll(") ");
2124 try self.writeSrcNode(stream, inst_data.src_node);
2125 }
2126
2127 fn writeSwitchBlock(self: *Writer, stream: *std.Io.Writer, inst: Zir.Inst.Index) !void {
2128 const inst_data = self.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
2129 const extra = self.code.extraData(Zir.Inst.SwitchBlock, inst_data.payload_index);
2130
2131 var extra_index: usize = extra.end;
2132
2133 const multi_cases_len = if (extra.data.bits.has_multi_cases) blk: {
2134 const multi_cases_len = self.code.extra[extra_index];
2135 extra_index += 1;
2136 break :blk multi_cases_len;
2137 } else 0;
21382017
2139 const tag_capture_inst: Zir.Inst.Index = if (extra.data.bits.any_has_tag_capture) blk: {
2140 const tag_capture_inst = self.code.extra[extra_index];
2141 extra_index += 1;
2142 break :blk @enumFromInt(tag_capture_inst);
2143 } else undefined;
2144
2145 try self.writeInstRef(stream, extra.data.operand);
2018 try self.writeSwitchCaptures(stream, else_case.capture, else_case.has_tag_capture, inst, &zir_switch);
2019 if (else_case.is_inline) try stream.writeAll("inline ");
21462020
2147 if (extra.data.bits.any_has_tag_capture) {
2148 try stream.writeAll(", tag_capture=");
2149 try self.writeInstIndex(stream, tag_capture_inst);
2021 try stream.writeAll("else => ");
2022 try self.writeBracedBody(stream, else_case.body);
21502023 }
2151
2152 self.indent += 2;
2153
2154 const special_prongs = extra.data.bits.special_prongs;
2155
2156 if (special_prongs.hasElse()) {
2157 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(self.code.extra[extra_index]);
2158 const capture_text = switch (info.capture) {
2159 .none => "",
2160 .by_val => "by_val ",
2161 .by_ref => "by_ref ",
2162 };
2163 const inline_text = if (info.is_inline) "inline " else "";
2164 extra_index += 1;
2165 const body = self.code.bodySlice(extra_index, info.body_len);
2166 extra_index += body.len;
2167
2024 if (zir_switch.under_case.resolve()) |under_case| {
21682025 try stream.writeAll(",\n");
21692026 try stream.splatByteAll(' ', self.indent);
2170 try stream.print("{s}{s}else => ", .{ capture_text, inline_text });
2171 try self.writeBracedBody(stream, body);
2172 }
21732027
2174 if (special_prongs.hasUnder()) {
2175 var single_item_ref: Zir.Inst.Ref = .none;
2176 var items_len: u32 = 0;
2177 var ranges_len: u32 = 0;
2178 if (special_prongs.hasOneAdditionalItem()) {
2179 single_item_ref = @enumFromInt(self.code.extra[extra_index]);
2180 extra_index += 1;
2181 } else if (special_prongs.hasManyAdditionalItems()) {
2182 items_len = self.code.extra[extra_index];
2183 extra_index += 1;
2184 ranges_len = self.code.extra[extra_index];
2185 extra_index += 1;
2186 }
2187 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(self.code.extra[extra_index]);
2188 extra_index += 1;
2189 const items = self.code.refSlice(extra_index, items_len);
2190 extra_index += items_len;
2028 try self.writeSwitchCaptures(stream, under_case.capture, under_case.has_tag_capture, inst, &zir_switch);
21912029
2030 try stream.writeAll("_ => ");
2031 try self.writeBracedBody(stream, under_case.body);
2032 }
2033
2034 var case_it = zir_switch.iterateCases();
2035 while (case_it.next()) |case| {
21922036 try stream.writeAll(",\n");
21932037 try stream.splatByteAll(' ', self.indent);
2194 switch (info.capture) {
2195 .none => {},
2196 .by_val => try stream.writeAll("by_val "),
2197 .by_ref => try stream.writeAll("by_ref "),
2198 }
2199 if (info.is_inline) try stream.writeAll("inline ");
22002038
2201 try stream.writeAll("_");
2202 if (single_item_ref != .none) {
2203 try stream.writeAll(", ");
2204 try self.writeInstRef(stream, single_item_ref);
2205 }
2206 for (items) |item_ref| {
2207 try stream.writeAll(", ");
2208 try self.writeInstRef(stream, item_ref);
2209 }
2039 const prong_info = case.prong_info;
2040 try self.writeSwitchCaptures(stream, prong_info.capture, prong_info.has_tag_capture, inst, &zir_switch);
2041 if (prong_info.is_inline) try stream.writeAll("inline ");
22102042
2211 var range_i: usize = 0;
2212 while (range_i < ranges_len) : (range_i += 1) {
2213 const item_first: Zir.Inst.Ref = @enumFromInt(self.code.extra[extra_index]);
2214 extra_index += 1;
2215 const item_last: Zir.Inst.Ref = @enumFromInt(self.code.extra[extra_index]);
2216 extra_index += 1;
2043 const prong_body = self.code.bodySlice(extra_index, prong_info.body_len);
2044 extra_index += prong_body.len;
22172045
2218 try stream.writeAll(", ");
2219 try self.writeInstRef(stream, item_first);
2220 try stream.writeAll("...");
2221 try self.writeInstRef(stream, item_last);
2046 var first_item: bool = true;
2047 if (case.isUnder()) {
2048 try stream.writeAll("_");
2049 first_item = false;
22222050 }
2223
2224 const body = self.code.bodySlice(extra_index, info.body_len);
2225 extra_index += info.body_len;
2226 try stream.writeAll(" => ");
2227 try self.writeBracedBody(stream, body);
2228 }
2229
2230 {
2231 const scalar_cases_len = extra.data.bits.scalar_cases_len;
2232 var scalar_i: usize = 0;
2233 while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
2234 const item_ref: Zir.Inst.Ref = @enumFromInt(self.code.extra[extra_index]);
2235 extra_index += 1;
2236 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(self.code.extra[extra_index]);
2237 extra_index += 1;
2238 const body = self.code.bodySlice(extra_index, info.body_len);
2239 extra_index += info.body_len;
2240
2241 try stream.writeAll(",\n");
2242 try stream.splatByteAll(' ', self.indent);
2243 switch (info.capture) {
2244 .none => {},
2245 .by_val => try stream.writeAll("by_val "),
2246 .by_ref => try stream.writeAll("by_ref "),
2051 for (case.item_infos) |item_info| {
2052 if (!first_item) try stream.writeAll(", ");
2053 first_item = false;
2054
2055 switch (item_info.unwrap()) {
2056 .enum_literal => |str_index| {
2057 const str = self.code.nullTerminatedString(str_index);
2058 try stream.print("\".{f}\"", .{std.zig.fmtString(str)});
2059 },
2060 .error_value => |str_index| {
2061 const str = self.code.nullTerminatedString(str_index);
2062 try stream.print("\"error.{f}\"", .{std.zig.fmtString(str)});
2063 },
2064 .number_literal => |zir_ref| {
2065 try self.writeInstRef(stream, zir_ref);
2066 },
2067 .body_len => |body_len| {
2068 const item_body = self.code.bodySlice(extra_index, body_len);
2069 extra_index += item_body.len;
2070 try self.writeBracedDecl(stream, item_body);
2071 },
22472072 }
2248 if (info.is_inline) try stream.writeAll("inline ");
2249 try self.writeInstRef(stream, item_ref);
2250 try stream.writeAll(" => ");
2251 try self.writeBracedBody(stream, body);
22522073 }
2253 }
2254 {
2255 var multi_i: usize = 0;
2256 while (multi_i < multi_cases_len) : (multi_i += 1) {
2257 const items_len = self.code.extra[extra_index];
2258 extra_index += 1;
2259 const ranges_len = self.code.extra[extra_index];
2260 extra_index += 1;
2261 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(self.code.extra[extra_index]);
2262 extra_index += 1;
2263 const items = self.code.refSlice(extra_index, items_len);
2264 extra_index += items_len;
2265
2266 try stream.writeAll(",\n");
2267 try stream.splatByteAll(' ', self.indent);
2268 switch (info.capture) {
2269 .none => {},
2270 .by_val => try stream.writeAll("by_val "),
2271 .by_ref => try stream.writeAll("by_ref "),
2272 }
2273 if (info.is_inline) try stream.writeAll("inline ");
2274
2275 for (items, 0..) |item_ref, item_i| {
2276 if (item_i != 0) try stream.writeAll(", ");
2277 try self.writeInstRef(stream, item_ref);
2278 }
2279
2280 var range_i: usize = 0;
2281 while (range_i < ranges_len) : (range_i += 1) {
2282 const item_first: Zir.Inst.Ref = @enumFromInt(self.code.extra[extra_index]);
2283 extra_index += 1;
2284 const item_last: Zir.Inst.Ref = @enumFromInt(self.code.extra[extra_index]);
2285 extra_index += 1;
2286
2287 if (range_i != 0 or items.len != 0) {
2288 try stream.writeAll(", ");
2074 for (case.range_infos) |range_info| {
2075 if (!first_item) try stream.writeAll(", ");
2076 first_item = false;
2077
2078 var first_range_item = true;
2079 for (&range_info) |item_info| {
2080 if (!first_range_item) try stream.writeAll("...");
2081 first_range_item = false;
2082
2083 switch (item_info.unwrap()) {
2084 .enum_literal => |str_index| {
2085 const str = self.code.nullTerminatedString(str_index);
2086 try stream.print("\".{f}\"", .{std.zig.fmtString(str)});
2087 },
2088 .error_value => |str_index| {
2089 const str = self.code.nullTerminatedString(str_index);
2090 try stream.print("\"error.{f}\"", .{std.zig.fmtString(str)});
2091 },
2092 .number_literal => |zir_ref| {
2093 try self.writeInstRef(stream, zir_ref);
2094 },
2095 .body_len => |body_len| {
2096 const item_body = self.code.bodySlice(extra_index, body_len);
2097 extra_index += item_body.len;
2098 try self.writeBracedDecl(stream, item_body);
2099 },
22892100 }
2290 try self.writeInstRef(stream, item_first);
2291 try stream.writeAll("...");
2292 try self.writeInstRef(stream, item_last);
22932101 }
2294
2295 const body = self.code.bodySlice(extra_index, info.body_len);
2296 extra_index += info.body_len;
2297 try stream.writeAll(" => ");
2298 try self.writeBracedBody(stream, body);
22992102 }
2103 try stream.writeAll(" => ");
2104 try self.writeBracedBody(stream, prong_body);
23002105 }
23012106
23022107 self.indent -= 2;
23032108
23042109 try stream.writeAll(") ");
2305 try self.writeSrcNode(stream, inst_data.src_node);
2110 try self.writeSrcNode(stream, zir_switch.switch_src_node_offset);
2111 }
2112
2113 fn writeSwitchCaptures(
2114 self: *Writer,
2115 stream: *std.Io.Writer,
2116 capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,
2117 has_tag_capture: bool,
2118 switch_inst: Zir.Inst.Index,
2119 zir_switch: *const Zir.UnwrappedSwitchBlock,
2120 ) !void {
2121 if (capture != .none) {
2122 try stream.print("{t}=", .{capture});
2123 const capture_inst = zir_switch.payload_capture_placeholder.unwrap() orelse switch_inst;
2124 try self.writeInstIndex(stream, capture_inst);
2125 try stream.writeAll(" ");
2126 }
2127 if (has_tag_capture) {
2128 try stream.writeAll("tag=");
2129 const capture_inst = zir_switch.tag_capture_placeholder.unwrap() orelse switch_inst;
2130 try self.writeInstIndex(stream, capture_inst);
2131 try stream.writeAll(" ");
2132 }
23062133 }
23072134
23082135 fn writePlNodeField(self: *Writer, stream: *std.Io.Writer, inst: Zir.Inst.Index) !void {