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 {...@@ -115,7 +115,6 @@ fn setExtra(astgen: *AstGen, index: usize, extra: anytype) void {
115 Zir.Inst.Call.Flags,115 Zir.Inst.Call.Flags,
116 Zir.Inst.BuiltinCall.Flags,116 Zir.Inst.BuiltinCall.Flags,
117 Zir.Inst.SwitchBlock.Bits,117 Zir.Inst.SwitchBlock.Bits,
118 Zir.Inst.SwitchBlockErrUnion.Bits,
119 Zir.Inst.FuncFancy.Bits,118 Zir.Inst.FuncFancy.Bits,
120 Zir.Inst.Param.Type,119 Zir.Inst.Param.Type,
121 Zir.Inst.Func.RetTy,120 Zir.Inst.Func.RetTy,
...@@ -858,11 +857,11 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE...@@ -858,11 +857,11 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE
858 no_switch_on_err: {857 no_switch_on_err: {
859 const error_token = if_full.error_token orelse break :no_switch_on_err;858 const error_token = if_full.error_token orelse break :no_switch_on_err;
860 const else_node = if_full.ast.else_expr.unwrap() orelse break :no_switch_on_err;859 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;860 const switch_full = tree.fullSwitch(else_node) orelse break :no_switch_on_err;
862 if (full_switch.label_token != null) break :no_switch_on_err;861 if (switch_full.label_token != null) break :no_switch_on_err; // handled in `ifExpr`
863 if (tree.nodeTag(full_switch.ast.condition) != .identifier) break :no_switch_on_err;862 if (tree.nodeTag(switch_full.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;863 if (!try astgen.tokenIdentEql(error_token, tree.nodeMainToken(switch_full.ast.condition))) break :no_switch_on_err;
865 return switchExprErrUnion(gz, scope, ri.br(), node, .@"if");864 return switchExpr(gz, scope, ri.br(), node, switch_full, .{ .@"if" = if_full });
866 }865 }
867 return ifExpr(gz, scope, ri.br(), node, if_full);866 return ifExpr(gz, scope, ri.br(), node, if_full);
868 },867 },
...@@ -1024,11 +1023,11 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE...@@ -1024,11 +1023,11 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE
1024 null;1023 null;
1025 no_switch_on_err: {1024 no_switch_on_err: {
1026 const capture_token = payload_token orelse break :no_switch_on_err;1025 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;1026 const switch_full = 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;1027 if (switch_full.label_token != null) break :no_switch_on_err; // handled in `orelseCatchExpr`
1029 if (tree.nodeTag(full_switch.ast.condition) != .identifier) break :no_switch_on_err;1028 if (tree.nodeTag(switch_full.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;1029 if (!try astgen.tokenIdentEql(capture_token, tree.nodeMainToken(switch_full.ast.condition))) break :no_switch_on_err;
1031 return switchExprErrUnion(gz, scope, ri.br(), node, .@"catch");1030 return switchExpr(gz, scope, ri.br(), node, switch_full, .@"catch");
1032 }1031 }
1033 switch (ri.rl) {1032 switch (ri.rl) {
1034 .ref, .ref_coerced_ty => return orelseCatchExpr(1033 .ref, .ref_coerced_ty => return orelseCatchExpr(
...@@ -1108,7 +1107,7 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE...@@ -1108,7 +1107,7 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE
1108 .error_set_decl => return errorSetDecl(gz, ri, node),1107 .error_set_decl => return errorSetDecl(gz, ri, node),
1109 .array_access => return arrayAccess(gz, scope, ri, node),1108 .array_access => return arrayAccess(gz, scope, ri, node),
1110 .@"comptime" => return comptimeExprAst(gz, scope, ri, node),1109 .@"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
1113 .@"nosuspend" => return nosuspendExpr(gz, scope, ri, node),1112 .@"nosuspend" => return nosuspendExpr(gz, scope, ri, node),
1114 .@"suspend" => return suspendExpr(gz, scope, node),1113 .@"suspend" => return suspendExpr(gz, scope, node),
...@@ -3134,14 +3133,7 @@ fn deferStmt(...@@ -3134,14 +3133,7 @@ fn deferStmt(
3134 }3133 }
3135 const remapped_err_code: Zir.Inst.Index = @enumFromInt(gz.astgen.instructions.len);3134 const remapped_err_code: Zir.Inst.Index = @enumFromInt(gz.astgen.instructions.len);
3136 opt_remapped_err_code = remapped_err_code.toOptional();3135 opt_remapped_err_code = remapped_err_code.toOptional();
3137 try gz.astgen.instructions.append(gz.astgen.gpa, .{3136 _ = try gz.astgen.appendPlaceholder();
3138 .tag = .extended,
3139 .data = .{ .extended = .{
3140 .opcode = .value_placeholder,
3141 .small = undefined,
3142 .operand = undefined,
3143 } },
3144 });
3145 const remapped_err_code_ref = remapped_err_code.toRef();3137 const remapped_err_code_ref = remapped_err_code.toRef();
3146 local_val_scope = .{3138 local_val_scope = .{
3147 .parent = &defer_gen.base,3139 .parent = &defer_gen.base,
...@@ -6115,7 +6107,30 @@ fn orelseCatchExpr(...@@ -6115,7 +6107,30 @@ fn orelseCatchExpr(
6115 break :blk &err_val_scope.base;6107 break :blk &err_val_scope.base;
6116 };6108 };
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 };
6119 if (!else_scope.endsWithNoReturn()) {6134 if (!else_scope.endsWithNoReturn()) {
6120 // As our last action before the break, "pop" the error trace if needed6135 // As our last action before the break, "pop" the error trace if needed
6121 if (do_err_trace)6136 if (do_err_trace)
...@@ -6468,7 +6483,26 @@ fn ifExpr(...@@ -6468,7 +6483,26 @@ fn ifExpr(
6468 break :s &else_scope.base;6483 break :s &else_scope.base;
6469 }6484 }
6470 };6485 };
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 };
6472 if (!else_scope.endsWithNoReturn()) {6506 if (!else_scope.endsWithNoReturn()) {
6473 // As our last action before the break, "pop" the error trace if needed6507 // As our last action before the break, "pop" the error trace if needed
6474 if (do_err_trace)6508 if (do_err_trace)
...@@ -7117,568 +7151,156 @@ fn forExpr(...@@ -7117,568 +7151,156 @@ fn forExpr(
7117 return result;7151 return result;
7118}7152}
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(
7121 parent_gz: *GenZir,7184 parent_gz: *GenZir,
7122 scope: *Scope,7185 scope: *Scope,
7123 ri: ResultInfo,7186 ri: ResultInfo,
7124 catch_or_if_node: Ast.Node.Index,7187 node: Ast.Node.Index,
7125 node_ty: enum { @"catch", @"if" },7188 switch_full: Ast.full.Switch,
7189 non_err: SwitchNonErr,
7126) InnerError!Zir.Inst.Ref {7190) InnerError!Zir.Inst.Ref {
7127 const astgen = parent_gz.astgen;7191 const astgen = parent_gz.astgen;
7128 const gpa = astgen.gpa;7192 const gpa = astgen.gpa;
7129 const tree = astgen.tree;7193 const tree = astgen.tree;
71307194
7131 const if_full = switch (node_ty) {7195 const switch_node, const operand_node, const err_token = switch (non_err) {
7132 .@"catch" => undefined,7196 .none, .peer_break_target => .{
7133 .@"if" => tree.fullIf(catch_or_if_node).?,7197 node,
7134 };7198 switch_full.ast.condition,
71357199 undefined,
7136 const switch_node, const operand_node, const error_payload = switch (node_ty) {7200 },
7137 .@"catch" => .{7201 .@"catch" => .{
7138 tree.nodeData(catch_or_if_node).node_and_node[1],7202 tree.nodeData(node).node_and_node[1],
7139 tree.nodeData(catch_or_if_node).node_and_node[0],7203 tree.nodeData(node).node_and_node[0],
7140 tree.nodeMainToken(catch_or_if_node) + 2,7204 tree.nodeMainToken(node) + 2,
7141 },7205 },
7142 .@"if" => .{7206 .@"if" => |if_full| .{
7143 if_full.ast.else_expr.unwrap().?,7207 if_full.ast.else_expr.unwrap().?,
7144 if_full.ast.cond_expr,7208 if_full.ast.cond_expr,
7145 if_full.error_token.?,7209 if_full.error_token.?,
7146 },7210 },
7147 };7211 };
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;7221 const need_rl = astgen.nodes_need_rl.contains(node);
7151 const need_rl = astgen.nodes_need_rl.contains(catch_or_if_node);
7152 const block_ri: ResultInfo = if (need_rl) ri else .{7222 const block_ri: ResultInfo = if (need_rl) ri else .{
7153 .rl = switch (ri.rl) {7223 .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)).? },
7155 .inferred_ptr => .none,7225 .inferred_ptr => .none,
7156 else => ri.rl,7226 else => ri.rl,
7157 },7227 },
7158 .ctx = ri.ctx,7228 .ctx = ri.ctx,
7159 };7229 };
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
7166 // We need to call `rvalue` to write through to the pointer only if we had a7231 // We need to call `rvalue` to write through to the pointer only if we had a
7167 // result pointer and aren't forwarding it.7232 // result pointer and aren't forwarding it.
7168 const LocTag = @typeInfo(ResultInfo.Loc).@"union".tag_type.?;7233 const LocTag = @typeInfo(ResultInfo.Loc).@"union".tag_type.?;
7169 const need_result_rvalue = @as(LocTag, block_ri.rl) != @as(LocTag, ri.rl);7234 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) {7236 const catch_or_if_node = if (needs_non_err_handling) node else undefined;
7180 const case_src = case.ast.arrow_token - 1;7237 const do_err_trace = needs_non_err_handling and astgen.fn_block != null;
7181 if (else_src) |src| {7238 const non_err_is_ref: bool = switch (non_err) {
7182 return astgen.failTokNotes(7239 .none, .peer_break_target => undefined,
7183 case_src,7240 .@"catch" => ri.rl == .ref or ri.rl == .ref_coerced_ty,
7184 "multiple else prongs in switch expression",7241 .@"if" => |if_full| if_full.payload_token != null and
7185 .{},7242 tree.tokenTag(if_full.payload_token.?) == .asterisk,
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 };7243 };
72377244
7238 astgen.advanceSourceCursorToNode(operand_node);7245 if (switch_full.label_token) |label_token| {
7239 const operand_lc: LineColumn = .{ astgen.source_line - parent_gz.decl_line, astgen.source_column };7246 try astgen.checkLabelRedefinition(scope, label_token);
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});
7384 }7247 }
73857248
7386 const err_name = blk: {7249 const err_capture_name: Zir.NullTerminatedString = if (needs_non_err_handling) blk: {
7387 const err_str = tree.tokenSlice(error_payload);7250 const err_str = tree.tokenSlice(err_token);
7388 if (mem.eql(u8, err_str, "_")) {7251 if (mem.eql(u8, err_str, "_")) {
7389 // This is fatal because we already know we're switching on the captured error.7252 // 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", .{});
7391 }7254 }
7392 const err_name = try astgen.identAsString(error_payload);7255 const err_name = try astgen.identAsString(err_token);
7393 try astgen.detectLocalShadowing(scope, err_name, error_payload, err_str, .capture);7256 try astgen.detectLocalShadowing(scope, err_name, err_token, err_str, .capture);
7394
7395 break :blk err_name;7257 break :blk err_name;
7396 };7258 } else undefined;
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 }
76487259
7649 // We perform two passes over the AST. This first pass is to collect information7260 // 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,7261 // for the following variables, make note of the special prong AST node indices,
7651 // and bail out with a compile error if there are multiple special prongs present.7262 // and bail out with a compile error if there are incompatible special prongs present.
7652 var any_payload_is_ref = false;7263 var any_payload_is_ref = false;
7264 var any_has_payload_capture = false;
7653 var any_has_tag_capture = false;7265 var any_has_tag_capture = false;
7654 var any_non_inline_capture = false;7266 var any_maybe_runtime_capture = false;
7655 var scalar_cases_len: u32 = 0;7267 var scalar_cases_len: u32 = 0;
7656 var multi_cases_len: u32 = 0;7268 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;
7658 var else_case_node: Ast.Node.OptionalIndex = .none;7271 var else_case_node: Ast.Node.OptionalIndex = .none;
7659 var else_src: ?Ast.TokenIndex = null;7272 var else_src: ?Ast.TokenIndex = null;
7660 var underscore_case_node: Ast.Node.OptionalIndex = .none;7273 var under_case_node: Ast.Node.OptionalIndex = .none;
7661 var underscore_node: Ast.Node.OptionalIndex = .none;7274 var underscore_node: Ast.Node.OptionalIndex = .none;
7662 var underscore_src: ?Ast.TokenIndex = null;7275 var underscore_src: ?Ast.TokenIndex = null;
7663 var underscore_additional_items: Zir.SpecialProngs.AdditionalItems = .none;7276 var under_is_bare = false;
7664 for (case_nodes) |case_node| {7277 for (case_nodes) |case_node| {
7665 const case = tree.fullSwitchCase(case_node).?;7278 const case = tree.fullSwitchCase(case_node).?;
7666 if (case.payload_token) |payload_token| {7279 if (case.payload_token) |payload_token| {
7667 const ident = if (tree.tokenTag(payload_token) == .asterisk) blk: {7280 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.
7668 any_payload_is_ref = true;7283 any_payload_is_ref = true;
7669 break :blk payload_token + 1;7284 break :blk payload_token + 1;
7670 } else payload_token;7285 } 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 }
7671 if (tree.tokenTag(ident + 1) == .comma) {7298 if (tree.tokenTag(ident + 1) == .comma) {
7672 any_has_tag_capture = true;7299 any_has_tag_capture = true;
7673 }
76747300
7675 // If the first capture is ignored, then there is no runtime-known7301 if (case.inline_token == null) {
7676 // capture, as the tag capture must be for an inline prong.7302 any_maybe_runtime_capture = true;
7677 // This check isn't perfect, because for things like enums, the7303 }
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;
7682 }7304 }
7683 }7305 }
76847306
...@@ -7690,13 +7312,7 @@ fn switchExpr(...@@ -7690,13 +7312,7 @@ fn switchExpr(
7690 case_src,7312 case_src,
7691 "multiple else prongs in switch expression",7313 "multiple else prongs in switch expression",
7692 .{},7314 .{},
7693 &[_]u32{7315 &.{try astgen.errNoteTok(src, "previous else prong here", .{})},
7694 try astgen.errNoteTok(
7695 src,
7696 "previous else prong here",
7697 .{},
7698 ),
7699 },
7700 );7316 );
7701 }7317 }
7702 else_case_node = case_node.toOptional();7318 else_case_node = case_node.toOptional();
...@@ -7704,156 +7320,492 @@ fn switchExpr(...@@ -7704,156 +7320,492 @@ fn switchExpr(
7704 continue;7320 continue;
7705 }7321 }
77067322
7707 // Check for '_' prong.7323 // Check for '_' prong and ranges.
7708 var case_has_underscore = false;7324 var case_has_underscore = false;
7325 var case_has_ranges = false;
7709 for (case.ast.values) |val| {7326 for (case.ast.values) |val| {
7710 switch (tree.nodeTag(val)) {7327 switch (tree.nodeTag(val)) {
7711 .identifier => if (mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(val)), "_")) {7328 .switch_range => {
7712 const val_src = tree.nodeMainToken(val);7329 total_ranges_len += 1;
7713 if (underscore_src) |src| {7330 case_has_ranges = true;
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;
7740 },7331 },
7741 .string_literal => return astgen.failNode(val, "cannot switch on strings", .{}),7332 .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 },
7743 }7373 }
7744 }7374 }
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) {
7748 scalar_cases_len += 1;7378 scalar_cases_len += 1;
7749 } else {7379 } else if (case_len >= 1) {
7750 multi_cases_len += 1;7380 multi_cases_len += 1;
7751 }7381 }
7752 if (case.inline_token != null) {
7753 inline_cases_len += 1;
7754 }
7755 }7382 }
77567383
7757 const special_prongs: Zir.SpecialProngs = .init(7384 const has_else = else_src != null;
7758 else_src != null,7385 const has_under = underscore_src != null;
7759 underscore_src != null,7386 if (under_is_bare) assert(has_under); // make sure that the former implies the latter
7760 underscore_additional_items,7387 if (is_err_switch) assert(!has_under); // should have failed by now
7761 );7388 const any_ranges = total_ranges_len > 0;
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 };
77727389
7773 // If this switch is labeled, it may have `continue`s targeting it, and thus we need the operand type7390 // This contains all of the body lengths (already in the correct order) and
7774 // to provide a result type.7391 // the bodies they belong to that go into the `extra` array later, except the
7775 const raw_operand_ty_ref = if (switch_full.label_token != null) t: {7392 // first item_table_end slots are a table that indexes the item bodies (and
7776 break :t try parent_gz.addUnNode(.typeof, raw_operand, operand_node);7393 // also indirectly the prong bodies, as they are always trailing after their
7777 } else undefined;7394 // item bodies).
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
7782 const payloads = &astgen.scratch;7395 const payloads = &astgen.scratch;
7783 const scratch_top = astgen.scratch.items.len;7396 const scratch_top = astgen.scratch.items.len;
7784 const case_table_start = scratch_top;7397 var payloads_end = scratch_top;
7785 const else_case_index = if (has_else) case_table_start else undefined;7398
7786 const under_case_index = if (has_under) case_table_start + @intFromBool(has_else) else undefined;7399 // Since range item body pairs are always contiguous we don't technically
7787 const scalar_case_table = case_table_start + @intFromBool(has_else) + @intFromBool(has_under);7400 // have to keep track of the position of the second body. However handling
7788 const multi_case_table = scalar_case_table + scalar_cases_len;7401 // all of the several indices and offsets is complicated enough as it is,
7789 const case_table_end = multi_case_table + multi_cases_len;7402 // so for the sake of keeping this function a little bit more simple we do
7790 try astgen.scratch.resize(gpa, case_table_end);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);
7791 defer astgen.scratch.items.len = scratch_top;7431 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
7793 var block_scope = parent_gz.makeSubBlock(scope);7440 var block_scope = parent_gz.makeSubBlock(scope);
7794 // block_scope not used for collecting instructions7441 // block_scope not used for collecting instructions
7795 block_scope.instructions_top = GenZir.unstacked_top;7442 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.
7799 try emitDbgStmtForceCurrentIndex(parent_gz, operand_lc);7459 try emitDbgStmtForceCurrentIndex(parent_gz, operand_lc);
7800 // This gets added to the parent block later, after the item expressions.7460 // 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;7461 const switch_tag: Zir.Inst.Tag = switch (non_err) {
7802 const switch_block = try parent_gz.makeBlockInst(switch_tag, node);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
7804 if (switch_full.label_token) |label_token| {7541 if (switch_full.label_token) |label_token| {
7805 block_scope.label = .{ .token = label_token };7542 block_scope.label = .{ .token = label_token };
7806 block_scope.break_target = switch_block;
7807 block_scope.continue_target = .{ .switch_continue = switch_block };7543 block_scope.continue_target = .{ .switch_continue = switch_block };
7808 block_scope.continue_result_info = .{7544 block_scope.continue_result_info = .{
7809 .rl = if (any_payload_is_ref)7545 .rl = if (any_payload_is_ref)
7810 .{ .ref_coerced_ty = raw_operand_ty_ref }7546 .{ .ref_coerced_ty = switch_block.toRef() }
7811 else7547 else
7812 .{ .coerced_ty = raw_operand_ty_ref },7548 .{ .coerced_ty = switch_block.toRef() },
7813 };7549 };
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.
7816 }7553 }
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.7579 var prong_body_extra_insts_buf: [3]Zir.Inst.Index = undefined;
7819 var case_scope = parent_gz.makeSubBlock(&block_scope.base);7580 const prong_body_extra_insts: []const Zir.Inst.Index = extra_insts: {
7820 case_scope.instructions_top = GenZir.unstacked_top;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 it7588 const switch_operand, const catch_or_if_operand = if (needs_non_err_handling)
7823 const tag_inst = if (any_has_tag_capture) tag_inst: {7589 .{ switch_block.toRef(), raw_operand }
7824 const inst: Zir.Inst.Index = @enumFromInt(astgen.instructions.len);7590 else
7825 try astgen.instructions.append(astgen.gpa, .{7591 .{ raw_operand, undefined };
7826 .tag = .extended,7592
7827 .data = .{ .extended = .{7593 // We re-use this same scope for all case items and contents.
7828 .opcode = .value_placeholder,7594 var scratch_scope = parent_gz.makeSubBlock(&block_scope.base);
7829 .small = undefined,7595 scratch_scope.instructions_top = GenZir.unstacked_top;
7830 .operand = undefined,7596
7831 } },7597 // We have to take care of the non-error body first if there is one.
7832 });7598 non_err_body: {
7833 break :tag_inst inst;7599 if (!needs_non_err_handling) break :non_err_body;
7834 } else undefined;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
7836 // In this pass we generate all the item and prong expressions.7686 // In this pass we generate all the item and prong expressions.
7837 var multi_case_index: u32 = 0;7687 var multi_case_index: u32 = 0;
7838 var scalar_case_index: u32 = 0;7688 var scalar_case_index: u32 = 0;
7689 var multi_item_offset: usize = 0;
7839 for (case_nodes) |case_node| {7690 for (case_nodes) |case_node| {
7840 const case = tree.fullSwitchCase(case_node).?;7691 const case = tree.fullSwitchCase(case_node).?;
78417692
7842 const is_multi_case = case.ast.values.len > 1 or7693 const case_has_under = case_node.toOptional() == under_case_node;
7843 (case.ast.values.len == 1 and tree.nodeTag(case.ast.values[0]) == .switch_range);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;7777 // Capture and prong body
7846 var dbg_var_inst: Zir.Inst.Ref = undefined;7778
7779 var dbg_var_payload_name: Zir.NullTerminatedString = .empty;
7780 var dbg_var_payload_inst: Zir.Inst.Ref = undefined;
7847 var dbg_var_tag_name: Zir.NullTerminatedString = .empty;7781 var dbg_var_tag_name: Zir.NullTerminatedString = .empty;
7848 var dbg_var_tag_inst: Zir.Inst.Ref = undefined;7782 var dbg_var_tag_inst: Zir.Inst.Ref = undefined;
7849 var has_tag_capture = false;7783 var has_tag_capture = false;
7850 var capture_val_scope: Scope.LocalVal = undefined;7784 var err_capture_scope: Scope.LocalVal = undefined;
7851 var tag_scope: Scope.LocalVal = undefined;7785 var payload_capture_scope: Scope.LocalVal = undefined;
7786 var tag_capture_scope: Scope.LocalVal = undefined;
78527787
7853 var capture: Zir.Inst.SwitchBlock.ProngInfo.Capture = .none;7788 var capture: Zir.Inst.SwitchBlock.ProngInfo.Capture = .none;
78547789
7855 const sub_scope = blk: {7790 // Check all captures and make them available to the prong body.
7856 const payload_token = case.payload_token orelse break :blk &case_scope.base;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;
7857 const capture_is_ref = tree.tokenTag(payload_token) == .asterisk;7809 const capture_is_ref = tree.tokenTag(payload_token) == .asterisk;
7858 const ident = payload_token + @intFromBool(capture_is_ref);7810 const ident = payload_token + @intFromBool(capture_is_ref);
78597811
...@@ -7867,36 +7819,38 @@ fn switchExpr(...@@ -7867,36 +7819,38 @@ fn switchExpr(
7867 return astgen.failTok(payload_token, "pointer modifier invalid on discard", .{});7819 return astgen.failTok(payload_token, "pointer modifier invalid on discard", .{});
7868 }7820 }
7869 capture = .none;7821 capture = .none;
7870 payload_sub_scope = &case_scope.base;7822 payload_sub_scope = switch_scope;
7871 } else {7823 } else {
7872 const capture_name = try astgen.identAsString(ident);7824 const capture_name = try astgen.identAsString(ident);
7873 try astgen.detectLocalShadowing(&case_scope.base, capture_name, ident, ident_slice, .capture);7825 try astgen.detectLocalShadowing(&scratch_scope.base, capture_name, ident, ident_slice, .capture);
7874 capture_val_scope = .{7826 payload_capture_scope = .{
7875 .parent = &case_scope.base,7827 .parent = switch_scope,
7876 .gen_zir = &case_scope,7828 .gen_zir = &scratch_scope,
7877 .name = capture_name,7829 .name = capture_name,
7878 .inst = switch_block.toRef(),7830 .inst = payload_capture_inst.toRef(),
7879 .token_src = ident,7831 .token_src = ident,
7880 .id_cat = .capture,7832 .id_cat = .capture,
7881 };7833 };
7882 dbg_var_name = capture_name;7834 dbg_var_payload_name = payload_capture_scope.name;
7883 dbg_var_inst = switch_block.toRef();7835 dbg_var_payload_inst = payload_capture_scope.inst;
7884 payload_sub_scope = &capture_val_scope.base;7836 payload_sub_scope = &payload_capture_scope.base;
7885 }7837 }
78867838
7887 const tag_token = if (tree.tokenTag(ident + 1) == .comma)7839 if (is_err_switch and capture == .by_ref) {
7888 ident + 27840 return astgen.failTok(ident, "error set cannot be captured by reference", .{});
7889 else if (capture == .none) {7841 }
7890 // discarding the capture is only valid iff the tag is captured7842
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
7891 // whether the tag capture is discarded is handled below7847 // whether the tag capture is discarded is handled below
7892 return astgen.failTok(payload_token, "discard of capture; omit it instead", .{});7848 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
7895 const tag_slice = tree.tokenSlice(tag_token);7851 const tag_slice = tree.tokenSlice(tag_token);
7896 if (mem.eql(u8, tag_slice, "_")) {7852 if (mem.eql(u8, tag_slice, "_")) {
7897 return astgen.failTok(tag_token, "discard of tag capture; omit it instead", .{});7853 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", .{});
7900 }7854 }
7901 const tag_name = try astgen.identAsString(tag_token);7855 const tag_name = try astgen.identAsString(tag_token);
7902 try astgen.detectLocalShadowing(payload_sub_scope, tag_name, tag_token, tag_slice, .@"switch tag capture");7856 try astgen.detectLocalShadowing(payload_sub_scope, tag_name, tag_token, tag_slice, .@"switch tag capture");
...@@ -7904,123 +7858,155 @@ fn switchExpr(...@@ -7904,123 +7858,155 @@ fn switchExpr(
7904 assert(any_has_tag_capture);7858 assert(any_has_tag_capture);
7905 has_tag_capture = true;7859 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 = .{
7908 .parent = payload_sub_scope,7866 .parent = payload_sub_scope,
7909 .gen_zir = &case_scope,7867 .gen_zir = &scratch_scope,
7910 .name = tag_name,7868 .name = tag_name,
7911 .inst = tag_inst.toRef(),7869 .inst = tag_capture_inst.toRef(),
7912 .token_src = tag_token,7870 .token_src = tag_token,
7913 .id_cat = .@"switch tag capture",7871 .id_cat = .@"switch tag capture",
7914 };7872 };
7915 dbg_var_tag_name = tag_name;7873 dbg_var_tag_name = tag_capture_scope.name;
7916 dbg_var_tag_inst = tag_inst.toRef();7874 dbg_var_tag_inst = tag_capture_scope.inst;
7917 break :blk &tag_scope.base;7875 break :scope &tag_capture_scope.base;
7918 };7876 };
79197877
7920 const header_index: u32 = @intCast(payloads.items.len);7878 if (capture != .none) assert(any_has_payload_capture);
7921 const body_len_index = if (is_multi_case) blk: {7879 if (is_err_switch) {
7922 if (case_node.toOptional() == underscore_case_node) {7880 assert(!any_payload_is_ref); // should have failed by now
7923 payloads.items[under_case_index] = header_index;7881 assert(!any_has_tag_capture); // should have failed by now
7924 if (special_prongs.hasOneAdditionalItem()) {7882 }
7925 try payloads.resize(gpa, header_index + 2); // item, body_len7883
7926 const maybe_item_node = case.ast.values[0];7884 prong_body: {
7927 const item_node = if (maybe_item_node.toOptional() == underscore_node)7885 scratch_scope.instructions_top = parent_gz.instructions.items.len;
7928 case.ast.values[1]7886 defer scratch_scope.unstack();
7929 else7887
7930 maybe_item_node;7888 if (dbg_var_payload_name != .empty) {
7931 const item_inst = try comptimeExpr(parent_gz, scope, item_ri, item_node, .switch_item);7889 try scratch_scope.addDbgVar(.dbg_var_val, dbg_var_payload_name, dbg_var_payload_inst);
7932 payloads.items[header_index] = @intFromEnum(item_inst);7890 }
7933 break :blk header_index + 1;7891 if (dbg_var_tag_name != .empty) {
7934 }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);
7935 } else {7903 } else {
7936 payloads.items[multi_case_table + multi_case_index] = header_index;7904 try checkUsed(parent_gz, &scratch_scope.base, prong_body_scope);
7937 multi_case_index += 1;
7938 }7905 }
7939 try payloads.resize(gpa, header_index + 3); // items_len, ranges_len, body_len7906 if (!scratch_scope.endsWithNoReturn()) {
79407907 // As our last action before the break, "pop" the error trace if needed
7941 // items7908 if (do_err_trace) {
7942 var items_len: u32 = 0;7909 try restoreErrRetIndex(
7943 for (case.ast.values) |item_node| {7910 &scratch_scope,
7944 if (item_node.toOptional() == underscore_node or7911 .{ .block = switch_block },
7945 tree.nodeTag(item_node) == .switch_range)7912 block_scope.break_result_info,
7946 {7913 target_expr_node,
7947 continue;7914 case_result,
7915 );
7948 }7916 }
7949 items_len += 1;7917 _ = try scratch_scope.addBreakWithSrcNode(.@"break", switch_block, case_result, target_expr_node);
7950
7951 const item_inst = try comptimeExpr(parent_gz, scope, item_ri, item_node, .switch_item);
7952 try payloads.append(gpa, @intFromEnum(item_inst));
7953 }7918 }
79547919
7955 // ranges7920 const body_slice = scratch_scope.instructionsSlice();
7956 var ranges_len: u32 = 0;7921 const body_start: u32 = @intCast(payloads.items.len);
7957 for (case.ast.values) |range| {7922 const body_len = astgen.countBodyLenAfterFixupsExtraRefs(body_slice, prong_body_extra_insts);
7958 if (tree.nodeTag(range) != .switch_range) {7923 try payloads.ensureUnusedCapacity(gpa, body_len);
7959 continue;7924 astgen.appendBodyWithFixupsExtraRefsArrayList(payloads, body_slice, prong_body_extra_insts);
7960 }7925
7961 ranges_len += 1;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;7951 else_info = .{
7964 const first = try comptimeExpr(parent_gz, scope, item_ri, first_node, .switch_item);7952 .body_len = @intCast(body_len),
7965 const last = try comptimeExpr(parent_gz, scope, item_ri, last_node, .switch_item);7953 .capture = capture,
7966 try payloads.appendSlice(gpa, &[_]u32{7954 .is_inline = case.inline_token != null,
7967 @intFromEnum(first), @intFromEnum(last),7955 .has_tag_capture = has_tag_capture,
7968 });7956 .is_simple_noreturn = is_simple_noreturn,
7957 };
7958 else_prong_body_start = body_start;
7959 break :prong_body;
7969 }7960 }
79707961
7971 payloads.items[header_index] = items_len;7962 if (case_has_under) {
7972 payloads.items[header_index + 1] = ranges_len;7963 // We're either writing under_prong_info or under_index here.
7973 break :blk header_index + 2;7964 if (under_is_bare) {
7974 } else if (case_node.toOptional() == else_case_node) blk: {7965 assert(case.ast.values.len == 1); // only `_`
7975 payloads.items[else_case_index] = header_index;7966 const bare_under_info: Zir.Inst.SwitchBlock.ProngInfo.BareUnder = .{
7976 try payloads.resize(gpa, header_index + 1); // body_len7967 .body_len = @intCast(body_len),
7977 break :blk header_index;7968 .capture = capture,
7978 } else if (case_node.toOptional() == underscore_case_node) blk: {7969 .has_tag_capture = has_tag_capture,
7979 assert(!special_prongs.hasAdditionalItems());7970 };
7980 payloads.items[under_case_index] = header_index;7971 under_extra = @bitCast(bare_under_info);
7981 try payloads.resize(gpa, header_index + 1); // body_len7972 bare_under_prong_body_start = body_start;
7982 break :blk header_index;7973 break :prong_body;
7983 } else blk: {7974 } else if (is_multi_case) {
7984 payloads.items[scalar_case_table + scalar_case_index] = header_index;7975 under_extra = scalar_cases_len + multi_case_index;
7985 scalar_case_index += 1;7976 } else {
7986 try payloads.resize(gpa, header_index + 2); // item, body_len7977 under_extra = scalar_case_index;
7987 const item_node = case.ast.values[0];7978 }
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);
8009 }7979 }
80107980
8011 const case_slice = case_scope.instructionsSlice();7981 // We allow prongs with error items which are not inside the error set
8012 const extra_insts: []const Zir.Inst.Index = if (has_tag_capture) &.{ switch_block, tag_inst } else &.{switch_block};7982 // being switched on if their body is `=> comptime unreachable,`.
8013 const body_len = astgen.countBodyLenAfterFixupsExtraRefs(case_slice, extra_insts);7983 const is_comptime_unreach = comptime_unreach: {
8014 try payloads.ensureUnusedCapacity(gpa, body_len);7984 if (tree.nodeTag(target_expr_node) != .@"comptime") break :comptime_unreach false;
8015 payloads.items[body_len_index] = @bitCast(Zir.Inst.SwitchBlock.ProngInfo{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 = .{
8016 .body_len = @intCast(body_len),7990 .body_len = @intCast(body_len),
8017 .capture = capture,7991 .capture = capture,
8018 .is_inline = case.inline_token != null,7992 .is_inline = case.inline_token != null,
8019 .has_tag_capture = has_tag_capture,7993 .has_tag_capture = has_tag_capture,
8020 });7994 .is_comptime_unreach = is_comptime_unreach,
8021 appendBodyWithFixupsExtraRefsArrayList(astgen, payloads, case_slice, extra_insts);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 }
8022 }8006 }
8023 }8007 }
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
8025 if (switch_full.label_token) |label_token| if (!block_scope.label.?.used) {8011 if (switch_full.label_token) |label_token| if (!block_scope.label.?.used) {
8026 try astgen.appendErrorTok(label_token, "unused switch label", .{});8012 try astgen.appendErrorTok(label_token, "unused switch label", .{});
...@@ -8029,84 +8015,108 @@ fn switchExpr(...@@ -8029,84 +8015,108 @@ fn switchExpr(
8029 // Now that the item expressions are generated we can add this.8015 // Now that the item expressions are generated we can add this.
8030 try parent_gz.instructions.append(gpa, switch_block);8016 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
8032 try astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.SwitchBlock).@"struct".fields.len +8022 try astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.SwitchBlock).@"struct".fields.len +
8033 @intFromBool(multi_cases_len != 0) +8023 @intFromBool(multi_cases_len > 0) + // multi_cases_len
8034 @intFromBool(any_has_tag_capture) +8024 @intFromBool(payload_capture_inst_is_placeholder) + // payload_capture_placeholder
8035 payloads.items.len - scratch_top);8025 @intFromBool(tag_capture_inst_is_placeholder) + // tag_capture_placeholder
80368026 @intFromBool(needs_non_err_handling) + // catch_or_if_src_node_offset
8037 const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.SwitchBlock{8027 @intFromBool(needs_non_err_handling) + // non_err_info
8038 .operand = raw_operand,8028 @intFromBool(has_else) + // else_info
8039 .bits = Zir.Inst.SwitchBlock.Bits{8029 @intFromBool(has_under) + // under_prong_info or under_index
8040 .has_multi_cases = multi_cases_len != 0,8030 payloads.items.len - body_table_end); // item infos and bodies
8041 .special_prongs = special_prongs,8031
8042 .any_has_tag_capture = any_has_tag_capture,8032 // singular pieces of data
8043 .any_non_inline_capture = any_non_inline_capture,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,
8044 .has_continue = switch_full.label_token != null and block_scope.label.?.used_for_continue,8041 .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,
8045 .scalar_cases_len = @intCast(scalar_cases_len),8045 .scalar_cases_len = @intCast(scalar_cases_len),
8046 },8046 },
8047 });8047 });
8048 astgen.instructions.items(.data)[@intFromEnum(switch_block)].pl_node.payload_index = zir_payload_index;
80488049
8049 if (multi_cases_len != 0) {8050 if (multi_cases_len > 0) astgen.extra.appendAssumeCapacity(multi_cases_len);
8050 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));
8051 }8057 }
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) {8061 const extra_payloads_start = astgen.extra.items.len;
8054 astgen.extra.appendAssumeCapacity(@intFromEnum(tag_inst));
8055 }
80568062
8057 const zir_datas = astgen.instructions.items(.data);8063 // body lens
8058 zir_datas[@intFromEnum(switch_block)].pl_node.payload_index = payload_index;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 }
8060 if (has_else) {8071 if (has_else) {
8061 const start_index = payloads.items[else_case_index];8072 const body = payloads.items[else_prong_body_start..][0..else_info.body_len];
8062 var end_index = start_index + 1;8073 astgen.extra.appendSliceAssumeCapacity(body);
8063 const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(payloads.items[start_index]);8074 }
8064 end_index += prong_info.body_len;8075 if (under_is_bare) {
8065 astgen.extra.appendSliceAssumeCapacity(payloads.items[start_index..end_index]);8076 const under_prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(under_extra);
8066 }8077 const body = payloads.items[bare_under_prong_body_start..][0..under_prong_info.body_len];
8067 if (has_under) {8078 astgen.extra.appendSliceAssumeCapacity(body);
8068 const start_index = payloads.items[under_case_index];8079 }
8069 var body_len_index = start_index;8080 for (0..scalar_cases_len) |scalar_i| {
8070 var end_index = start_index;8081 const item_info: Zir.Inst.SwitchBlock.ItemInfo = @bitCast(payloads.items[scalar_item_infos_start + scalar_i]);
8071 switch (underscore_additional_items) {8082 const item_body_start = payloads.items[scalar_body_table + scalar_i];
8072 .none => {8083 const item_body = payloads.items[item_body_start..][0 .. item_info.bodyLen() orelse 0];
8073 end_index += 1;8084 const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(payloads.items[scalar_prong_infos_start + scalar_i]);
8074 },8085 const prong_body_start = item_body_start + item_body.len;
8075 .one => {8086 const prong_body = payloads.items[prong_body_start..][0..prong_info.body_len];
8076 body_len_index += 1;8087 astgen.extra.appendSliceAssumeCapacity(prong_body);
8077 end_index += 2;8088 astgen.extra.appendSliceAssumeCapacity(item_body);
8078 },8089 }
8079 .many => {8090 var multi_item_i: usize = 0;
8080 body_len_index += 2;8091 for (0..multi_cases_len) |multi_i| {
8081 const items_len = payloads.items[start_index];8092 const prong_body_start = payloads.items[multi_prong_body_table + multi_i];
8082 const ranges_len = payloads.items[start_index + 1];8093 const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(payloads.items[multi_prong_infos_start + multi_i]);
8083 end_index += 3 + items_len + 2 * ranges_len;8094 const prong_body = payloads.items[prong_body_start..][0..prong_info.body_len];
8084 },8095 astgen.extra.appendSliceAssumeCapacity(prong_body);
8085 }8096
8086 const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(payloads.items[body_len_index]);8097 const items_len = payloads.items[multi_case_items_lens_start + multi_i];
8087 end_index += prong_info.body_len;8098 const ranges_len = if (any_ranges) ranges_len: {
8088 astgen.extra.appendSliceAssumeCapacity(payloads.items[start_index..end_index]);8099 break :ranges_len payloads.items[multi_case_ranges_lens_start + multi_i];
8089 }8100 } else 0;
8090 for (payloads.items[scalar_case_table..case_table_end], 0..) |start_index, i| {8101 // The table entries and body lens are already in the correct order so we
8091 var body_len_index = start_index;8102 // don't have to differentiate between items and ranges here.
8092 var end_index = start_index;8103 for (0..items_len + 2 * ranges_len) |_| {
8093 const table_index = scalar_case_table + i;8104 const item_info: Zir.Inst.SwitchBlock.ItemInfo = @bitCast(payloads.items[multi_items_infos_start + multi_item_i]);
8094 if (table_index < multi_case_table) {8105 if (item_info.bodyLen()) |body_len| {
8095 body_len_index += 1;8106 const body_start = payloads.items[multi_item_body_table + multi_item_i];
8096 end_index += 2;8107 const body = payloads.items[body_start..][0..body_len];
8097 } else {8108 astgen.extra.appendSliceAssumeCapacity(body);
8098 body_len_index += 2;8109 }
8099 const items_len = payloads.items[start_index];8110 multi_item_i += 1;
8100 const ranges_len = payloads.items[start_index + 1];
8101 end_index += 3 + items_len + 2 * ranges_len;
8102 }8111 }
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]);
8106 }8112 }
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
8108 if (need_result_rvalue) {8118 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);
8110 } else {8120 } else {
8111 return switch_block.toRef();8121 return switch_block.toRef();
8112 }8122 }
...@@ -13786,6 +13796,19 @@ fn scanContainer(...@@ -13786,6 +13796,19 @@ fn scanContainer(
13786 return error.AnalysisFail;13796 return error.AnalysisFail;
13787}13797}
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
13789/// Assumes capacity for body has already been added. Needed capacity taking into13812/// Assumes capacity for body has already been added. Needed capacity taking into
13790/// account fixups can be found with `countBodyLenAfterFixups`.13813/// account fixups can be found with `countBodyLenAfterFixups`.
13791fn appendBodyWithFixups(astgen: *AstGen, body: []const Zir.Inst.Index) void {13814fn 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) {...@@ -95,7 +95,6 @@ pub fn extraData(code: Zir, comptime T: type, index: usize) ExtraData(T) {
95 Inst.Call.Flags,95 Inst.Call.Flags,
96 Inst.BuiltinCall.Flags,96 Inst.BuiltinCall.Flags,
97 Inst.SwitchBlock.Bits,97 Inst.SwitchBlock.Bits,
98 Inst.SwitchBlockErrUnion.Bits,
99 Inst.FuncFancy.Bits,98 Inst.FuncFancy.Bits,
100 Inst.Declaration.Flags,99 Inst.Declaration.Flags,
101 Inst.Param.Type,100 Inst.Param.Type,
...@@ -350,7 +349,8 @@ pub const Inst = struct {...@@ -350,7 +349,8 @@ pub const Inst = struct {
350 /// Uses the `break` union field.349 /// Uses the `break` union field.
351 break_inline,350 break_inline,
352 /// Branch from within a switch case to the case specified by the operand.351 /// 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`.
354 switch_continue,354 switch_continue,
355 /// Checks that comptime control flow does not happen inside a runtime block.355 /// Checks that comptime control flow does not happen inside a runtime block.
356 /// Uses the `un_node` union field.356 /// Uses the `un_node` union field.
...@@ -722,8 +722,10 @@ pub const Inst = struct {...@@ -722,8 +722,10 @@ pub const Inst = struct {
722 /// A switch expression. Uses the `pl_node` union field.722 /// A switch expression. Uses the `pl_node` union field.
723 /// AST node is the switch, payload is `SwitchBlock`. Operand is a pointer.723 /// AST node is the switch, payload is `SwitchBlock`. Operand is a pointer.
724 switch_block_ref,724 switch_block_ref,
725 /// A switch on an error union `a catch |err| switch (err) {...}`.725 /// A switch on an error union:
726 /// Uses the `pl_node` union field. AST node is the `catch`, payload is `SwitchBlockErrUnion`.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`.
727 switch_block_err_union,729 switch_block_err_union,
728 /// Check that operand type supports the dereference operand (.*).730 /// Check that operand type supports the dereference operand (.*).
729 /// Uses the `un_node` field.731 /// Uses the `un_node` field.
...@@ -3293,143 +3295,168 @@ pub const Inst = struct {...@@ -3293,143 +3295,168 @@ pub const Inst = struct {
3293 };3295 };
32943296
3295 /// Trailing:3297 /// Trailing:
3296 /// 0. multi_cases_len: u32 // if `has_multi_cases`3298 /// 0. multi_cases_len: u32, // If has_multi_cases is set.
3297 /// 1. err_capture_inst: u32 // if `any_uses_err_capture`3299 /// 1. payload_capture_placeholder: Inst.Index, // If payload_capture_inst_is_placeholder is set.
3298 /// 2. non_err_body {3300 /// // Index of instruction prongs use to refer to their payload capture.
3299 /// info: ProngInfo,3301 /// 2. tag_capture_placeholder: Inst.Index, // If tag_capture_inst_is_placeholder is set.
3300 /// inst: Index // for every `info.body_len`3302 /// // Index of instruction prongs use to refer to their tag capture.
3301 /// }3303 /// 3. catch_or_if_src_node_offset: Ast.Node.Offset, // If inst is switch_block_err_union.
3302 /// 3. else_body { // if `has_else`3304 /// 4. non_err_info: ProngInfo.NonErr, // If inst is switch_block_err_union.
3303 /// info: ProngInfo,3305 /// 5. else_info: ProngInfo.Else, // If has_else is set.
3304 /// inst: Index // for every `info.body_len`3306 /// 6. under_info: ProngInfo.Under, // If has_under is set and
3305 /// }3307 /// // under_is_bare is set.
3306 /// 4. scalar_cases: { // for every `scalar_cases_len`3308 /// 7. under_index: u32, // If has_under is set and
3307 /// item: Ref,3309 /// // under_is_bare is *not* set.
3308 /// info: ProngInfo,3310 /// // Index into switch cases.
3309 /// inst: Index // for every `info.body_len`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
3310 /// }3325 /// }
3311 /// 5. multi_cases: { // for every `multi_cases_len`3326 /// 15. else_body: { // If has_else is set.
3312 /// items_len: u32,3327 /// body_inst: Inst.Index, // for every else_info.body_len
3313 /// ranges_len: u32,3328 /// }
3314 /// info: ProngInfo,3329 /// 16. under_body: { // If has_under is set and
3315 /// item: Ref // for every `items_len`3330 /// // under_is_bare is set.
3316 /// ranges: { // for every `ranges_len`3331 /// body_inst: Inst.Index, // for every under_info.body_len
3317 /// item_first: Ref,3332 /// }
3318 /// item_last: Ref,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
3319 /// }3339 /// }
3320 /// inst: Index // for every `info.body_len`
3321 /// }3340 /// }
3322 ///3341 /// 18. multi_bodies: { // for each multi_items_info
3323 /// When analyzing a case body, the switch instruction itself refers to the3342 /// prong_body: {
3324 /// captured error, or to the success value in `non_err_body`. Whether this3343 /// body_inst: Inst.Index, // for each multi_prong_info.body_len
3325 /// is captured by reference or by value depends on whether the `byref` bit3344 /// }
3326 /// is set for the corresponding body. `err_capture_inst` refers to the error3345 /// item_body: { // for each item_info
3327 /// capture outside of the `switch`, i.e. `err` in3346 /// body_inst: Inst.Index, // for every item_info.body_len
3328 /// `x catch |err| switch (err) { ... }`.3347 /// }
3329 pub const SwitchBlockErrUnion = struct {3348 /// range_bodies: { // for each .{first_info, last_info} in range_items_info
3330 operand: Ref,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,
3331 bits: Bits,3356 bits: Bits,
3332 main_src_node_offset: Ast.Node.Offset,
33333357
3334 pub const Bits = packed struct(u32) {3358 pub const Bits = packed struct(u32) {
3335 /// If true, one or more prongs have multiple items.3359 /// If true, one or more prongs have multiple items.
3336 has_multi_cases: bool,3360 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,
3338 has_else: bool,3364 has_else: bool,
3339 any_uses_err_capture: bool,3365 has_under: bool,
3340 payload_is_ref: 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,
3341 scalar_cases_len: ScalarCasesLen,3375 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;
3344 };3381 };
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.
3399 pub const ProngInfo = packed struct(u32) {3383 pub const ProngInfo = packed struct(u32) {
3400 body_len: u28,3384 body_len: u27,
3401 capture: ProngInfo.Capture,3385 capture: ProngInfo.Capture,
3402 is_inline: bool,3386 is_inline: bool,
3403 has_tag_capture: bool,3387 has_tag_capture: bool,
3388 is_comptime_unreach: bool,
34043389
3405 pub const Capture = enum(u2) {3390 pub const Capture = enum(u2) {
3406 none,3391 none,
3407 by_val,3392 by_val,
3408 by_ref,3393 by_ref,
3409 };3394 };
3410 };
34113395
3412 pub const Bits = packed struct(u32) {3396 pub const NonErr = packed struct(u32) {
3413 /// If true, one or more prongs have multiple items.3397 body_len: u29,
3414 has_multi_cases: bool,3398 capture: ProngInfo.Capture,
3415 /// Information about the special prong.3399 operand_is_ref: bool,
3416 special_prongs: SpecialProngs,3400 };
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,
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 };
3427 };3415 };
34283416
3429 pub const MultiProng = struct {3417 pub const ItemInfo = packed struct(u32) {
3430 items: []const Ref,3418 kind: ItemInfo.Kind,
3431 body: []const Index,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 }
3432 };3457 };
3458
3459 pub const Kind = enum { default, ref, err_union };
3433 };3460 };
34343461
3435 pub const ArrayInitRefTy = struct {3462 pub const ArrayInitRefTy = struct {
...@@ -4004,69 +4031,6 @@ pub const Inst = struct {...@@ -4004,69 +4031,6 @@ pub const Inst = struct {
4004 };4031 };
4005};4032};
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
4070pub const DeclIterator = struct {4034pub const DeclIterator = struct {
4071 extra_index: u32,4035 extra_index: u32,
4072 decls_remaining: u32,4036 decls_remaining: u32,
...@@ -4842,8 +4806,48 @@ fn findTrackableInner(...@@ -4842,8 +4806,48 @@ fn findTrackableInner(
4842 const body = zir.bodySlice(extra.end, extra.data.body_len);4806 const body = zir.bodySlice(extra.end, extra.data.body_len);
4843 try zir.findTrackableBody(gpa, contents, defers, body);4807 try zir.findTrackableBody(gpa, contents, defers, body);
4844 },4808 },
4845 .switch_block, .switch_block_ref => return zir.findTrackableSwitch(gpa, contents, defers, inst, .normal),4809
4846 .switch_block_err_union => return zir.findTrackableSwitch(gpa, contents, defers, inst, .err_union),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
4848 .suspend_block => @panic("TODO iterate suspend block"),4852 .suspend_block => @panic("TODO iterate suspend block"),
48494853
...@@ -4890,119 +4894,6 @@ fn findTrackableInner(...@@ -4890,119 +4894,6 @@ fn findTrackableInner(
4890 }4894 }
4891}4895}
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
5006fn findTrackableBody(4897fn findTrackableBody(
5007 zir: Zir,4898 zir: Zir,
5008 gpa: Allocator,4899 gpa: Allocator,
...@@ -5337,6 +5228,306 @@ pub fn getAssociatedSrcHash(zir: Zir, inst: Zir.Inst.Index) ?std.zig.SrcHash {...@@ -5337,6 +5228,306 @@ pub fn getAssociatedSrcHash(zir: Zir, inst: Zir.Inst.Index) ?std.zig.SrcHash {
5337 }5228 }
5338}5229}
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
5340/// When the ZIR update tracking logic must be modified to consider new instructions,5531/// When the ZIR update tracking logic must be modified to consider new instructions,
5341/// change this constant to trigger compile errors at all relevant locations.5532/// change this constant to trigger compile errors at all relevant locations.
5342pub const inst_tracking_version = 0;5533pub const inst_tracking_version = 0;
src/print_zir.zig+111-284
...@@ -447,10 +447,9 @@ const Writer = struct {...@@ -447,10 +447,9 @@ const Writer = struct {
447447
448 .switch_block,448 .switch_block,
449 .switch_block_ref,449 .switch_block_ref,
450 .switch_block_err_union,
450 => try self.writeSwitchBlock(stream, inst),451 => try self.writeSwitchBlock(stream, inst),
451452
452 .switch_block_err_union => try self.writeSwitchBlockErrUnion(stream, inst),
453
454 .field_ptr_load,453 .field_ptr_load,
455 .field_ptr,454 .field_ptr,
456 .decl_literal,455 .decl_literal,
...@@ -1987,322 +1986,150 @@ const Writer = struct {...@@ -1987,322 +1986,150 @@ const Writer = struct {
1987 try self.writeSrcNode(stream, inst_data.src_node);1986 try self.writeSrcNode(stream, inst_data.src_node);
1988 }1987 }
19891988
1990 fn writeSwitchBlockErrUnion(self: *Writer, stream: *std.Io.Writer, inst: Zir.Inst.Index) !void {1989 fn writeSwitchBlock(
1991 const inst_data = self.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;1990 self: *Writer,
1992 const extra = self.code.extraData(Zir.Inst.SwitchBlockErrUnion, inst_data.payload_index);1991 stream: *std.Io.Writer,
19931992 inst: Zir.Inst.Index,
1994 var extra_index: usize = extra.end;1993 ) !void {
19951994 const zir_switch = self.code.getSwitchBlock(inst);
1996 const multi_cases_len = if (extra.data.bits.has_multi_cases) blk: {1995 var extra_index = zir_switch.end;
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);
20091996
2010 if (extra.data.bits.any_uses_err_capture) {1997 try self.writeInstRef(stream, zir_switch.main_operand);
2011 try stream.writeAll(", err_capture=");
2012 try self.writeInstIndex(stream, err_capture_inst);
2013 }
20141998
2015 self.indent += 2;1999 self.indent += 2;
20162000
2017 {2001 if (zir_switch.non_err_case) |non_err_case| {
2018 const info = @as(Zir.Inst.SwitchBlock.ProngInfo, @bitCast(self.code.extra[extra_index]));2002 if (non_err_case.operand_is_ref) try stream.writeAll(" ref");
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;
20242003
2025 try stream.writeAll(",\n");2004 try stream.writeAll(",\n");
2026 try stream.splatByteAll(' ', self.indent);2005 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) {2007 try self.writeSwitchCaptures(stream, non_err_case.capture, false, inst, &zir_switch);
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;
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| {
2043 try stream.writeAll(",\n");2015 try stream.writeAll(",\n");
2044 try stream.splatByteAll(' ', self.indent);2016 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: {2018 try self.writeSwitchCaptures(stream, else_case.capture, else_case.has_tag_capture, inst, &zir_switch);
2140 const tag_capture_inst = self.code.extra[extra_index];2019 if (else_case.is_inline) try stream.writeAll("inline ");
2141 extra_index += 1;
2142 break :blk @enumFromInt(tag_capture_inst);
2143 } else undefined;
2144
2145 try self.writeInstRef(stream, extra.data.operand);
21462020
2147 if (extra.data.bits.any_has_tag_capture) {2021 try stream.writeAll("else => ");
2148 try stream.writeAll(", tag_capture=");2022 try self.writeBracedBody(stream, else_case.body);
2149 try self.writeInstIndex(stream, tag_capture_inst);
2150 }2023 }
21512024 if (zir_switch.under_case.resolve()) |under_case| {
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
2168 try stream.writeAll(",\n");2025 try stream.writeAll(",\n");
2169 try stream.splatByteAll(' ', self.indent);2026 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()) {2028 try self.writeSwitchCaptures(stream, under_case.capture, under_case.has_tag_capture, inst, &zir_switch);
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;
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| {
2192 try stream.writeAll(",\n");2036 try stream.writeAll(",\n");
2193 try stream.splatByteAll(' ', self.indent);2037 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("_");2039 const prong_info = case.prong_info;
2202 if (single_item_ref != .none) {2040 try self.writeSwitchCaptures(stream, prong_info.capture, prong_info.has_tag_capture, inst, &zir_switch);
2203 try stream.writeAll(", ");2041 if (prong_info.is_inline) try stream.writeAll("inline ");
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 }
22102042
2211 var range_i: usize = 0;2043 const prong_body = self.code.bodySlice(extra_index, prong_info.body_len);
2212 while (range_i < ranges_len) : (range_i += 1) {2044 extra_index += prong_body.len;
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;
22172045
2218 try stream.writeAll(", ");2046 var first_item: bool = true;
2219 try self.writeInstRef(stream, item_first);2047 if (case.isUnder()) {
2220 try stream.writeAll("...");2048 try stream.writeAll("_");
2221 try self.writeInstRef(stream, item_last);2049 first_item = false;
2222 }2050 }
22232051 for (case.item_infos) |item_info| {
2224 const body = self.code.bodySlice(extra_index, info.body_len);2052 if (!first_item) try stream.writeAll(", ");
2225 extra_index += info.body_len;2053 first_item = false;
2226 try stream.writeAll(" => ");2054
2227 try self.writeBracedBody(stream, body);2055 switch (item_info.unwrap()) {
2228 }2056 .enum_literal => |str_index| {
22292057 const str = self.code.nullTerminatedString(str_index);
2230 {2058 try stream.print("\".{f}\"", .{std.zig.fmtString(str)});
2231 const scalar_cases_len = extra.data.bits.scalar_cases_len;2059 },
2232 var scalar_i: usize = 0;2060 .error_value => |str_index| {
2233 while (scalar_i < scalar_cases_len) : (scalar_i += 1) {2061 const str = self.code.nullTerminatedString(str_index);
2234 const item_ref: Zir.Inst.Ref = @enumFromInt(self.code.extra[extra_index]);2062 try stream.print("\"error.{f}\"", .{std.zig.fmtString(str)});
2235 extra_index += 1;2063 },
2236 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(self.code.extra[extra_index]);2064 .number_literal => |zir_ref| {
2237 extra_index += 1;2065 try self.writeInstRef(stream, zir_ref);
2238 const body = self.code.bodySlice(extra_index, info.body_len);2066 },
2239 extra_index += info.body_len;2067 .body_len => |body_len| {
22402068 const item_body = self.code.bodySlice(extra_index, body_len);
2241 try stream.writeAll(",\n");2069 extra_index += item_body.len;
2242 try stream.splatByteAll(' ', self.indent);2070 try self.writeBracedDecl(stream, item_body);
2243 switch (info.capture) {2071 },
2244 .none => {},
2245 .by_val => try stream.writeAll("by_val "),
2246 .by_ref => try stream.writeAll("by_ref "),
2247 }2072 }
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);
2252 }2073 }
2253 }2074 for (case.range_infos) |range_info| {
2254 {2075 if (!first_item) try stream.writeAll(", ");
2255 var multi_i: usize = 0;2076 first_item = false;
2256 while (multi_i < multi_cases_len) : (multi_i += 1) {2077
2257 const items_len = self.code.extra[extra_index];2078 var first_range_item = true;
2258 extra_index += 1;2079 for (&range_info) |item_info| {
2259 const ranges_len = self.code.extra[extra_index];2080 if (!first_range_item) try stream.writeAll("...");
2260 extra_index += 1;2081 first_range_item = false;
2261 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(self.code.extra[extra_index]);2082
2262 extra_index += 1;2083 switch (item_info.unwrap()) {
2263 const items = self.code.refSlice(extra_index, items_len);2084 .enum_literal => |str_index| {
2264 extra_index += items_len;2085 const str = self.code.nullTerminatedString(str_index);
22652086 try stream.print("\".{f}\"", .{std.zig.fmtString(str)});
2266 try stream.writeAll(",\n");2087 },
2267 try stream.splatByteAll(' ', self.indent);2088 .error_value => |str_index| {
2268 switch (info.capture) {2089 const str = self.code.nullTerminatedString(str_index);
2269 .none => {},2090 try stream.print("\"error.{f}\"", .{std.zig.fmtString(str)});
2270 .by_val => try stream.writeAll("by_val "),2091 },
2271 .by_ref => try stream.writeAll("by_ref "),2092 .number_literal => |zir_ref| {
2272 }2093 try self.writeInstRef(stream, zir_ref);
2273 if (info.is_inline) try stream.writeAll("inline ");2094 },
22742095 .body_len => |body_len| {
2275 for (items, 0..) |item_ref, item_i| {2096 const item_body = self.code.bodySlice(extra_index, body_len);
2276 if (item_i != 0) try stream.writeAll(", ");2097 extra_index += item_body.len;
2277 try self.writeInstRef(stream, item_ref);2098 try self.writeBracedDecl(stream, item_body);
2278 }2099 },
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(", ");
2289 }2100 }
2290 try self.writeInstRef(stream, item_first);
2291 try stream.writeAll("...");
2292 try self.writeInstRef(stream, item_last);
2293 }2101 }
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);
2299 }2102 }
2103 try stream.writeAll(" => ");
2104 try self.writeBracedBody(stream, prong_body);
2300 }2105 }
23012106
2302 self.indent -= 2;2107 self.indent -= 2;
23032108
2304 try stream.writeAll(") ");2109 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 }
2306 }2133 }
23072134
2308 fn writePlNodeField(self: *Writer, stream: *std.Io.Writer, inst: Zir.Inst.Index) !void {2135 fn writePlNodeField(self: *Writer, stream: *std.Io.Writer, inst: Zir.Inst.Index) !void {