From 5b00e24b6e28c24d7fa2417e9b7b88e13e75741e Mon Sep 17 00:00:00 2001 From: Justus Klausecker Date: Wed, 7 Jan 2026 17:07:34 +0100 Subject: [PATCH] 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). --- lib/std/zig/AstGen.zig | 1645 ++++++++++++++++++++-------------------- lib/std/zig/Zir.zig | 783 +++++++++++-------- src/print_zir.zig | 405 +++------- 3 files changed, 1437 insertions(+), 1396 deletions(-) diff --git a/lib/std/zig/AstGen.zig b/lib/std/zig/AstGen.zig index f7d5b386815c84eaf0c2674ea859f8dcc39e16c1..124543b8836223393607f66605ee5d51e04feebe 100644 --- a/lib/std/zig/AstGen.zig +++ b/lib/std/zig/AstGen.zig @@ -115,7 +115,6 @@ fn setExtra(astgen: *AstGen, index: usize, extra: anytype) void { Zir.Inst.Call.Flags, Zir.Inst.BuiltinCall.Flags, Zir.Inst.SwitchBlock.Bits, - Zir.Inst.SwitchBlockErrUnion.Bits, Zir.Inst.FuncFancy.Bits, Zir.Inst.Param.Type, Zir.Inst.Func.RetTy, @@ -858,11 +857,11 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE no_switch_on_err: { const error_token = if_full.error_token orelse break :no_switch_on_err; const else_node = if_full.ast.else_expr.unwrap() orelse break :no_switch_on_err; - const full_switch = tree.fullSwitch(else_node) orelse break :no_switch_on_err; - if (full_switch.label_token != null) break :no_switch_on_err; - if (tree.nodeTag(full_switch.ast.condition) != .identifier) break :no_switch_on_err; - if (!mem.eql(u8, tree.tokenSlice(error_token), tree.tokenSlice(tree.nodeMainToken(full_switch.ast.condition)))) break :no_switch_on_err; - return switchExprErrUnion(gz, scope, ri.br(), node, .@"if"); + const switch_full = tree.fullSwitch(else_node) orelse break :no_switch_on_err; + if (switch_full.label_token != null) break :no_switch_on_err; // handled in `ifExpr` + if (tree.nodeTag(switch_full.ast.condition) != .identifier) break :no_switch_on_err; + if (!try astgen.tokenIdentEql(error_token, tree.nodeMainToken(switch_full.ast.condition))) break :no_switch_on_err; + return switchExpr(gz, scope, ri.br(), node, switch_full, .{ .@"if" = if_full }); } return ifExpr(gz, scope, ri.br(), node, if_full); }, @@ -1024,11 +1023,11 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE null; no_switch_on_err: { const capture_token = payload_token orelse break :no_switch_on_err; - const full_switch = tree.fullSwitch(tree.nodeData(node).node_and_node[1]) orelse break :no_switch_on_err; - if (full_switch.label_token != null) break :no_switch_on_err; - if (tree.nodeTag(full_switch.ast.condition) != .identifier) break :no_switch_on_err; - if (!mem.eql(u8, tree.tokenSlice(capture_token), tree.tokenSlice(tree.nodeMainToken(full_switch.ast.condition)))) break :no_switch_on_err; - return switchExprErrUnion(gz, scope, ri.br(), node, .@"catch"); + const switch_full = tree.fullSwitch(tree.nodeData(node).node_and_node[1]) orelse break :no_switch_on_err; + if (switch_full.label_token != null) break :no_switch_on_err; // handled in `orelseCatchExpr` + if (tree.nodeTag(switch_full.ast.condition) != .identifier) break :no_switch_on_err; + if (!try astgen.tokenIdentEql(capture_token, tree.nodeMainToken(switch_full.ast.condition))) break :no_switch_on_err; + return switchExpr(gz, scope, ri.br(), node, switch_full, .@"catch"); } switch (ri.rl) { .ref, .ref_coerced_ty => return orelseCatchExpr( @@ -1108,7 +1107,7 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE .error_set_decl => return errorSetDecl(gz, ri, node), .array_access => return arrayAccess(gz, scope, ri, node), .@"comptime" => return comptimeExprAst(gz, scope, ri, node), - .@"switch", .switch_comma => return switchExpr(gz, scope, ri.br(), node, tree.fullSwitch(node).?), + .@"switch", .switch_comma => return switchExpr(gz, scope, ri.br(), node, tree.fullSwitch(node).?, .none), .@"nosuspend" => return nosuspendExpr(gz, scope, ri, node), .@"suspend" => return suspendExpr(gz, scope, node), @@ -3134,14 +3133,7 @@ fn deferStmt( } const remapped_err_code: Zir.Inst.Index = @enumFromInt(gz.astgen.instructions.len); opt_remapped_err_code = remapped_err_code.toOptional(); - try gz.astgen.instructions.append(gz.astgen.gpa, .{ - .tag = .extended, - .data = .{ .extended = .{ - .opcode = .value_placeholder, - .small = undefined, - .operand = undefined, - } }, - }); + _ = try gz.astgen.appendPlaceholder(); const remapped_err_code_ref = remapped_err_code.toRef(); local_val_scope = .{ .parent = &defer_gen.base, @@ -6115,7 +6107,30 @@ fn orelseCatchExpr( break :blk &err_val_scope.base; }; - const else_result = try fullBodyExpr(&else_scope, else_sub_scope, block_scope.break_result_info, rhs, .allow_branch_hint); + const else_result = else_result: { + if (tree.fullSwitch(rhs)) |switch_full| no_switch_on_err: { + if (tree.nodeTag(node) != .@"catch") break :no_switch_on_err; + const catch_token = tree.nodeMainToken(node); + const capture_token = if (tree.tokenTag(catch_token + 1) == .pipe) token: { + break :token catch_token + 2; + } else break :no_switch_on_err; + if (switch_full.label_token == null) break :no_switch_on_err; // must use `switchExpr` with `non_err = .@"if"` + if (tree.nodeTag(switch_full.ast.condition) != .identifier) break :no_switch_on_err; + if (!try astgen.tokenIdentEql(capture_token, tree.nodeMainToken(switch_full.ast.condition))) break :no_switch_on_err; + break :else_result try switchExpr( + &else_scope, + else_sub_scope, + block_scope.break_result_info, + rhs, + switch_full, + .{ .peer_break_target = .{ + .block_inst = block, + .block_ri = block_ri, + } }, + ); + } + break :else_result try fullBodyExpr(&else_scope, else_sub_scope, block_scope.break_result_info, rhs, .allow_branch_hint); + }; if (!else_scope.endsWithNoReturn()) { // As our last action before the break, "pop" the error trace if needed if (do_err_trace) @@ -6468,7 +6483,26 @@ fn ifExpr( break :s &else_scope.base; } }; - const else_result = try fullBodyExpr(&else_scope, sub_scope, block_scope.break_result_info, else_node, .allow_branch_hint); + const else_result = else_result: { + if (tree.fullSwitch(else_node)) |switch_full| no_switch_on_err: { + const error_token = if_full.error_token orelse break :no_switch_on_err; + if (switch_full.label_token == null) break :no_switch_on_err; // must use `switchExpr` with `non_err = .@"if"` + if (tree.nodeTag(switch_full.ast.condition) != .identifier) break :no_switch_on_err; + if (!try astgen.tokenIdentEql(error_token, tree.nodeMainToken(switch_full.ast.condition))) break :no_switch_on_err; + break :else_result try switchExpr( + &else_scope, + sub_scope, + block_scope.break_result_info, + else_node, + switch_full, + .{ .peer_break_target = .{ + .block_inst = block, + .block_ri = block_ri, + } }, + ); + } + break :else_result try fullBodyExpr(&else_scope, sub_scope, block_scope.break_result_info, else_node, .allow_branch_hint); + }; if (!else_scope.endsWithNoReturn()) { // As our last action before the break, "pop" the error trace if needed if (do_err_trace) @@ -7117,568 +7151,156 @@ fn forExpr( return result; } -fn switchExprErrUnion( +const SwitchNonErr = union(enum) { + /// A regular switch expression. + /// Emits `switch_block[_ref]`. + none, + /// `eu catch |err| switch (err) { ... }` + /// + /// `switch` must not be labeled. + /// Emits `switch_block_err_union`. + @"catch", + /// `if (eu) |payload| { ... } else |err| switch (err) { ... }` + /// + /// `switch` must not be labeled. + /// Emits `switch_block_err_union`. + @"if": Ast.full.If, + /// `eu catch |err| label: switch (err) { ... }` + /// `if (eu) |payload| { ... } else |err| label: switch (err) { ... }` + /// + /// `switch` must be labeled. + /// Emits a `condbr` on the non-error body and a regular switch, though the + /// non-error prong and all `break`s from switch prongs are peers. + /// Exists to avoid a rather complex special case of `switch_block_err_union`. + peer_break_target: struct { + /// Refers to the enclosing block of the entire switch-on-err expression. + block_inst: Zir.Inst.Index, + /// Belongs to `block_inst`. + block_ri: ResultInfo, + }, +}; + +fn switchExpr( parent_gz: *GenZir, scope: *Scope, ri: ResultInfo, - catch_or_if_node: Ast.Node.Index, - node_ty: enum { @"catch", @"if" }, + node: Ast.Node.Index, + switch_full: Ast.full.Switch, + non_err: SwitchNonErr, ) InnerError!Zir.Inst.Ref { const astgen = parent_gz.astgen; const gpa = astgen.gpa; const tree = astgen.tree; - const if_full = switch (node_ty) { - .@"catch" => undefined, - .@"if" => tree.fullIf(catch_or_if_node).?, - }; - - const switch_node, const operand_node, const error_payload = switch (node_ty) { + const switch_node, const operand_node, const err_token = switch (non_err) { + .none, .peer_break_target => .{ + node, + switch_full.ast.condition, + undefined, + }, .@"catch" => .{ - tree.nodeData(catch_or_if_node).node_and_node[1], - tree.nodeData(catch_or_if_node).node_and_node[0], - tree.nodeMainToken(catch_or_if_node) + 2, + tree.nodeData(node).node_and_node[1], + tree.nodeData(node).node_and_node[0], + tree.nodeMainToken(node) + 2, }, - .@"if" => .{ + .@"if" => |if_full| .{ if_full.ast.else_expr.unwrap().?, if_full.ast.cond_expr, if_full.error_token.?, }, }; - const switch_full = tree.fullSwitch(switch_node).?; + const case_nodes = switch_full.ast.cases; - const do_err_trace = astgen.fn_block != null; - const need_rl = astgen.nodes_need_rl.contains(catch_or_if_node); + const is_err_switch = non_err != .none; + const needs_non_err_handling = switch (non_err) { + .none => false, + .peer_break_target => false, // handled by parent expression + .@"catch", .@"if" => true, + }; + + const need_rl = astgen.nodes_need_rl.contains(node); const block_ri: ResultInfo = if (need_rl) ri else .{ .rl = switch (ri.rl) { - .ptr => .{ .ty = (try ri.rl.resultType(parent_gz, catch_or_if_node)).? }, + .ptr => .{ .ty = (try ri.rl.resultType(parent_gz, node)).? }, .inferred_ptr => .none, else => ri.rl, }, .ctx = ri.ctx, }; - const payload_is_ref = switch (node_ty) { - .@"if" => if_full.payload_token != null and tree.tokenTag(if_full.payload_token.?) == .asterisk, + // We need to call `rvalue` to write through to the pointer only if we had a + // result pointer and aren't forwarding it. + const LocTag = @typeInfo(ResultInfo.Loc).@"union".tag_type.?; + const need_result_rvalue = @as(LocTag, block_ri.rl) != @as(LocTag, ri.rl); + + const catch_or_if_node = if (needs_non_err_handling) node else undefined; + const do_err_trace = needs_non_err_handling and astgen.fn_block != null; + const non_err_is_ref: bool = switch (non_err) { + .none, .peer_break_target => undefined, .@"catch" => ri.rl == .ref or ri.rl == .ref_coerced_ty, + .@"if" => |if_full| if_full.payload_token != null and + tree.tokenTag(if_full.payload_token.?) == .asterisk, }; - // We need to call `rvalue` to write through to the pointer only if we had a - // result pointer and aren't forwarding it. - const LocTag = @typeInfo(ResultInfo.Loc).@"union".tag_type.?; - const need_result_rvalue = @as(LocTag, block_ri.rl) != @as(LocTag, ri.rl); - var scalar_cases_len: u32 = 0; - var multi_cases_len: u32 = 0; - var inline_cases_len: u32 = 0; - var has_else = false; - var else_node: Ast.Node.OptionalIndex = .none; - var else_src: ?Ast.TokenIndex = null; - for (switch_full.ast.cases) |case_node| { - const case = tree.fullSwitchCase(case_node).?; - - if (case.ast.values.len == 0) { - const case_src = case.ast.arrow_token - 1; - if (else_src) |src| { - return astgen.failTokNotes( - case_src, - "multiple else prongs in switch expression", - .{}, - &[_]u32{ - try astgen.errNoteTok( - src, - "previous else prong here", - .{}, - ), - }, - ); - } - has_else = true; - else_node = case_node.toOptional(); - else_src = case_src; - continue; - } else if (case.ast.values.len == 1 and - tree.nodeTag(case.ast.values[0]) == .identifier and - mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(case.ast.values[0])), "_")) - { - const case_src = case.ast.arrow_token - 1; - return astgen.failTokNotes( - case_src, - "'_' prong is not allowed when switching on errors", - .{}, - &[_]u32{ - try astgen.errNoteTok( - case_src, - "consider using 'else'", - .{}, - ), - }, - ); - } - - for (case.ast.values) |val| { - if (tree.nodeTag(val) == .string_literal) - return astgen.failNode(val, "cannot switch on strings", .{}); - } - - if (case.ast.values.len == 1 and tree.nodeTag(case.ast.values[0]) != .switch_range) { - scalar_cases_len += 1; - } else { - multi_cases_len += 1; - } - if (case.inline_token != null) { - inline_cases_len += 1; - } - } - - const operand_ri: ResultInfo = .{ - .rl = if (payload_is_ref) .ref else .none, - .ctx = .error_handling_expr, - }; - - astgen.advanceSourceCursorToNode(operand_node); - const operand_lc: LineColumn = .{ astgen.source_line - parent_gz.decl_line, astgen.source_column }; - - const raw_operand = try reachableExpr(parent_gz, scope, operand_ri, operand_node, switch_node); - const item_ri: ResultInfo = .{ .rl = .none }; - - // This contains the data that goes into the `extra` array for the SwitchBlockErrUnion, except - // the first cases_nodes.len slots are a table that indexes payloads later in the array, - // with the non-error and else case indices coming first, then scalar_cases_len indexes, then - // multi_cases_len indexes - const payloads = &astgen.scratch; - const scratch_top = astgen.scratch.items.len; - const case_table_start = scratch_top; - const scalar_case_table = case_table_start + 1 + @intFromBool(has_else); - const multi_case_table = scalar_case_table + scalar_cases_len; - const case_table_end = multi_case_table + multi_cases_len; - - try astgen.scratch.resize(gpa, case_table_end); - defer astgen.scratch.items.len = scratch_top; - - var block_scope = parent_gz.makeSubBlock(scope); - // block_scope not used for collecting instructions - block_scope.instructions_top = GenZir.unstacked_top; - block_scope.setBreakResultInfo(block_ri); - - // Sema expects a dbg_stmt immediately before switch_block_err_union - try emitDbgStmtForceCurrentIndex(parent_gz, operand_lc); - // This gets added to the parent block later, after the item expressions. - const switch_block = try parent_gz.makeBlockInst(.switch_block_err_union, switch_node); - - // We re-use this same scope for all cases, including the special prong, if any. - var case_scope = parent_gz.makeSubBlock(&block_scope.base); - case_scope.instructions_top = GenZir.unstacked_top; - - { - const body_len_index: u32 = @intCast(payloads.items.len); - payloads.items[case_table_start] = body_len_index; - try payloads.resize(gpa, body_len_index + 1); // body_len - - case_scope.instructions_top = parent_gz.instructions.items.len; - defer case_scope.unstack(); - - const unwrap_payload_tag: Zir.Inst.Tag = if (payload_is_ref) - .err_union_payload_unsafe_ptr - else - .err_union_payload_unsafe; - - const unwrapped_payload = try case_scope.addUnNode( - unwrap_payload_tag, - raw_operand, - catch_or_if_node, - ); - - switch (node_ty) { - .@"catch" => { - const case_result = switch (ri.rl) { - .ref, .ref_coerced_ty => unwrapped_payload, - else => try rvalue( - &case_scope, - block_scope.break_result_info, - unwrapped_payload, - catch_or_if_node, - ), - }; - _ = try case_scope.addBreakWithSrcNode( - .@"break", - switch_block, - case_result, - catch_or_if_node, - ); - }, - .@"if" => { - var payload_val_scope: Scope.LocalVal = undefined; - - const then_node = if_full.ast.then_expr; - const then_sub_scope = s: { - assert(if_full.error_token != null); - if (if_full.payload_token) |payload_token| { - const token_name_index = payload_token + @intFromBool(payload_is_ref); - const ident_name = try astgen.identAsString(token_name_index); - const token_name_str = tree.tokenSlice(token_name_index); - if (mem.eql(u8, "_", token_name_str)) - break :s &case_scope.base; - try astgen.detectLocalShadowing( - &case_scope.base, - ident_name, - token_name_index, - token_name_str, - .capture, - ); - payload_val_scope = .{ - .parent = &case_scope.base, - .gen_zir = &case_scope, - .name = ident_name, - .inst = unwrapped_payload, - .token_src = token_name_index, - .id_cat = .capture, - }; - try case_scope.addDbgVar(.dbg_var_val, ident_name, unwrapped_payload); - break :s &payload_val_scope.base; - } else { - _ = try case_scope.addUnNode( - .ensure_err_union_payload_void, - raw_operand, - catch_or_if_node, - ); - break :s &case_scope.base; - } - }; - const then_result = try expr( - &case_scope, - then_sub_scope, - block_scope.break_result_info, - then_node, - ); - try checkUsed(parent_gz, &case_scope.base, then_sub_scope); - if (!case_scope.endsWithNoReturn()) { - _ = try case_scope.addBreakWithSrcNode( - .@"break", - switch_block, - then_result, - then_node, - ); - } - }, - } - - const case_slice = case_scope.instructionsSlice(); - const body_len = astgen.countBodyLenAfterFixupsExtraRefs(case_slice, &.{switch_block}); - try payloads.ensureUnusedCapacity(gpa, body_len); - const capture: Zir.Inst.SwitchBlock.ProngInfo.Capture = switch (node_ty) { - .@"catch" => .none, - .@"if" => if (if_full.payload_token == null) - .none - else if (payload_is_ref) - .by_ref - else - .by_val, - }; - payloads.items[body_len_index] = @bitCast(Zir.Inst.SwitchBlock.ProngInfo{ - .body_len = @intCast(body_len), - .capture = capture, - .is_inline = false, - .has_tag_capture = false, - }); - appendBodyWithFixupsExtraRefsArrayList(astgen, payloads, case_slice, &.{switch_block}); + if (switch_full.label_token) |label_token| { + try astgen.checkLabelRedefinition(scope, label_token); } - const err_name = blk: { - const err_str = tree.tokenSlice(error_payload); + const err_capture_name: Zir.NullTerminatedString = if (needs_non_err_handling) blk: { + const err_str = tree.tokenSlice(err_token); if (mem.eql(u8, err_str, "_")) { // This is fatal because we already know we're switching on the captured error. - return astgen.failTok(error_payload, "discard of error capture; omit it instead", .{}); + return astgen.failTok(err_token, "discard of error capture; omit it instead", .{}); } - const err_name = try astgen.identAsString(error_payload); - try astgen.detectLocalShadowing(scope, err_name, error_payload, err_str, .capture); - + const err_name = try astgen.identAsString(err_token); + try astgen.detectLocalShadowing(scope, err_name, err_token, err_str, .capture); break :blk err_name; - }; - - // allocate a shared dummy instruction for the error capture - const err_inst = err_inst: { - const inst: Zir.Inst.Index = @enumFromInt(astgen.instructions.len); - try astgen.instructions.append(astgen.gpa, .{ - .tag = .extended, - .data = .{ .extended = .{ - .opcode = .value_placeholder, - .small = undefined, - .operand = undefined, - } }, - }); - break :err_inst inst; - }; - - // In this pass we generate all the item and prong expressions for error cases. - var multi_case_index: u32 = 0; - var scalar_case_index: u32 = 0; - var any_uses_err_capture = false; - for (switch_full.ast.cases) |case_node| { - const case = tree.fullSwitchCase(case_node).?; - - const is_multi_case = case.ast.values.len > 1 or - (case.ast.values.len == 1 and tree.nodeTag(case.ast.values[0]) == .switch_range); - - var dbg_var_name: Zir.NullTerminatedString = .empty; - var dbg_var_inst: Zir.Inst.Ref = undefined; - var err_scope: Scope.LocalVal = undefined; - var capture_scope: Scope.LocalVal = undefined; - - const sub_scope = blk: { - err_scope = .{ - .parent = &case_scope.base, - .gen_zir = &case_scope, - .name = err_name, - .inst = err_inst.toRef(), - .token_src = error_payload, - .id_cat = .capture, - }; - - const capture_token = case.payload_token orelse break :blk &err_scope.base; - if (tree.tokenTag(capture_token) != .identifier) { - return astgen.failTok(capture_token + 1, "error set cannot be captured by reference", .{}); - } - - const capture_slice = tree.tokenSlice(capture_token); - if (mem.eql(u8, capture_slice, "_")) { - try astgen.appendErrorTok(capture_token, "discard of error capture; omit it instead", .{}); - } - const tag_name = try astgen.identAsString(capture_token); - try astgen.detectLocalShadowing(&case_scope.base, tag_name, capture_token, capture_slice, .capture); - - capture_scope = .{ - .parent = &case_scope.base, - .gen_zir = &case_scope, - .name = tag_name, - .inst = switch_block.toRef(), - .token_src = capture_token, - .id_cat = .capture, - }; - dbg_var_name = tag_name; - dbg_var_inst = switch_block.toRef(); - - err_scope.parent = &capture_scope.base; - - break :blk &err_scope.base; - }; - - const header_index: u32 = @intCast(payloads.items.len); - const body_len_index = if (is_multi_case) blk: { - payloads.items[multi_case_table + multi_case_index] = header_index; - multi_case_index += 1; - try payloads.resize(gpa, header_index + 3); // items_len, ranges_len, body_len - - // items - var items_len: u32 = 0; - for (case.ast.values) |item_node| { - if (tree.nodeTag(item_node) == .switch_range) continue; - items_len += 1; - - const item_inst = try comptimeExpr(parent_gz, scope, item_ri, item_node, .switch_item); - try payloads.append(gpa, @intFromEnum(item_inst)); - } - - // ranges - var ranges_len: u32 = 0; - for (case.ast.values) |range| { - if (tree.nodeTag(range) != .switch_range) continue; - ranges_len += 1; - - const first_node, const last_node = tree.nodeData(range).node_and_node; - const first = try comptimeExpr(parent_gz, scope, item_ri, first_node, .switch_item); - const last = try comptimeExpr(parent_gz, scope, item_ri, last_node, .switch_item); - try payloads.appendSlice(gpa, &[_]u32{ - @intFromEnum(first), @intFromEnum(last), - }); - } - - payloads.items[header_index] = items_len; - payloads.items[header_index + 1] = ranges_len; - break :blk header_index + 2; - } else if (case_node.toOptional() == else_node) blk: { - payloads.items[case_table_start + 1] = header_index; - try payloads.resize(gpa, header_index + 1); // body_len - break :blk header_index; - } else blk: { - payloads.items[scalar_case_table + scalar_case_index] = header_index; - scalar_case_index += 1; - try payloads.resize(gpa, header_index + 2); // item, body_len - const item_node = case.ast.values[0]; - const item_inst = try comptimeExpr(parent_gz, scope, item_ri, item_node, .switch_item); - payloads.items[header_index] = @intFromEnum(item_inst); - break :blk header_index + 1; - }; - - { - // temporarily stack case_scope on parent_gz - case_scope.instructions_top = parent_gz.instructions.items.len; - defer case_scope.unstack(); - - if (do_err_trace and nodeMayAppendToErrorTrace(tree, operand_node)) - _ = try case_scope.addSaveErrRetIndex(.always); - - if (dbg_var_name != .empty) { - try case_scope.addDbgVar(.dbg_var_val, dbg_var_name, dbg_var_inst); - } - - const target_expr_node = case.ast.target_expr; - const case_result = try fullBodyExpr(&case_scope, sub_scope, block_scope.break_result_info, target_expr_node, .allow_branch_hint); - // check capture_scope, not err_scope to avoid false positive unused error capture - try checkUsed(parent_gz, &case_scope.base, err_scope.parent); - const uses_err = err_scope.used != .none or err_scope.discarded != .none; - if (uses_err) { - try case_scope.addDbgVar(.dbg_var_val, err_name, err_inst.toRef()); - any_uses_err_capture = true; - } - - if (!parent_gz.refIsNoReturn(case_result)) { - if (do_err_trace) - try restoreErrRetIndex( - &case_scope, - .{ .block = switch_block }, - block_scope.break_result_info, - target_expr_node, - case_result, - ); - - _ = try case_scope.addBreakWithSrcNode(.@"break", switch_block, case_result, target_expr_node); - } - - const case_slice = case_scope.instructionsSlice(); - const extra_insts: []const Zir.Inst.Index = if (uses_err) &.{ switch_block, err_inst } else &.{switch_block}; - const body_len = astgen.countBodyLenAfterFixupsExtraRefs(case_slice, extra_insts); - try payloads.ensureUnusedCapacity(gpa, body_len); - payloads.items[body_len_index] = @bitCast(Zir.Inst.SwitchBlock.ProngInfo{ - .body_len = @intCast(body_len), - .capture = if (case.payload_token != null) .by_val else .none, - .is_inline = case.inline_token != null, - .has_tag_capture = false, - }); - appendBodyWithFixupsExtraRefsArrayList(astgen, payloads, case_slice, extra_insts); - } - } - // Now that the item expressions are generated we can add this. - try parent_gz.instructions.append(gpa, switch_block); - - try astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.SwitchBlockErrUnion).@"struct".fields.len + - @intFromBool(multi_cases_len != 0) + - payloads.items.len - case_table_end + - (case_table_end - case_table_start) * @typeInfo(Zir.Inst.As).@"struct".fields.len); - - const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.SwitchBlockErrUnion{ - .operand = raw_operand, - .bits = Zir.Inst.SwitchBlockErrUnion.Bits{ - .has_multi_cases = multi_cases_len != 0, - .has_else = has_else, - .scalar_cases_len = @intCast(scalar_cases_len), - .any_uses_err_capture = any_uses_err_capture, - .payload_is_ref = payload_is_ref, - }, - .main_src_node_offset = parent_gz.nodeIndexToRelative(catch_or_if_node), - }); - - if (multi_cases_len != 0) { - astgen.extra.appendAssumeCapacity(multi_cases_len); - } - - if (any_uses_err_capture) { - astgen.extra.appendAssumeCapacity(@intFromEnum(err_inst)); - } - - const zir_datas = astgen.instructions.items(.data); - zir_datas[@intFromEnum(switch_block)].pl_node.payload_index = payload_index; - - for (payloads.items[case_table_start..case_table_end], 0..) |start_index, i| { - var body_len_index = start_index; - var end_index = start_index; - const table_index = case_table_start + i; - if (table_index < scalar_case_table) { - end_index += 1; - } else if (table_index < multi_case_table) { - body_len_index += 1; - end_index += 2; - } else { - body_len_index += 2; - const items_len = payloads.items[start_index]; - const ranges_len = payloads.items[start_index + 1]; - end_index += 3 + items_len + 2 * ranges_len; - } - const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(payloads.items[body_len_index]); - end_index += prong_info.body_len; - astgen.extra.appendSliceAssumeCapacity(payloads.items[start_index..end_index]); - } - - if (need_result_rvalue) { - return rvalue(parent_gz, ri, switch_block.toRef(), switch_node); - } else { - return switch_block.toRef(); - } -} - -fn switchExpr( - parent_gz: *GenZir, - scope: *Scope, - ri: ResultInfo, - node: Ast.Node.Index, - switch_full: Ast.full.Switch, -) InnerError!Zir.Inst.Ref { - const astgen = parent_gz.astgen; - const gpa = astgen.gpa; - const tree = astgen.tree; - const operand_node = switch_full.ast.condition; - const case_nodes = switch_full.ast.cases; - - const need_rl = astgen.nodes_need_rl.contains(node); - const block_ri: ResultInfo = if (need_rl) ri else .{ - .rl = switch (ri.rl) { - .ptr => .{ .ty = (try ri.rl.resultType(parent_gz, node)).? }, - .inferred_ptr => .none, - else => ri.rl, - }, - .ctx = ri.ctx, - }; - // We need to call `rvalue` to write through to the pointer only if we had a - // result pointer and aren't forwarding it. - const LocTag = @typeInfo(ResultInfo.Loc).@"union".tag_type.?; - const need_result_rvalue = @as(LocTag, block_ri.rl) != @as(LocTag, ri.rl); - - if (switch_full.label_token) |label_token| { - try astgen.checkLabelRedefinition(scope, label_token); - } + } else undefined; // We perform two passes over the AST. This first pass is to collect information - // for the following variables, make note of the special prong AST node index, - // and bail out with a compile error if there are multiple special prongs present. + // for the following variables, make note of the special prong AST node indices, + // and bail out with a compile error if there are incompatible special prongs present. var any_payload_is_ref = false; + var any_has_payload_capture = false; var any_has_tag_capture = false; - var any_non_inline_capture = false; + var any_maybe_runtime_capture = false; var scalar_cases_len: u32 = 0; var multi_cases_len: u32 = 0; - var inline_cases_len: u32 = 0; + var total_items_len: usize = 0; + var total_ranges_len: usize = 0; var else_case_node: Ast.Node.OptionalIndex = .none; var else_src: ?Ast.TokenIndex = null; - var underscore_case_node: Ast.Node.OptionalIndex = .none; + var under_case_node: Ast.Node.OptionalIndex = .none; var underscore_node: Ast.Node.OptionalIndex = .none; var underscore_src: ?Ast.TokenIndex = null; - var underscore_additional_items: Zir.SpecialProngs.AdditionalItems = .none; + var under_is_bare = false; for (case_nodes) |case_node| { const case = tree.fullSwitchCase(case_node).?; if (case.payload_token) |payload_token| { const ident = if (tree.tokenTag(payload_token) == .asterisk) blk: { + // Capturing errors by reference is never allowed, but as we will + // check for this again later we will fail as late as possible. any_payload_is_ref = true; break :blk payload_token + 1; } else payload_token; + + if (!mem.eql(u8, tree.tokenSlice(ident), "_")) { + any_has_payload_capture = true; + + // If we're capturing a union, its payload value cannot always be + // comptime-known, even if its prong is inlined as inlining only + // affects its enum tag. + // This check isn't perfect, because for things like enums, the + // entire capture *is* comptime-known for inline prongs! But such + // knowledge requires semantic analysis. + any_maybe_runtime_capture = true; + } if (tree.tokenTag(ident + 1) == .comma) { any_has_tag_capture = true; - } - // If the first capture is ignored, then there is no runtime-known - // capture, as the tag capture must be for an inline prong. - // This check isn't perfect, because for things like enums, the - // first prong *is* comptime-known for inline prongs! But such - // knowledge requires semantic analysis. - if (!mem.eql(u8, tree.tokenSlice(ident), "_")) { - any_non_inline_capture = true; + if (case.inline_token == null) { + any_maybe_runtime_capture = true; + } } } @@ -7690,13 +7312,7 @@ fn switchExpr( case_src, "multiple else prongs in switch expression", .{}, - &[_]u32{ - try astgen.errNoteTok( - src, - "previous else prong here", - .{}, - ), - }, + &.{try astgen.errNoteTok(src, "previous else prong here", .{})}, ); } else_case_node = case_node.toOptional(); @@ -7704,156 +7320,492 @@ fn switchExpr( continue; } - // Check for '_' prong. + // Check for '_' prong and ranges. var case_has_underscore = false; + var case_has_ranges = false; for (case.ast.values) |val| { switch (tree.nodeTag(val)) { - .identifier => if (mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(val)), "_")) { - const val_src = tree.nodeMainToken(val); - if (underscore_src) |src| { - return astgen.failTokNotes( - val_src, - "multiple '_' prongs in switch expression", - .{}, - &[_]u32{ - try astgen.errNoteTok( - src, - "previous '_' prong here", - .{}, - ), - }, - ); - } - if (case.inline_token != null) { - return astgen.failTok(val_src, "cannot inline '_' prong", .{}); - } - underscore_case_node = case_node.toOptional(); - underscore_src = val_src; - underscore_node = val.toOptional(); - underscore_additional_items = switch (case.ast.values.len) { - 0 => unreachable, - 1 => .none, - 2 => .one, - else => .many, - }; - case_has_underscore = true; + .switch_range => { + total_ranges_len += 1; + case_has_ranges = true; }, .string_literal => return astgen.failNode(val, "cannot switch on strings", .{}), - else => {}, + else => |tag| { + if (tag == .identifier and + mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(val)), "_")) + { + const val_src = tree.nodeMainToken(val); + if (is_err_switch) { + const case_src = case.ast.arrow_token - 1; + return astgen.failTokNotes( + case_src, + "'_' prong is not allowed when switching on errors", + .{}, + &.{ + try astgen.errNoteTok( + case_src, + "consider using 'else'", + .{}, + ), + }, + ); + } + if (underscore_src) |src| { + return astgen.failTokNotes( + val_src, + "multiple '_' prongs in switch expression", + .{}, + &.{try astgen.errNoteTok(src, "previous '_' prong here", .{})}, + ); + } + if (case.inline_token != null) { + return astgen.failTok(val_src, "cannot inline '_' prong", .{}); + } + under_case_node = case_node.toOptional(); + underscore_src = val_src; + underscore_node = val.toOptional(); + under_is_bare = case.ast.values.len == 1; + case_has_underscore = true; + } else { + total_items_len += 1; + } + }, } } - if (case_has_underscore) continue; - if (case.ast.values.len == 1 and tree.nodeTag(case.ast.values[0]) != .switch_range) { + const case_len = case.ast.values.len - @intFromBool(case_has_underscore); + if (case_len == 1 and !case_has_ranges) { scalar_cases_len += 1; - } else { + } else if (case_len >= 1) { multi_cases_len += 1; } - if (case.inline_token != null) { - inline_cases_len += 1; - } } - const special_prongs: Zir.SpecialProngs = .init( - else_src != null, - underscore_src != null, - underscore_additional_items, - ); - const has_else = special_prongs.hasElse(); - const has_under = special_prongs.hasUnder(); - - const operand_ri: ResultInfo = .{ .rl = if (any_payload_is_ref) .ref else .none }; - - astgen.advanceSourceCursorToNode(operand_node); - const operand_lc: LineColumn = .{ astgen.source_line - parent_gz.decl_line, astgen.source_column }; - - const raw_operand = try expr(parent_gz, scope, operand_ri, operand_node); - const item_ri: ResultInfo = .{ .rl = .none }; - - // If this switch is labeled, it may have `continue`s targeting it, and thus we need the operand type - // to provide a result type. - const raw_operand_ty_ref = if (switch_full.label_token != null) t: { - break :t try parent_gz.addUnNode(.typeof, raw_operand, operand_node); - } else undefined; - - // This contains the data that goes into the `extra` array for the SwitchBlock/SwitchBlockMulti, - // except the first cases_nodes.len slots are a table that indexes payloads later in the array, with - // the special case index coming first, then scalar_case_len indexes, then multi_cases_len indexes + const has_else = else_src != null; + const has_under = underscore_src != null; + if (under_is_bare) assert(has_under); // make sure that the former implies the latter + if (is_err_switch) assert(!has_under); // should have failed by now + const any_ranges = total_ranges_len > 0; + + // This contains all of the body lengths (already in the correct order) and + // the bodies they belong to that go into the `extra` array later, except the + // first item_table_end slots are a table that indexes the item bodies (and + // also indirectly the prong bodies, as they are always trailing after their + // item bodies). const payloads = &astgen.scratch; const scratch_top = astgen.scratch.items.len; - const case_table_start = scratch_top; - const else_case_index = if (has_else) case_table_start else undefined; - const under_case_index = if (has_under) case_table_start + @intFromBool(has_else) else undefined; - const scalar_case_table = case_table_start + @intFromBool(has_else) + @intFromBool(has_under); - const multi_case_table = scalar_case_table + scalar_cases_len; - const case_table_end = multi_case_table + multi_cases_len; - try astgen.scratch.resize(gpa, case_table_end); + var payloads_end = scratch_top; + + // Since range item body pairs are always contiguous we don't technically + // have to keep track of the position of the second body. However handling + // all of the several indices and offsets is complicated enough as it is, + // so for the sake of keeping this function a little bit more simple we do + // it anyway. + + const scalar_body_table = payloads_end; + payloads_end += scalar_cases_len; + const multi_item_body_table = payloads_end; + payloads_end += total_items_len + 2 * total_ranges_len - scalar_cases_len; + const multi_prong_body_table = payloads_end; + payloads_end += multi_cases_len; + const body_table_end = payloads_end; + + const scalar_prong_infos_start = payloads_end; + payloads_end += scalar_cases_len; + const multi_prong_infos_start = payloads_end; + payloads_end += multi_cases_len; + const multi_case_items_lens_start = payloads_end; + payloads_end += multi_cases_len; + const multi_case_ranges_lens_start = if (any_ranges) blk: { + const multi_case_ranges_lens_start = payloads_end; + payloads_end += multi_cases_len; + break :blk multi_case_ranges_lens_start; + } else undefined; + const scalar_item_infos_start = payloads_end; + payloads_end += scalar_cases_len; + const multi_items_infos_start = payloads_end; + payloads_end += total_items_len - scalar_cases_len + 2 * total_ranges_len; + const bodies_start = payloads_end; + + try payloads.resize(gpa, bodies_start); defer astgen.scratch.items.len = scratch_top; + var non_err_prong_body_start: u32 = undefined; + var else_prong_body_start: u32 = undefined; + var bare_under_prong_body_start: u32 = undefined; + var non_err_info: Zir.Inst.SwitchBlock.ProngInfo.NonErr = undefined; + var else_info: Zir.Inst.SwitchBlock.ProngInfo.Else = undefined; + var under_extra: u32 = undefined; + var block_scope = parent_gz.makeSubBlock(scope); // block_scope not used for collecting instructions block_scope.instructions_top = GenZir.unstacked_top; - block_scope.setBreakResultInfo(block_ri); - // Sema expects a dbg_stmt immediately before switch_block(_ref) + const operand_ri: ResultInfo = .{ + .rl = if (any_payload_is_ref or + (needs_non_err_handling and non_err_is_ref)) .ref else .none, + .ctx = if (do_err_trace) .error_handling_expr else .none, + }; + + astgen.advanceSourceCursorToNode(operand_node); + const operand_lc: LineColumn = .{ astgen.source_line - parent_gz.decl_line, astgen.source_column }; + + const raw_operand: Zir.Inst.Ref = if (needs_non_err_handling) + try reachableExpr(parent_gz, scope, operand_ri, operand_node, switch_node) + else + try expr(parent_gz, scope, operand_ri, operand_node); + + // Sema expects a dbg_stmt immediately before any kind of switch_block inst. try emitDbgStmtForceCurrentIndex(parent_gz, operand_lc); // This gets added to the parent block later, after the item expressions. - const switch_tag: Zir.Inst.Tag = if (any_payload_is_ref) .switch_block_ref else .switch_block; - const switch_block = try parent_gz.makeBlockInst(switch_tag, node); + const switch_tag: Zir.Inst.Tag = switch (non_err) { + .none, .peer_break_target => if (any_payload_is_ref) .switch_block_ref else .switch_block, + .@"if", .@"catch" => .switch_block_err_union, + }; + const switch_block = try parent_gz.makeBlockInst(switch_tag, switch_node); + + // Set `break` target if applicable; `continue` target may differ! + switch (non_err) { + .none => { + if (switch_full.label_token != null) { + block_scope.break_target = switch_block; + } + block_scope.setBreakResultInfo(block_ri); + }, + .@"catch", .@"if" => { + assert(switch_full.label_token == null); // use `peer_break_target` code path instead! + block_scope.setBreakResultInfo(block_ri); + }, + .peer_break_target => |peer_break_target| { + + // Special case; we have an error switch + label situation and we + // want to generate this: + // ``` + // %1 = block({ + // %2 = is_non_err(%operand) + // %3 = condbr(%2, { + // %4 = err_union_payload_unsafe(%operand) + // %5 = break(%1, result) // targets enclosing `block` + // }, { + // %6 = err_union_code(%operand) + // %7 = switch_block(%6, + // { ... } => { + // %8 = break(%1, result) // targets enclosing `block` + // }, + // { ... } => { + // %9 = switch_continue(%7, result) // targets `switch_block` + // }, + // ) + // %10 = break(%1, @void_value) + // }) + // }) + // ``` + // to ensure that the non-err case and the switch are only peers when + // breaking from either, but not when continuing the switch. We use + // this lowering to avoiding a rather complex special case in Sema. + + assert(switch_full.label_token != null); // use `switch_block_err_union` code path instead! + assert(.block == astgen.instructions.items(.tag)[@intFromEnum(peer_break_target.block_inst)]); + block_scope.break_target = peer_break_target.block_inst; + block_scope.setBreakResultInfo(peer_break_target.block_ri); + }, + } + + // We need a bunch of separate locations to store several capture values: + // `... |err| switch (err) { else => |e| { ... } }` // `err` and `e` + // `... => |payload, tag| { ... }` // `payload` and `tag` + // and result types: + // `foo => { ... }` // `foo` needs a result type + // `... => continue :sw val` // `val` needs a result type + // Some observations: + // - If we just use the switch inst itself we don't need a placeholder! + // - We can always tell for sure whether a capture exists. We also know + // that its existence implies that it has to be used. + // - We can't know whether there are any `continue`s before analyzing all + // prong bodies. At that point we already need a result location. We do + // know whether there even *could* be any though by looking for a label. + // - Sema wants a result location in `zirSwitchContinue`. If that's the + // switch inst itself, there's no need to look at the switch inst data. + // Some conclusions: + // - We should use the switch inst as the continue result location if needed. + // - If we need more insts for captures and our switch inst is already used + // for something else, we start creating placeholder insts. + + // Prong items use the switch block instruction as their result type. + // No other components of the switch statement are in scope while they are + // being resolved, so this is never a problem. + const item_ri: ResultInfo = .{ .rl = .{ .coerced_ty = switch_block.toRef() } }; + + var switch_block_inst_is_occupied: bool = false; if (switch_full.label_token) |label_token| { block_scope.label = .{ .token = label_token }; - block_scope.break_target = switch_block; block_scope.continue_target = .{ .switch_continue = switch_block }; block_scope.continue_result_info = .{ .rl = if (any_payload_is_ref) - .{ .ref_coerced_ty = raw_operand_ty_ref } + .{ .ref_coerced_ty = switch_block.toRef() } else - .{ .coerced_ty = raw_operand_ty_ref }, + .{ .coerced_ty = switch_block.toRef() }, }; + switch_block_inst_is_occupied = true; - // `break_result_info` already set by `setBreakResultInfo` above. + // `break_target` and `break_result_info` already set above. } + if (needs_non_err_handling) { + // `switch_block_err_union` uses the switch block inst as its err capture/ + // switch operand. This is always ok as its switch can never have a label. + assert(!switch_block_inst_is_occupied); + switch_block_inst_is_occupied = true; + } + // `... => |payload| { ... }` + const payload_capture_inst, const payload_capture_inst_is_placeholder = inst: { + if (!any_has_payload_capture) break :inst .{ undefined, false }; + if (!switch_block_inst_is_occupied) { + switch_block_inst_is_occupied = true; + break :inst .{ switch_block, false }; + } + break :inst .{ try astgen.appendPlaceholder(), true }; + }; + // `... => |_, tag| { ... }` + const tag_capture_inst, const tag_capture_inst_is_placeholder = inst: { + if (!any_has_tag_capture) break :inst .{ undefined, false }; + if (!switch_block_inst_is_occupied) { + switch_block_inst_is_occupied = true; + break :inst .{ switch_block, false }; + } + break :inst .{ try astgen.appendPlaceholder(), true }; + }; + + var prong_body_extra_insts_buf: [3]Zir.Inst.Index = undefined; + const prong_body_extra_insts: []const Zir.Inst.Index = extra_insts: { + var extra_insts: std.ArrayList(Zir.Inst.Index) = .initBuffer(&prong_body_extra_insts_buf); + if (switch_block_inst_is_occupied) extra_insts.appendAssumeCapacity(switch_block); + if (payload_capture_inst_is_placeholder) extra_insts.appendAssumeCapacity(payload_capture_inst); + if (tag_capture_inst_is_placeholder) extra_insts.appendAssumeCapacity(tag_capture_inst); + break :extra_insts extra_insts.items; + }; + + const switch_operand, const catch_or_if_operand = if (needs_non_err_handling) + .{ switch_block.toRef(), raw_operand } + else + .{ raw_operand, undefined }; + + // We re-use this same scope for all case items and contents. + var scratch_scope = parent_gz.makeSubBlock(&block_scope.base); + scratch_scope.instructions_top = GenZir.unstacked_top; + + // We have to take care of the non-error body first if there is one. + non_err_body: { + if (!needs_non_err_handling) break :non_err_body; + + scratch_scope.instructions_top = parent_gz.instructions.items.len; + defer scratch_scope.unstack(); - // We re-use this same scope for all cases, including the special prong, if any. - var case_scope = parent_gz.makeSubBlock(&block_scope.base); - case_scope.instructions_top = GenZir.unstacked_top; - - // If any prong has an inline tag capture, allocate a shared dummy instruction for it - const tag_inst = if (any_has_tag_capture) tag_inst: { - const inst: Zir.Inst.Index = @enumFromInt(astgen.instructions.len); - try astgen.instructions.append(astgen.gpa, .{ - .tag = .extended, - .data = .{ .extended = .{ - .opcode = .value_placeholder, - .small = undefined, - .operand = undefined, - } }, - }); - break :tag_inst inst; - } else undefined; + // It's always ok to use the switch block inst to refer to the error union + // payload as the actual switch statement isn't even in scope yet. + const non_err_payload_inst = switch_block; + var non_err_capture: Zir.Inst.SwitchBlock.ProngInfo.Capture = .none; + + switch (non_err) { + .none, .peer_break_target => unreachable, + .@"catch" => { + // We always effectively capture the error union payload; we use + // it to `break` from the entire `switch_block_err_union`. + non_err_capture = if (non_err_is_ref) .by_ref else .by_val; + + const then_result = switch (ri.rl) { + .ref, .ref_coerced_ty => non_err_payload_inst.toRef(), + else => try rvalue( + &scratch_scope, + block_scope.break_result_info, + non_err_payload_inst.toRef(), + catch_or_if_node, + ), + }; + _ = try scratch_scope.addBreakWithSrcNode( + .@"break", + switch_block, + then_result, + catch_or_if_node, + ); + }, + .@"if" => |if_full| { + var payload_val_scope: Scope.LocalVal = undefined; + + const then_node = if_full.ast.then_expr; + const then_sub_scope: *Scope = scope: { + if (if_full.payload_token) |payload_token| { + const ident_token = payload_token + @intFromBool(non_err_is_ref); + const ident_name = try astgen.identAsString(ident_token); + const ident_name_str = tree.tokenSlice(ident_token); + if (mem.eql(u8, "_", ident_name_str)) { + break :scope &scratch_scope.base; + } + non_err_capture = if (non_err_is_ref) .by_ref else .by_val; + try astgen.detectLocalShadowing(&scratch_scope.base, ident_name, ident_token, ident_name_str, .capture); + payload_val_scope = .{ + .parent = &scratch_scope.base, + .gen_zir = &scratch_scope, + .name = ident_name, + .inst = non_err_payload_inst.toRef(), + .token_src = ident_token, + .id_cat = .capture, + }; + try scratch_scope.addDbgVar(.dbg_var_val, ident_name, non_err_payload_inst.toRef()); + break :scope &payload_val_scope.base; + } else { + _ = try scratch_scope.addUnNode( + .ensure_err_union_payload_void, + catch_or_if_operand, + catch_or_if_node, + ); + break :scope &scratch_scope.base; + } + }; + const then_result = try fullBodyExpr(&scratch_scope, then_sub_scope, block_scope.break_result_info, then_node, .allow_branch_hint); + try checkUsed(parent_gz, &scratch_scope.base, then_sub_scope); + if (!scratch_scope.endsWithNoReturn()) { + _ = try scratch_scope.addBreakWithSrcNode(.@"break", switch_block, then_result, then_node); + } + }, + } + const body_slice = scratch_scope.instructionsSlice(); + const body_start: u32 = @intCast(payloads.items.len); + const body_len = astgen.countBodyLenAfterFixupsExtraRefs(body_slice, &.{non_err_payload_inst}); + try payloads.ensureUnusedCapacity(gpa, body_len); + astgen.appendBodyWithFixupsExtraRefsArrayList(payloads, body_slice, &.{non_err_payload_inst}); + + non_err_prong_body_start = body_start; + non_err_info = .{ + .body_len = @intCast(body_len), + .capture = non_err_capture, + .operand_is_ref = non_err_is_ref, + }; + } // In this pass we generate all the item and prong expressions. var multi_case_index: u32 = 0; var scalar_case_index: u32 = 0; + var multi_item_offset: usize = 0; for (case_nodes) |case_node| { const case = tree.fullSwitchCase(case_node).?; - const is_multi_case = case.ast.values.len > 1 or - (case.ast.values.len == 1 and tree.nodeTag(case.ast.values[0]) == .switch_range); + const case_has_under = case_node.toOptional() == under_case_node; + const ranges_len: u32 = if (any_ranges) blk: { + var ranges_len: u32 = 0; + for (case.ast.values) |value| { + ranges_len += @intFromBool(tree.nodeTag(value) == .switch_range); + } + break :blk ranges_len; + } else 0; + const items_len: u32 = @intCast(case.ast.values.len - ranges_len - @intFromBool(case_has_under)); + const is_multi_case = items_len > 1 or ranges_len > 0; - var dbg_var_name: Zir.NullTerminatedString = .empty; - var dbg_var_inst: Zir.Inst.Ref = undefined; + // item/range bodies in order of occurence + var item_i: usize = 0; + var range_i: usize = 0; + for (case.ast.values) |value| { + if (value.toOptional() == underscore_node) continue; + const is_range = tree.nodeTag(value) == .switch_range; + const range: [2]Ast.Node.Index = if (is_range) tree.nodeData(value).node_and_node else undefined; + const nodes: []const Ast.Node.Index = if (is_range) &range else &.{value}; + for (nodes) |item| { + // We lower enum literals, error values and number literals + // manually to save space since they are very commonly used as + // switch case items. + const body_start: u32 = @intCast(payloads.items.len); + const item_info: Zir.Inst.SwitchBlock.ItemInfo = blk: switch (tree.nodeTag(item)) { + .enum_literal => { + const str_index = try astgen.identAsString(tree.nodeMainToken(item)); + break :blk .wrap(.{ .enum_literal = str_index }); + }, + .error_value => { + const ident_token = tree.nodeMainToken(item) + 2; // skip 'error', '.' + const str_index = try astgen.identAsString(ident_token); + break :blk .wrap(.{ .error_value = str_index }); + }, + .number_literal => { + // We don't actually need a final result type for number + // literals, they can just be turned into `comptime_int` + // or `comptime_float` as usual and then be coerced to + // the correct type later during semantic analysis. + assert(scratch_scope.instructions_top == GenZir.unstacked_top); // important! we emit into `parent_gz` which `scratch_scope` is stacked on top of + const zir_ref = try comptimeExpr(parent_gz, scope, .{ .rl = .none }, item, .switch_item); + break :blk .wrap(.{ .number_literal = zir_ref }); + }, + else => { + scratch_scope.instructions_top = parent_gz.instructions.items.len; + defer scratch_scope.unstack(); + const item_result = try fullBodyExpr(&scratch_scope, scope, item_ri, item, .normal); + if (!scratch_scope.endsWithNoReturn()) { + _ = try scratch_scope.addBreakWithSrcNode(.break_inline, switch_block, item_result, item); + } + const item_slice = scratch_scope.instructionsSlice(); + const body_len = astgen.countBodyLenAfterFixupsExtraRefs(item_slice, &.{switch_block}); + try payloads.ensureUnusedCapacity(gpa, body_len); + astgen.appendBodyWithFixupsExtraRefsArrayList(payloads, item_slice, &.{switch_block}); + break :blk .wrap(.{ .body_len = body_len }); + }, + }; + if (is_multi_case) { + if (is_range) { + const offset = multi_item_offset + items_len + range_i; + payloads.items[multi_item_body_table + offset] = body_start; + payloads.items[multi_items_infos_start + offset] = @bitCast(item_info); + range_i += 1; + } else { + const offset = multi_item_offset + item_i; + payloads.items[multi_item_body_table + offset] = body_start; + payloads.items[multi_items_infos_start + offset] = @bitCast(item_info); + item_i += 1; + } + } else { + payloads.items[scalar_body_table + scalar_case_index] = body_start; + payloads.items[scalar_item_infos_start + scalar_case_index] = @bitCast(item_info); + } + } + } + if (is_multi_case) { + assert(item_i == items_len and range_i == 2 * ranges_len); + payloads.items[multi_case_items_lens_start + multi_case_index] = items_len; + if (any_ranges) { + payloads.items[multi_case_ranges_lens_start + multi_case_index] = ranges_len; + } + multi_item_offset += items_len + 2 * ranges_len; + } + + // Capture and prong body + + var dbg_var_payload_name: Zir.NullTerminatedString = .empty; + var dbg_var_payload_inst: Zir.Inst.Ref = undefined; var dbg_var_tag_name: Zir.NullTerminatedString = .empty; var dbg_var_tag_inst: Zir.Inst.Ref = undefined; var has_tag_capture = false; - var capture_val_scope: Scope.LocalVal = undefined; - var tag_scope: Scope.LocalVal = undefined; + var err_capture_scope: Scope.LocalVal = undefined; + var payload_capture_scope: Scope.LocalVal = undefined; + var tag_capture_scope: Scope.LocalVal = undefined; var capture: Zir.Inst.SwitchBlock.ProngInfo.Capture = .none; - const sub_scope = blk: { - const payload_token = case.payload_token orelse break :blk &case_scope.base; + // Check all captures and make them available to the prong body. + // Potential captures are: + // - for regular switch: payload and tag + // - for error switch: switch operand and payload + const prong_body_scope: *Scope = scope: { + const switch_scope: *Scope = if (needs_non_err_handling) blk: { + // We want to have the captured error we're switching on in scope! + err_capture_scope = .{ + .parent = &scratch_scope.base, + .gen_zir = &scratch_scope, + .name = err_capture_name, + .inst = switch_operand, + .token_src = err_token, + .id_cat = .capture, + }; + break :blk &err_capture_scope.base; + } else &scratch_scope.base; + + const payload_token = case.payload_token orelse break :scope switch_scope; const capture_is_ref = tree.tokenTag(payload_token) == .asterisk; const ident = payload_token + @intFromBool(capture_is_ref); @@ -7867,36 +7819,38 @@ fn switchExpr( return astgen.failTok(payload_token, "pointer modifier invalid on discard", .{}); } capture = .none; - payload_sub_scope = &case_scope.base; + payload_sub_scope = switch_scope; } else { const capture_name = try astgen.identAsString(ident); - try astgen.detectLocalShadowing(&case_scope.base, capture_name, ident, ident_slice, .capture); - capture_val_scope = .{ - .parent = &case_scope.base, - .gen_zir = &case_scope, + try astgen.detectLocalShadowing(&scratch_scope.base, capture_name, ident, ident_slice, .capture); + payload_capture_scope = .{ + .parent = switch_scope, + .gen_zir = &scratch_scope, .name = capture_name, - .inst = switch_block.toRef(), + .inst = payload_capture_inst.toRef(), .token_src = ident, .id_cat = .capture, }; - dbg_var_name = capture_name; - dbg_var_inst = switch_block.toRef(); - payload_sub_scope = &capture_val_scope.base; + dbg_var_payload_name = payload_capture_scope.name; + dbg_var_payload_inst = payload_capture_scope.inst; + payload_sub_scope = &payload_capture_scope.base; } - const tag_token = if (tree.tokenTag(ident + 1) == .comma) - ident + 2 - else if (capture == .none) { - // discarding the capture is only valid iff the tag is captured + if (is_err_switch and capture == .by_ref) { + return astgen.failTok(ident, "error set cannot be captured by reference", .{}); + } + + const tag_token = if (tree.tokenTag(ident + 1) == .comma) blk: { + break :blk ident + 2; + } else if (capture == .none) { + // discarding the capture is only valid if the tag is captured // whether the tag capture is discarded is handled below return astgen.failTok(payload_token, "discard of capture; omit it instead", .{}); - } else break :blk payload_sub_scope; + } else break :scope payload_sub_scope; const tag_slice = tree.tokenSlice(tag_token); if (mem.eql(u8, tag_slice, "_")) { return astgen.failTok(tag_token, "discard of tag capture; omit it instead", .{}); - } else if (case.inline_token == null) { - return astgen.failTok(tag_token, "tag capture on non-inline prong", .{}); } const tag_name = try astgen.identAsString(tag_token); try astgen.detectLocalShadowing(payload_sub_scope, tag_name, tag_token, tag_slice, .@"switch tag capture"); @@ -7904,123 +7858,155 @@ fn switchExpr( assert(any_has_tag_capture); has_tag_capture = true; - tag_scope = .{ + if (is_err_switch) { + return astgen.failTok(tag_token, "cannot capture tag of error union", .{}); + } + + tag_capture_scope = .{ .parent = payload_sub_scope, - .gen_zir = &case_scope, + .gen_zir = &scratch_scope, .name = tag_name, - .inst = tag_inst.toRef(), + .inst = tag_capture_inst.toRef(), .token_src = tag_token, .id_cat = .@"switch tag capture", }; - dbg_var_tag_name = tag_name; - dbg_var_tag_inst = tag_inst.toRef(); - break :blk &tag_scope.base; + dbg_var_tag_name = tag_capture_scope.name; + dbg_var_tag_inst = tag_capture_scope.inst; + break :scope &tag_capture_scope.base; }; - const header_index: u32 = @intCast(payloads.items.len); - const body_len_index = if (is_multi_case) blk: { - if (case_node.toOptional() == underscore_case_node) { - payloads.items[under_case_index] = header_index; - if (special_prongs.hasOneAdditionalItem()) { - try payloads.resize(gpa, header_index + 2); // item, body_len - const maybe_item_node = case.ast.values[0]; - const item_node = if (maybe_item_node.toOptional() == underscore_node) - case.ast.values[1] - else - maybe_item_node; - const item_inst = try comptimeExpr(parent_gz, scope, item_ri, item_node, .switch_item); - payloads.items[header_index] = @intFromEnum(item_inst); - break :blk header_index + 1; - } - } else { - payloads.items[multi_case_table + multi_case_index] = header_index; - multi_case_index += 1; - } - try payloads.resize(gpa, header_index + 3); // items_len, ranges_len, body_len + if (capture != .none) assert(any_has_payload_capture); + if (is_err_switch) { + assert(!any_payload_is_ref); // should have failed by now + assert(!any_has_tag_capture); // should have failed by now + } - // items - var items_len: u32 = 0; - for (case.ast.values) |item_node| { - if (item_node.toOptional() == underscore_node or - tree.nodeTag(item_node) == .switch_range) - { - continue; - } - items_len += 1; + prong_body: { + scratch_scope.instructions_top = parent_gz.instructions.items.len; + defer scratch_scope.unstack(); - const item_inst = try comptimeExpr(parent_gz, scope, item_ri, item_node, .switch_item); - try payloads.append(gpa, @intFromEnum(item_inst)); - } - - // ranges - var ranges_len: u32 = 0; - for (case.ast.values) |range| { - if (tree.nodeTag(range) != .switch_range) { - continue; - } - ranges_len += 1; - - const first_node, const last_node = tree.nodeData(range).node_and_node; - const first = try comptimeExpr(parent_gz, scope, item_ri, first_node, .switch_item); - const last = try comptimeExpr(parent_gz, scope, item_ri, last_node, .switch_item); - try payloads.appendSlice(gpa, &[_]u32{ - @intFromEnum(first), @intFromEnum(last), - }); - } - - payloads.items[header_index] = items_len; - payloads.items[header_index + 1] = ranges_len; - break :blk header_index + 2; - } else if (case_node.toOptional() == else_case_node) blk: { - payloads.items[else_case_index] = header_index; - try payloads.resize(gpa, header_index + 1); // body_len - break :blk header_index; - } else if (case_node.toOptional() == underscore_case_node) blk: { - assert(!special_prongs.hasAdditionalItems()); - payloads.items[under_case_index] = header_index; - try payloads.resize(gpa, header_index + 1); // body_len - break :blk header_index; - } else blk: { - payloads.items[scalar_case_table + scalar_case_index] = header_index; - scalar_case_index += 1; - try payloads.resize(gpa, header_index + 2); // item, body_len - const item_node = case.ast.values[0]; - const item_inst = try comptimeExpr(parent_gz, scope, item_ri, item_node, .switch_item); - payloads.items[header_index] = @intFromEnum(item_inst); - break :blk header_index + 1; - }; - - { - // temporarily stack case_scope on parent_gz - case_scope.instructions_top = parent_gz.instructions.items.len; - defer case_scope.unstack(); - - if (dbg_var_name != .empty) { - try case_scope.addDbgVar(.dbg_var_val, dbg_var_name, dbg_var_inst); + if (dbg_var_payload_name != .empty) { + try scratch_scope.addDbgVar(.dbg_var_val, dbg_var_payload_name, dbg_var_payload_inst); } if (dbg_var_tag_name != .empty) { - try case_scope.addDbgVar(.dbg_var_val, dbg_var_tag_name, dbg_var_tag_inst); + try scratch_scope.addDbgVar(.dbg_var_val, dbg_var_tag_name, dbg_var_tag_inst); + } + if (do_err_trace and nodeMayAppendToErrorTrace(tree, operand_node)) { + _ = try scratch_scope.addSaveErrRetIndex(.always); } const target_expr_node = case.ast.target_expr; - const case_result = try fullBodyExpr(&case_scope, sub_scope, block_scope.break_result_info, target_expr_node, .allow_branch_hint); - try checkUsed(parent_gz, &case_scope.base, sub_scope); - if (!parent_gz.refIsNoReturn(case_result)) { - _ = try case_scope.addBreakWithSrcNode(.@"break", switch_block, case_result, target_expr_node); + const case_result = try fullBodyExpr(&scratch_scope, prong_body_scope, block_scope.break_result_info, target_expr_node, .allow_branch_hint); + if (needs_non_err_handling) { + // If we would check `scratch_scope` here, we would get a false + // positive, that being the switch operand itself! + try checkUsed(parent_gz, &err_capture_scope.base, prong_body_scope); + } else { + try checkUsed(parent_gz, &scratch_scope.base, prong_body_scope); + } + if (!scratch_scope.endsWithNoReturn()) { + // As our last action before the break, "pop" the error trace if needed + if (do_err_trace) { + try restoreErrRetIndex( + &scratch_scope, + .{ .block = switch_block }, + block_scope.break_result_info, + target_expr_node, + case_result, + ); + } + _ = try scratch_scope.addBreakWithSrcNode(.@"break", switch_block, case_result, target_expr_node); } - const case_slice = case_scope.instructionsSlice(); - const extra_insts: []const Zir.Inst.Index = if (has_tag_capture) &.{ switch_block, tag_inst } else &.{switch_block}; - const body_len = astgen.countBodyLenAfterFixupsExtraRefs(case_slice, extra_insts); + const body_slice = scratch_scope.instructionsSlice(); + const body_start: u32 = @intCast(payloads.items.len); + const body_len = astgen.countBodyLenAfterFixupsExtraRefs(body_slice, prong_body_extra_insts); try payloads.ensureUnusedCapacity(gpa, body_len); - payloads.items[body_len_index] = @bitCast(Zir.Inst.SwitchBlock.ProngInfo{ + astgen.appendBodyWithFixupsExtraRefsArrayList(payloads, body_slice, prong_body_extra_insts); + + if (case_node.toOptional() == else_case_node) { + assert(case.ast.values.len == 0); + + // Specific `else` bodies can cause Sema to omit the + // "unreachable else prong" error so that certain generic code + // patterns don't trigger it. We do that for these bodies: + // `else => unreachable,` + // `else => return,` + // `else => |e| return e,` (where `e` is any identifier) + const is_simple_noreturn = switch (tree.nodeTag(target_expr_node)) { + .unreachable_literal => true, // `=> unreachable,` + .@"return" => simple_noreturn: { + const retval_node = tree.nodeData(target_expr_node).opt_node.unwrap() orelse { + break :simple_noreturn true; // `=> return,` + }; + // Check for `=> |e| return e,` + if (capture != .by_val) break :simple_noreturn false; + if (tree.nodeTag(retval_node) != .identifier) break :simple_noreturn false; + const payload_name = try astgen.identAsString(case.payload_token.?); + const retval_name = try astgen.identAsString(tree.nodeMainToken(retval_node)); + break :simple_noreturn payload_name == retval_name; + }, + else => false, + }; + + else_info = .{ + .body_len = @intCast(body_len), + .capture = capture, + .is_inline = case.inline_token != null, + .has_tag_capture = has_tag_capture, + .is_simple_noreturn = is_simple_noreturn, + }; + else_prong_body_start = body_start; + break :prong_body; + } + + if (case_has_under) { + // We're either writing under_prong_info or under_index here. + if (under_is_bare) { + assert(case.ast.values.len == 1); // only `_` + const bare_under_info: Zir.Inst.SwitchBlock.ProngInfo.BareUnder = .{ + .body_len = @intCast(body_len), + .capture = capture, + .has_tag_capture = has_tag_capture, + }; + under_extra = @bitCast(bare_under_info); + bare_under_prong_body_start = body_start; + break :prong_body; + } else if (is_multi_case) { + under_extra = scalar_cases_len + multi_case_index; + } else { + under_extra = scalar_case_index; + } + } + + // We allow prongs with error items which are not inside the error set + // being switched on if their body is `=> comptime unreachable,`. + const is_comptime_unreach = comptime_unreach: { + if (tree.nodeTag(target_expr_node) != .@"comptime") break :comptime_unreach false; + const comptime_node = tree.nodeData(target_expr_node).node; + break :comptime_unreach tree.nodeTag(comptime_node) == .unreachable_literal; + }; + + const prong_info: Zir.Inst.SwitchBlock.ProngInfo = .{ .body_len = @intCast(body_len), .capture = capture, .is_inline = case.inline_token != null, .has_tag_capture = has_tag_capture, - }); - appendBodyWithFixupsExtraRefsArrayList(astgen, payloads, case_slice, extra_insts); + .is_comptime_unreach = is_comptime_unreach, + }; + + if (is_multi_case) { + payloads.items[multi_prong_body_table + multi_case_index] = body_start; + payloads.items[multi_prong_infos_start + multi_case_index] = @bitCast(prong_info); + multi_case_index += 1; + } else { + // prong body start is implicit, it's right behind our only item. + payloads.items[scalar_prong_infos_start + scalar_case_index] = @bitCast(prong_info); + scalar_case_index += 1; + } } } + assert(scalar_case_index + multi_case_index + @intFromBool(has_else) + @intFromBool(under_is_bare) == case_nodes.len); + assert(multi_items_infos_start + multi_item_offset == bodies_start); if (switch_full.label_token) |label_token| if (!block_scope.label.?.used) { try astgen.appendErrorTok(label_token, "unused switch label", .{}); @@ -8029,84 +8015,108 @@ fn switchExpr( // Now that the item expressions are generated we can add this. try parent_gz.instructions.append(gpa, switch_block); + // We've collected all of the data we need! Now we just have to finalize it + // by copying our bodies from `payloads` to `extra`, this time in the order + // expected by ZIR consumers. + try astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.SwitchBlock).@"struct".fields.len + - @intFromBool(multi_cases_len != 0) + - @intFromBool(any_has_tag_capture) + - payloads.items.len - scratch_top); + @intFromBool(multi_cases_len > 0) + // multi_cases_len + @intFromBool(payload_capture_inst_is_placeholder) + // payload_capture_placeholder + @intFromBool(tag_capture_inst_is_placeholder) + // tag_capture_placeholder + @intFromBool(needs_non_err_handling) + // catch_or_if_src_node_offset + @intFromBool(needs_non_err_handling) + // non_err_info + @intFromBool(has_else) + // else_info + @intFromBool(has_under) + // under_prong_info or under_index + payloads.items.len - body_table_end); // item infos and bodies - const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.SwitchBlock{ - .operand = raw_operand, - .bits = Zir.Inst.SwitchBlock.Bits{ - .has_multi_cases = multi_cases_len != 0, - .special_prongs = special_prongs, - .any_has_tag_capture = any_has_tag_capture, - .any_non_inline_capture = any_non_inline_capture, + // singular pieces of data + const zir_payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.SwitchBlock{ + .raw_operand = raw_operand, + .bits = .{ + .has_multi_cases = multi_cases_len > 0, + .any_ranges = any_ranges, + .has_else = has_else, + .has_under = has_under, + .under_is_bare = under_is_bare, .has_continue = switch_full.label_token != null and block_scope.label.?.used_for_continue, + .any_maybe_runtime_capture = any_maybe_runtime_capture, + .payload_capture_inst_is_placeholder = payload_capture_inst_is_placeholder, + .tag_capture_inst_is_placeholder = tag_capture_inst_is_placeholder, .scalar_cases_len = @intCast(scalar_cases_len), }, }); + astgen.instructions.items(.data)[@intFromEnum(switch_block)].pl_node.payload_index = zir_payload_index; - if (multi_cases_len != 0) { - astgen.extra.appendAssumeCapacity(multi_cases_len); + if (multi_cases_len > 0) astgen.extra.appendAssumeCapacity(multi_cases_len); + if (payload_capture_inst_is_placeholder) astgen.extra.appendAssumeCapacity(@intFromEnum(payload_capture_inst)); + if (tag_capture_inst_is_placeholder) astgen.extra.appendAssumeCapacity(@intFromEnum(tag_capture_inst)); + if (needs_non_err_handling) { + const catch_or_if_src_node_offset = parent_gz.nodeIndexToRelative(catch_or_if_node); + astgen.extra.appendAssumeCapacity(@bitCast(@intFromEnum(catch_or_if_src_node_offset))); + astgen.extra.appendAssumeCapacity(@bitCast(non_err_info)); } + if (has_else) astgen.extra.appendAssumeCapacity(@bitCast(else_info)); + if (has_under) astgen.extra.appendAssumeCapacity(under_extra); - if (any_has_tag_capture) { - astgen.extra.appendAssumeCapacity(@intFromEnum(tag_inst)); - } + const extra_payloads_start = astgen.extra.items.len; - const zir_datas = astgen.instructions.items(.data); - zir_datas[@intFromEnum(switch_block)].pl_node.payload_index = payload_index; + // body lens + astgen.extra.appendSliceAssumeCapacity(payloads.items[body_table_end..bodies_start]); + // bodies + if (needs_non_err_handling) { + const body = payloads.items[non_err_prong_body_start..][0..non_err_info.body_len]; + astgen.extra.appendSliceAssumeCapacity(body); + } if (has_else) { - const start_index = payloads.items[else_case_index]; - var end_index = start_index + 1; - const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(payloads.items[start_index]); - end_index += prong_info.body_len; - astgen.extra.appendSliceAssumeCapacity(payloads.items[start_index..end_index]); + const body = payloads.items[else_prong_body_start..][0..else_info.body_len]; + astgen.extra.appendSliceAssumeCapacity(body); } - if (has_under) { - const start_index = payloads.items[under_case_index]; - var body_len_index = start_index; - var end_index = start_index; - switch (underscore_additional_items) { - .none => { - end_index += 1; - }, - .one => { - body_len_index += 1; - end_index += 2; - }, - .many => { - body_len_index += 2; - const items_len = payloads.items[start_index]; - const ranges_len = payloads.items[start_index + 1]; - end_index += 3 + items_len + 2 * ranges_len; - }, - } - const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(payloads.items[body_len_index]); - end_index += prong_info.body_len; - astgen.extra.appendSliceAssumeCapacity(payloads.items[start_index..end_index]); + if (under_is_bare) { + const under_prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(under_extra); + const body = payloads.items[bare_under_prong_body_start..][0..under_prong_info.body_len]; + astgen.extra.appendSliceAssumeCapacity(body); + } + for (0..scalar_cases_len) |scalar_i| { + const item_info: Zir.Inst.SwitchBlock.ItemInfo = @bitCast(payloads.items[scalar_item_infos_start + scalar_i]); + const item_body_start = payloads.items[scalar_body_table + scalar_i]; + const item_body = payloads.items[item_body_start..][0 .. item_info.bodyLen() orelse 0]; + const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(payloads.items[scalar_prong_infos_start + scalar_i]); + const prong_body_start = item_body_start + item_body.len; + const prong_body = payloads.items[prong_body_start..][0..prong_info.body_len]; + astgen.extra.appendSliceAssumeCapacity(prong_body); + astgen.extra.appendSliceAssumeCapacity(item_body); } - for (payloads.items[scalar_case_table..case_table_end], 0..) |start_index, i| { - var body_len_index = start_index; - var end_index = start_index; - const table_index = scalar_case_table + i; - if (table_index < multi_case_table) { - body_len_index += 1; - end_index += 2; - } else { - body_len_index += 2; - const items_len = payloads.items[start_index]; - const ranges_len = payloads.items[start_index + 1]; - end_index += 3 + items_len + 2 * ranges_len; + var multi_item_i: usize = 0; + for (0..multi_cases_len) |multi_i| { + const prong_body_start = payloads.items[multi_prong_body_table + multi_i]; + const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(payloads.items[multi_prong_infos_start + multi_i]); + const prong_body = payloads.items[prong_body_start..][0..prong_info.body_len]; + astgen.extra.appendSliceAssumeCapacity(prong_body); + + const items_len = payloads.items[multi_case_items_lens_start + multi_i]; + const ranges_len = if (any_ranges) ranges_len: { + break :ranges_len payloads.items[multi_case_ranges_lens_start + multi_i]; + } else 0; + // The table entries and body lens are already in the correct order so we + // don't have to differentiate between items and ranges here. + for (0..items_len + 2 * ranges_len) |_| { + const item_info: Zir.Inst.SwitchBlock.ItemInfo = @bitCast(payloads.items[multi_items_infos_start + multi_item_i]); + if (item_info.bodyLen()) |body_len| { + const body_start = payloads.items[multi_item_body_table + multi_item_i]; + const body = payloads.items[body_start..][0..body_len]; + astgen.extra.appendSliceAssumeCapacity(body); + } + multi_item_i += 1; } - const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(payloads.items[body_len_index]); - end_index += prong_info.body_len; - astgen.extra.appendSliceAssumeCapacity(payloads.items[start_index..end_index]); } + // Make sure we didn't forget anything... + assert(multi_item_i == total_items_len + 2 * total_ranges_len - scalar_cases_len); + assert(astgen.extra.items.len - extra_payloads_start == payloads.items.len - body_table_end); + if (need_result_rvalue) { - return rvalue(parent_gz, ri, switch_block.toRef(), node); + return rvalue(parent_gz, ri, switch_block.toRef(), switch_node); } else { return switch_block.toRef(); } @@ -13786,6 +13796,19 @@ fn scanContainer( return error.AnalysisFail; } +fn appendPlaceholder(astgen: *AstGen) Allocator.Error!Zir.Inst.Index { + const inst: Zir.Inst.Index = @enumFromInt(astgen.instructions.len); + try astgen.instructions.append(astgen.gpa, .{ + .tag = .extended, + .data = .{ .extended = .{ + .opcode = .value_placeholder, + .small = undefined, + .operand = undefined, + } }, + }); + return inst; +} + /// Assumes capacity for body has already been added. Needed capacity taking into /// account fixups can be found with `countBodyLenAfterFixups`. fn appendBodyWithFixups(astgen: *AstGen, body: []const Zir.Inst.Index) void { diff --git a/lib/std/zig/Zir.zig b/lib/std/zig/Zir.zig index 37ce7b4cfa120b17901a0600810ac27acdf6a406..092886ffd1fc7312cc18e94a33b52749a0207d5d 100644 --- a/lib/std/zig/Zir.zig +++ b/lib/std/zig/Zir.zig @@ -95,7 +95,6 @@ pub fn extraData(code: Zir, comptime T: type, index: usize) ExtraData(T) { Inst.Call.Flags, Inst.BuiltinCall.Flags, Inst.SwitchBlock.Bits, - Inst.SwitchBlockErrUnion.Bits, Inst.FuncFancy.Bits, Inst.Declaration.Flags, Inst.Param.Type, @@ -350,7 +349,8 @@ pub const Inst = struct { /// Uses the `break` union field. break_inline, /// Branch from within a switch case to the case specified by the operand. - /// Uses the `break` union field. `block_inst` refers to a `switch_block` or `switch_block_ref`. + /// Uses the `break` union field. `block_inst` refers to a `switch_block`/ + /// `switch_block_ref`/`switch_block_err_union`. switch_continue, /// Checks that comptime control flow does not happen inside a runtime block. /// Uses the `un_node` union field. @@ -722,8 +722,10 @@ pub const Inst = struct { /// A switch expression. Uses the `pl_node` union field. /// AST node is the switch, payload is `SwitchBlock`. Operand is a pointer. switch_block_ref, - /// A switch on an error union `a catch |err| switch (err) {...}`. - /// Uses the `pl_node` union field. AST node is the `catch`, payload is `SwitchBlockErrUnion`. + /// A switch on an error union: + /// - `eu catch |err| switch (err) {...}`, AST node is the `catch`. + /// - `if (eu) |payload| {...} else |err| {...}`, AST node is the `if`. + /// Uses the `pl_node` union field. Payload is `SwitchBlock`. switch_block_err_union, /// Check that operand type supports the dereference operand (.*). /// Uses the `un_node` field. @@ -3293,143 +3295,168 @@ pub const Inst = struct { }; /// Trailing: - /// 0. multi_cases_len: u32 // if `has_multi_cases` - /// 1. err_capture_inst: u32 // if `any_uses_err_capture` - /// 2. non_err_body { - /// info: ProngInfo, - /// inst: Index // for every `info.body_len` - /// } - /// 3. else_body { // if `has_else` - /// info: ProngInfo, - /// inst: Index // for every `info.body_len` - /// } - /// 4. scalar_cases: { // for every `scalar_cases_len` - /// item: Ref, - /// info: ProngInfo, - /// inst: Index // for every `info.body_len` - /// } - /// 5. multi_cases: { // for every `multi_cases_len` - /// items_len: u32, - /// ranges_len: u32, - /// info: ProngInfo, - /// item: Ref // for every `items_len` - /// ranges: { // for every `ranges_len` - /// item_first: Ref, - /// item_last: Ref, + /// 0. multi_cases_len: u32, // If has_multi_cases is set. + /// 1. payload_capture_placeholder: Inst.Index, // If payload_capture_inst_is_placeholder is set. + /// // Index of instruction prongs use to refer to their payload capture. + /// 2. tag_capture_placeholder: Inst.Index, // If tag_capture_inst_is_placeholder is set. + /// // Index of instruction prongs use to refer to their tag capture. + /// 3. catch_or_if_src_node_offset: Ast.Node.Offset, // If inst is switch_block_err_union. + /// 4. non_err_info: ProngInfo.NonErr, // If inst is switch_block_err_union. + /// 5. else_info: ProngInfo.Else, // If has_else is set. + /// 6. under_info: ProngInfo.Under, // If has_under is set and + /// // under_is_bare is set. + /// 7. under_index: u32, // If has_under is set and + /// // under_is_bare is *not* set. + /// // Index into switch cases. + /// 8. scalar_prong_info: ProngInfo, // for every scalar_cases_len + /// 9. multi_prong_info: ProngInfo, // for every multi_cases_len + /// 10. multi_case_items_len: u32, // for every multi_cases_len + /// 11. multi_case_ranges_len: u32, // If has_ranges is set: for every multi_cases_len + /// 12. scalar_item_info: ItemInfo, // for every scalar_cases_len + /// 13. multi_items_info: { // for every multi_cases_len + /// item_info: ItemInfo, // for each multi_case_items_len + /// range_items_info: { // for each multi_case_ranges_len + /// first_info: ItemInfo, + /// last_info: ItemInfo, /// } - /// inst: Index // for every `info.body_len` /// } - /// - /// When analyzing a case body, the switch instruction itself refers to the - /// captured error, or to the success value in `non_err_body`. Whether this - /// is captured by reference or by value depends on whether the `byref` bit - /// is set for the corresponding body. `err_capture_inst` refers to the error - /// capture outside of the `switch`, i.e. `err` in - /// `x catch |err| switch (err) { ... }`. - pub const SwitchBlockErrUnion = struct { - operand: Ref, - bits: Bits, - main_src_node_offset: Ast.Node.Offset, - - pub const Bits = packed struct(u32) { - /// If true, one or more prongs have multiple items. - has_multi_cases: bool, - /// If true, there is an else prong. This is mutually exclusive with `has_under`. - has_else: bool, - any_uses_err_capture: bool, - payload_is_ref: bool, - scalar_cases_len: ScalarCasesLen, - - pub const ScalarCasesLen = u28; - }; - - pub const MultiProng = struct { - items: []const Ref, - body: []const Index, - }; - }; - - /// 0. multi_cases_len: u32 // If has_multi_cases is set. - /// 1. tag_capture_inst: u32 // If any_has_tag_capture is set. Index of instruction prongs use to refer to the inline tag capture. - /// 2. else_body { // If special_prong.hasElse() is set. - /// info: ProngInfo, - /// body member Index for every info.body_len + /// 14. non_err_body { + /// body_inst: Index // for every non_err_info.body_len /// } - /// 3. under_body { // If special_prong.hasUnder() is set. - /// item: Ref, // If special_prong.hasOneAdditionalItem() is set. - /// items_len: u32, // If special_prong.hasManyAdditionalItems() is set. - /// ranges_len: u32, // If special_prong.hasManyAdditionalItems() is set. - /// info: ProngInfo, - /// item: Ref, // for every items_len - /// ranges: { // for every ranges_len - /// item_first: Ref, - /// item_last: Ref, + /// 15. else_body: { // If has_else is set. + /// body_inst: Inst.Index, // for every else_info.body_len + /// } + /// 16. under_body: { // If has_under is set and + /// // under_is_bare is set. + /// body_inst: Inst.Index, // for every under_info.body_len + /// } + /// 17. scalar_bodies: { // for every scalar_cases_len + /// prong_body: { // for each body_len in scalar_prong_info + /// body_inst: Inst.Index, // for every body_len /// } - /// body member Index for every info.body_len - /// } - /// 4. scalar_cases: { // for every scalar_cases_len - /// item: Ref, - /// info: ProngInfo, - /// body member Index for every info.body_len - /// } - /// 5. multi_cases: { // for every multi_cases_len - /// items_len: u32, - /// ranges_len: u32, - /// info: ProngInfo, - /// item: Ref, // for every items_len - /// ranges: { // for every ranges_len - /// item_first: Ref, - /// item_last: Ref, + /// item_body: { // for each body_len in scalar_item_info + /// body_inst: Inst.Index, // for every body_len + /// } + /// } + /// 18. multi_bodies: { // for each multi_items_info + /// prong_body: { + /// body_inst: Inst.Index, // for each multi_prong_info.body_len + /// } + /// item_body: { // for each item_info + /// body_inst: Inst.Index, // for every item_info.body_len + /// } + /// range_bodies: { // for each .{first_info, last_info} in range_items_info + /// first_body_inst: Inst.Index, // for every first_info.body_len + /// last_body_inst: Inst.Index, // for every last_info.body_len /// } - /// body member Index for every info.body_len /// } - /// - /// When analyzing a case body, the switch instruction itself refers to the - /// captured payload. Whether this is captured by reference or by value - /// depends on whether the `byref` bit is set for the corresponding body. pub const SwitchBlock = struct { - /// The operand passed to the `switch` expression. If this is a - /// `switch_block`, this is the operand value; if `switch_block_ref` it - /// is a pointer to the operand. `switch_block_ref` is always used if - /// any prong has a byref capture. - operand: Ref, + /// Either `catch`/`if` or `switch` operand. + raw_operand: Ref, bits: Bits, - /// These are stored in trailing data in `extra` for each prong. + pub const Bits = packed struct(u32) { + /// If true, one or more prongs have multiple items. + has_multi_cases: bool, + /// If true, one or more prongs have ranges. + /// Only valid if `has_multi_cases` is also set. + any_ranges: bool, + has_else: bool, + has_under: bool, + /// Only valid if `has_under` is also set. + under_is_bare: bool, + /// If true, at least one prong contains a `continue`. + /// Only valid if `has_label` is set. + has_continue: bool, + // If true, at least one prong has a non-inline payload/tag capture. + any_maybe_runtime_capture: bool, + payload_capture_inst_is_placeholder: bool, + tag_capture_inst_is_placeholder: bool, + scalar_cases_len: ScalarCasesLen, + + // NOTE maybe don't steal any more bits from poor `scalar_cases_len` + // and split `Bits` into two parts instead, `raw_operand` surely + // wouldn't mind donating a couple of bits for that purpose... + pub const ScalarCasesLen = u23; + }; + pub const ProngInfo = packed struct(u32) { - body_len: u28, + body_len: u27, capture: ProngInfo.Capture, is_inline: bool, has_tag_capture: bool, + is_comptime_unreach: bool, pub const Capture = enum(u2) { none, by_val, by_ref, }; - }; - pub const Bits = packed struct(u32) { - /// If true, one or more prongs have multiple items. - has_multi_cases: bool, - /// Information about the special prong. - special_prongs: SpecialProngs, - /// If true, at least one prong has an inline tag capture. - any_has_tag_capture: bool, - /// If true, at least one prong has a capture which may not - /// be comptime-known via `inline`. - any_non_inline_capture: bool, - /// If true, at least one prong contains a `continue`. - has_continue: bool, - scalar_cases_len: ScalarCasesLen, - - pub const ScalarCasesLen = u25; + pub const NonErr = packed struct(u32) { + body_len: u29, + capture: ProngInfo.Capture, + operand_is_ref: bool, + }; + + pub const Else = packed struct(u32) { + body_len: u27, + capture: ProngInfo.Capture, + is_inline: bool, + has_tag_capture: bool, + is_simple_noreturn: bool, + }; + + pub const BareUnder = packed struct(u32) { + body_len: u29, + capture: ProngInfo.Capture, + has_tag_capture: bool, + }; }; - pub const MultiProng = struct { - items: []const Ref, - body: []const Index, + pub const ItemInfo = packed struct(u32) { + kind: ItemInfo.Kind, + data: u30, + + pub const Kind = enum(u2) { + enum_literal, + error_value, + number_literal, + body_len, + }; + + pub const Unwrapped = union(ItemInfo.Kind) { + enum_literal: Zir.NullTerminatedString, + error_value: Zir.NullTerminatedString, + number_literal: Inst.Ref, + body_len: u32, + }; + + pub fn wrap(unwrapped: ItemInfo.Unwrapped) ItemInfo { + const data_uncasted: u32 = switch (unwrapped) { + .enum_literal => |str_index| @intFromEnum(str_index), + .error_value => |str_index| @intFromEnum(str_index), + .number_literal => |zir_ref| @intFromEnum(zir_ref), + .body_len => |body_len| body_len, + }; + return .{ .kind = unwrapped, .data = @intCast(data_uncasted) }; + } + + pub fn unwrap(item_info: ItemInfo) ItemInfo.Unwrapped { + return switch (item_info.kind) { + .enum_literal => .{ .enum_literal = @enumFromInt(item_info.data) }, + .error_value => .{ .error_value = @enumFromInt(item_info.data) }, + .number_literal => .{ .number_literal = @enumFromInt(item_info.data) }, + .body_len => .{ .body_len = item_info.data }, + }; + } + + pub fn bodyLen(item_info: ItemInfo) ?u32 { + return if (item_info.kind == .body_len) item_info.data else null; + } }; + + pub const Kind = enum { default, ref, err_union }; }; pub const ArrayInitRefTy = struct { @@ -4004,69 +4031,6 @@ pub const Inst = struct { }; }; -pub const SpecialProngs = enum(u3) { - none = 0b000, - /// Simple `else` prong. - /// `else => {},` - @"else" = 0b001, - /// Simple `_` prong. - /// `_ => {},` - under = 0b010, - /// Both an `else` and a `_` prong. - /// `else => {},` - /// `_ => {},` - under_and_else = 0b011, - /// `_` prong with 1 additional item. - /// `a, _ => {},` - under_one_item = 0b100, - /// Both an `else` and a `_` prong with 1 additional item. - /// `else => {},` - /// `a, _ => {},` - under_one_item_and_else = 0b101, - /// `_` prong with >1 additional items. - /// `a, _, b => {},` - under_many_items = 0b110, - /// Both an `else` and a `_` prong with >1 additional items. - /// `else => {},` - /// `a, _, b => {},` - under_many_items_and_else = 0b111, - - pub const AdditionalItems = enum(u3) { - none = @intFromEnum(SpecialProngs.under), - one = @intFromEnum(SpecialProngs.under_one_item), - many = @intFromEnum(SpecialProngs.under_many_items), - }; - - pub fn init(has_else: bool, has_under: bool, additional_items: AdditionalItems) SpecialProngs { - const else_bit: u3 = @intFromBool(has_else); - const under_bits: u3 = if (has_under) - @intFromEnum(additional_items) - else - @intFromEnum(SpecialProngs.none); - return @enumFromInt(else_bit | under_bits); - } - - pub fn hasElse(special_prongs: SpecialProngs) bool { - return (@intFromEnum(special_prongs) & 0b001) != 0; - } - - pub fn hasUnder(special_prongs: SpecialProngs) bool { - return (@intFromEnum(special_prongs) & 0b110) != 0; - } - - pub fn hasAdditionalItems(special_prongs: SpecialProngs) bool { - return (@intFromEnum(special_prongs) & 0b100) != 0; - } - - pub fn hasOneAdditionalItem(special_prongs: SpecialProngs) bool { - return (@intFromEnum(special_prongs) & 0b110) == @intFromEnum(SpecialProngs.under_one_item); - } - - pub fn hasManyAdditionalItems(special_prongs: SpecialProngs) bool { - return (@intFromEnum(special_prongs) & 0b110) == @intFromEnum(SpecialProngs.under_many_items); - } -}; - pub const DeclIterator = struct { extra_index: u32, decls_remaining: u32, @@ -4842,8 +4806,48 @@ fn findTrackableInner( const body = zir.bodySlice(extra.end, extra.data.body_len); try zir.findTrackableBody(gpa, contents, defers, body); }, - .switch_block, .switch_block_ref => return zir.findTrackableSwitch(gpa, contents, defers, inst, .normal), - .switch_block_err_union => return zir.findTrackableSwitch(gpa, contents, defers, inst, .err_union), + + .switch_block, + .switch_block_ref, + .switch_block_err_union, + => { + const zir_switch = zir.getSwitchBlock(inst); + if (zir_switch.non_err_case) |non_err_case| { + try zir.findTrackableBody(gpa, contents, defers, non_err_case.body); + } + if (zir_switch.else_case) |else_case| { + try zir.findTrackableBody(gpa, contents, defers, else_case.body); + } + if (zir_switch.under_case.resolve()) |under_case| { + try zir.findTrackableBody(gpa, contents, defers, under_case.body); + } + var extra_index = zir_switch.end; + var case_it = zir_switch.iterateCases(); + while (case_it.next()) |case| { + const prong_body = zir.bodySlice(extra_index, case.prong_info.body_len); + extra_index += prong_body.len; + try zir.findTrackableBody(gpa, contents, defers, prong_body); + for (case.item_infos) |item_info| { + if (item_info.bodyLen()) |body_len| { + const item_body = zir.bodySlice(extra_index, body_len); + extra_index += item_body.len; + try zir.findTrackableBody(gpa, contents, defers, item_body); + } + } + for (case.range_infos) |range_info| { + if (range_info[0].bodyLen()) |body_len| { + const first_body = zir.bodySlice(extra_index, body_len); + extra_index += first_body.len; + try zir.findTrackableBody(gpa, contents, defers, first_body); + } + if (range_info[1].bodyLen()) |body_len| { + const last_body = zir.bodySlice(extra_index, body_len); + extra_index += last_body.len; + try zir.findTrackableBody(gpa, contents, defers, last_body); + } + } + } + }, .suspend_block => @panic("TODO iterate suspend block"), @@ -4890,119 +4894,6 @@ fn findTrackableInner( } } -fn findTrackableSwitch( - zir: Zir, - gpa: Allocator, - contents: *DeclContents, - defers: *std.AutoHashMapUnmanaged(u32, void), - inst: Inst.Index, - /// Distinguishes between `switch_block[_ref]` and `switch_block_err_union`. - comptime kind: enum { normal, err_union }, -) Allocator.Error!void { - const inst_data = zir.instructions.items(.data)[@intFromEnum(inst)].pl_node; - const extra = zir.extraData(switch (kind) { - .normal => Inst.SwitchBlock, - .err_union => Inst.SwitchBlockErrUnion, - }, inst_data.payload_index); - - var extra_index: usize = extra.end; - - const multi_cases_len = if (extra.data.bits.has_multi_cases) blk: { - const multi_cases_len = zir.extra[extra_index]; - extra_index += 1; - break :blk multi_cases_len; - } else 0; - - if (switch (kind) { - .normal => extra.data.bits.any_has_tag_capture, - .err_union => extra.data.bits.any_uses_err_capture, - }) { - extra_index += 1; - } - - const has_special = switch (kind) { - .normal => extra.data.bits.special_prongs != .none, - .err_union => has_special: { - // Handle `non_err_body` first. - const prong_info: Inst.SwitchBlock.ProngInfo = @bitCast(zir.extra[extra_index]); - extra_index += 1; - const body = zir.bodySlice(extra_index, prong_info.body_len); - extra_index += body.len; - - try zir.findTrackableBody(gpa, contents, defers, body); - - break :has_special extra.data.bits.has_else; - }, - }; - - if (has_special) { - const has_else = if (kind == .normal) - extra.data.bits.special_prongs.hasElse() - else - true; - if (has_else) { - const prong_info: Inst.SwitchBlock.ProngInfo = @bitCast(zir.extra[extra_index]); - extra_index += 1; - const body = zir.bodySlice(extra_index, prong_info.body_len); - extra_index += body.len; - - try zir.findTrackableBody(gpa, contents, defers, body); - } - if (kind == .normal) { - const special_prongs = extra.data.bits.special_prongs; - - if (special_prongs.hasUnder()) { - var trailing_items_len: u32 = 0; - if (special_prongs.hasOneAdditionalItem()) { - extra_index += 1; - } else if (special_prongs.hasManyAdditionalItems()) { - const items_len = zir.extra[extra_index]; - extra_index += 1; - const ranges_len = zir.extra[extra_index]; - extra_index += 1; - trailing_items_len = items_len + ranges_len * 2; - } - const prong_info: Inst.SwitchBlock.ProngInfo = @bitCast(zir.extra[extra_index]); - extra_index += 1 + trailing_items_len; - const body = zir.bodySlice(extra_index, prong_info.body_len); - extra_index += body.len; - - try zir.findTrackableBody(gpa, contents, defers, body); - } - } - } - - { - const scalar_cases_len = extra.data.bits.scalar_cases_len; - for (0..scalar_cases_len) |_| { - extra_index += 1; - const prong_info: Inst.SwitchBlock.ProngInfo = @bitCast(zir.extra[extra_index]); - extra_index += 1; - const body = zir.bodySlice(extra_index, prong_info.body_len); - extra_index += body.len; - - try zir.findTrackableBody(gpa, contents, defers, body); - } - } - { - for (0..multi_cases_len) |_| { - const items_len = zir.extra[extra_index]; - extra_index += 1; - const ranges_len = zir.extra[extra_index]; - extra_index += 1; - const prong_info: Inst.SwitchBlock.ProngInfo = @bitCast(zir.extra[extra_index]); - extra_index += 1; - - extra_index += items_len + ranges_len * 2; - - const body = zir.bodySlice(extra_index, prong_info.body_len); - extra_index += body.len; - - try zir.findTrackableBody(gpa, contents, defers, body); - } - } -} - fn findTrackableBody( zir: Zir, gpa: Allocator, @@ -5337,6 +5228,306 @@ pub fn getAssociatedSrcHash(zir: Zir, inst: Zir.Inst.Index) ?std.zig.SrcHash { } } +pub fn getSwitchBlock(zir: *const Zir, switch_inst: Inst.Index) UnwrappedSwitchBlock { + const has_non_err = switch (zir.instructions.items(.tag)[@intFromEnum(switch_inst)]) { + .switch_block, .switch_block_ref => false, + .switch_block_err_union => true, + else => unreachable, + }; + const inst_data = zir.instructions.items(.data)[@intFromEnum(switch_inst)].pl_node; + const extra = zir.extraData(Inst.SwitchBlock, inst_data.payload_index); + const bits = extra.data.bits; + var extra_index = extra.end; + const multi_cases_len = if (bits.has_multi_cases) len: { + const multi_cases_len = zir.extra[extra_index]; + extra_index += 1; + break :len multi_cases_len; + } else 0; + const payload_capture_placeholder: Inst.OptionalIndex = if (bits.payload_capture_inst_is_placeholder) inst: { + const inst: Inst.Index = @enumFromInt(zir.extra[extra_index]); + extra_index += 1; + break :inst inst.toOptional(); + } else .none; + const tag_capture_placeholder: Inst.OptionalIndex = if (bits.tag_capture_inst_is_placeholder) inst: { + const inst: Inst.Index = @enumFromInt(zir.extra[extra_index]); + extra_index += 1; + break :inst inst.toOptional(); + } else .none; + const catch_or_if_src_node_offset: Ast.Node.OptionalOffset = if (has_non_err) node_offset: { + const node_offset: Ast.Node.Offset = @enumFromInt(@as(i32, @bitCast(zir.extra[extra_index]))); + extra_index += 1; + break :node_offset node_offset.toOptional(); + } else .none; + const non_err_info: Inst.SwitchBlock.ProngInfo.NonErr = if (has_non_err) non_err_info: { + const non_err_info: Inst.SwitchBlock.ProngInfo.NonErr = @bitCast(zir.extra[extra_index]); + extra_index += 1; + break :non_err_info non_err_info; + } else undefined; + const else_info: Inst.SwitchBlock.ProngInfo.Else = if (bits.has_else) else_info: { + const else_info: Inst.SwitchBlock.ProngInfo.Else = @bitCast(zir.extra[extra_index]); + extra_index += 1; + break :else_info else_info; + } else undefined; + const bare_under_info: Inst.SwitchBlock.ProngInfo.BareUnder = if (bits.has_under and bits.under_is_bare) bare_under_info: { + const bare_under_info: Inst.SwitchBlock.ProngInfo.BareUnder = @bitCast(zir.extra[extra_index]); + extra_index += 1; + break :bare_under_info bare_under_info; + } else undefined; + const under_index: u32 = if (bits.has_under and !bits.under_is_bare) under_index: { + const under_index = zir.extra[extra_index]; + extra_index += 1; + break :under_index under_index; + } else undefined; + const scalar_cases_len: u32 = bits.scalar_cases_len; + const prong_infos: []const Inst.SwitchBlock.ProngInfo = + @ptrCast(zir.extra[extra_index..][0 .. scalar_cases_len + multi_cases_len]); + extra_index += prong_infos.len; + const multi_case_items_lens = zir.extra[extra_index..][0..multi_cases_len]; + extra_index += multi_case_items_lens.len; + const multi_case_ranges_lens: ?[]const u32 = if (bits.any_ranges) lens: { + const multi_case_ranges_lens = zir.extra[extra_index..][0..multi_cases_len]; + extra_index += multi_case_ranges_lens.len; + break :lens multi_case_ranges_lens; + } else null; + var total_items_len: usize = scalar_cases_len; + for (multi_case_items_lens) |items_len| { + total_items_len += items_len; + } + if (multi_case_ranges_lens) |ranges_lens| for (ranges_lens) |ranges_len| { + total_items_len += 2 * ranges_len; + }; + const item_infos: []const Inst.SwitchBlock.ItemInfo = + @ptrCast(zir.extra[extra_index..][0..total_items_len]); + extra_index += item_infos.len; + const non_err_case: ?UnwrappedSwitchBlock.Case.NonErr = if (has_non_err) non_err_case: { + const body = zir.bodySlice(extra_index, non_err_info.body_len); + extra_index += body.len; + break :non_err_case .{ + .body = body, + .capture = non_err_info.capture, + .operand_is_ref = non_err_info.operand_is_ref, + }; + } else null; + const else_case: ?UnwrappedSwitchBlock.Case.Else = if (bits.has_else) else_case: { + const body = zir.bodySlice(extra_index, else_info.body_len); + extra_index += body.len; + break :else_case .{ + .index = .@"else", + .body = body, + .capture = else_info.capture, + .is_inline = else_info.is_inline, + .has_tag_capture = else_info.has_tag_capture, + .is_simple_noreturn = else_info.is_simple_noreturn, + }; + } else null; + const under_case: UnwrappedSwitchBlock.Case.Under = if (bits.has_under) under_case: { + if (bits.under_is_bare) { + const body = zir.bodySlice(extra_index, bare_under_info.body_len); + extra_index += body.len; + break :under_case .{ .bare = .{ + .index = .bare_under, + .body = body, + .capture = bare_under_info.capture, + .has_tag_capture = bare_under_info.has_tag_capture, + } }; + } else { + break :under_case .{ .index = under_index }; + } + } else .none; + return .{ + .main_operand = extra.data.raw_operand, + .switch_src_node_offset = inst_data.src_node, + .catch_or_if_src_node_offset = catch_or_if_src_node_offset, + .payload_capture_placeholder = payload_capture_placeholder, + .tag_capture_placeholder = tag_capture_placeholder, + .has_continue = bits.has_continue, + .any_maybe_runtime_capture = bits.any_maybe_runtime_capture, + .non_err_case = non_err_case, + .else_case = else_case, + .under_case = under_case, + .prong_infos = prong_infos, + .multi_case_items_lens = multi_case_items_lens, + .multi_case_ranges_lens = multi_case_ranges_lens, + .item_infos = item_infos, + .end = extra_index, + }; +} + +/// Trailing (starting at `end`): +/// 0. case_bodies: { // for each case in Case.Iterator.next() +/// prong_body: { +/// body_inst: Inst.Index, // for every case.prong_info.body_len, +/// } +/// item_body: { // for each body_len in case.item_infos +/// body_inst: Inst.Index, // for every body_len +/// } +/// range_bodies: { // for each .{first_info, last_info} in case.range_infos +/// first_body_inst: Inst.Index, // for every first_info.body_len +/// last_body_inst: Inst.Index, // for every last_info.body_len +/// } +/// } +pub const UnwrappedSwitchBlock = struct { + /// Either `catch`/`if` or `switch` operand. + main_operand: Inst.Ref, + switch_src_node_offset: Ast.Node.Offset, + catch_or_if_src_node_offset: Ast.Node.OptionalOffset, + payload_capture_placeholder: Inst.OptionalIndex, + tag_capture_placeholder: Inst.OptionalIndex, + has_continue: bool, + any_maybe_runtime_capture: bool, + non_err_case: ?Case.NonErr, + else_case: ?Case.Else, + under_case: Case.Under, + // Refer to doc comment and `iterateCases` to access everything below correctly. + prong_infos: []const Inst.SwitchBlock.ProngInfo, + multi_case_items_lens: []const u32, + multi_case_ranges_lens: ?[]const u32, + item_infos: []const Inst.SwitchBlock.ItemInfo, + end: usize, + + pub fn anyRanges(unwrapped: *const UnwrappedSwitchBlock) bool { + return unwrapped.multi_case_ranges_lens != null; + } + + pub fn scalarCasesLen(unwrapped: *const UnwrappedSwitchBlock) u32 { + return @intCast(unwrapped.prong_infos.len - unwrapped.multi_case_items_lens.len); + } + + pub fn multiCasesLen(unwrapped: *const UnwrappedSwitchBlock) u32 { + return @intCast(unwrapped.multi_case_items_lens.len); + } + + pub fn totalItemsLen(unwrapped: *const UnwrappedSwitchBlock) u32 { + var total_items_len: u32 = @intCast(unwrapped.item_infos.len); + if (unwrapped.multi_case_ranges_lens) |ranges_lens| { + for (ranges_lens) |len| total_items_len -= len; + } + return total_items_len; + } + + pub const Case = struct { + index: Case.Index, + prong_info: Inst.SwitchBlock.ProngInfo, + item_infos: []const Inst.SwitchBlock.ItemInfo, + range_infos: []const [2]Inst.SwitchBlock.ItemInfo, + + pub fn isUnder(case: *const Case) bool { + return case.index.is_under; + } + + pub const Index = packed struct(u32) { + kind: enum(u1) { scalar, multi }, + is_under: bool, + value: u30, + + pub const @"else": Case.Index = .{ + .kind = .scalar, + .is_under = false, + .value = std.math.maxInt(u30), + }; + + pub const bare_under: Case.Index = .{ + .kind = .scalar, + .is_under = true, + .value = std.math.maxInt(u30), + }; + }; + + pub const NonErr = struct { + body: []const Inst.Index, + capture: Inst.SwitchBlock.ProngInfo.Capture, + operand_is_ref: bool, + }; + + pub const Else = struct { + index: Case.Index, + body: []const Inst.Index, + capture: Inst.SwitchBlock.ProngInfo.Capture, + is_inline: bool, + has_tag_capture: bool, + is_simple_noreturn: bool, + }; + + pub const Under = union(enum) { + none, + bare: Under.Resolved, + index: u32, + + pub const Resolved = struct { + index: Case.Index, + body: []const Inst.Index, + capture: Inst.SwitchBlock.ProngInfo.Capture, + has_tag_capture: bool, + }; + + /// If this returns `null` and `under` is not `.none`, you'll have to + /// find the under case by iterating all cases and using `isUnder`! + pub fn resolve(under: Under) ?Under.Resolved { + return switch (under) { + .bare => |resolved| resolved, + .none, .index => null, + }; + } + }; + + pub const Iterator = struct { + next_idx: u32, + under_idx: ?u32, + prong_infos: []const Inst.SwitchBlock.ProngInfo, + multi_case_items_lens: []const u32, + multi_case_ranges_lens: ?[]const u32, + item_infos: []const Inst.SwitchBlock.ItemInfo, + + pub fn next(it: *Iterator) ?Case { + const idx = it.next_idx; + if (idx == it.prong_infos.len) return null; + it.next_idx += 1; + const scalar_cases_len = it.prong_infos.len - it.multi_case_items_lens.len; + return if (idx < scalar_cases_len) .{ + .index = .{ + .kind = .scalar, + .is_under = idx == it.under_idx, + .value = @intCast(idx), + }, + .prong_info = it.prong_infos[idx], + .item_infos = it.itemInfos(1), + .range_infos = &.{}, + } else .{ + .index = .{ + .kind = .multi, + .is_under = idx == it.under_idx, + .value = @intCast(idx - scalar_cases_len), + }, + .prong_info = it.prong_infos[idx], + .item_infos = it.itemInfos(it.multi_case_items_lens[idx - scalar_cases_len]), + .range_infos = if (it.multi_case_ranges_lens) |ranges_lens| b: { + break :b @ptrCast(it.itemInfos(2 * ranges_lens[idx - scalar_cases_len])); + } else &.{}, + }; + } + fn itemInfos(it: *Iterator, count: u32) []const Inst.SwitchBlock.ItemInfo { + const lens = it.item_infos[0..count]; + it.item_infos = it.item_infos[count..]; + return lens; + } + }; + }; + + pub fn iterateCases(unwrapped: UnwrappedSwitchBlock) Case.Iterator { + return .{ + .next_idx = 0, + .under_idx = switch (unwrapped.under_case) { + .none, .bare => null, + .index => |index| index, + }, + .prong_infos = unwrapped.prong_infos, + .multi_case_items_lens = unwrapped.multi_case_items_lens, + .multi_case_ranges_lens = unwrapped.multi_case_ranges_lens, + .item_infos = unwrapped.item_infos, + }; + } +}; + /// When the ZIR update tracking logic must be modified to consider new instructions, /// change this constant to trigger compile errors at all relevant locations. pub const inst_tracking_version = 0; diff --git a/src/print_zir.zig b/src/print_zir.zig index ced6c8e826458e86324acecc3211c6091e502b5f..0006207d6d6f6c9f371853184ef5b7fbc236768a 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -447,10 +447,9 @@ const Writer = struct { .switch_block, .switch_block_ref, + .switch_block_err_union, => try self.writeSwitchBlock(stream, inst), - .switch_block_err_union => try self.writeSwitchBlockErrUnion(stream, inst), - .field_ptr_load, .field_ptr, .decl_literal, @@ -1987,322 +1986,150 @@ const Writer = struct { try self.writeSrcNode(stream, inst_data.src_node); } - fn writeSwitchBlockErrUnion(self: *Writer, stream: *std.Io.Writer, inst: Zir.Inst.Index) !void { - const inst_data = self.code.instructions.items(.data)[@intFromEnum(inst)].pl_node; - const extra = self.code.extraData(Zir.Inst.SwitchBlockErrUnion, inst_data.payload_index); - - var extra_index: usize = extra.end; - - const multi_cases_len = if (extra.data.bits.has_multi_cases) blk: { - const multi_cases_len = self.code.extra[extra_index]; - extra_index += 1; - break :blk multi_cases_len; - } else 0; - - const err_capture_inst: Zir.Inst.Index = if (extra.data.bits.any_uses_err_capture) blk: { - const tag_capture_inst = self.code.extra[extra_index]; - extra_index += 1; - break :blk @enumFromInt(tag_capture_inst); - } else undefined; - - try self.writeInstRef(stream, extra.data.operand); - - if (extra.data.bits.any_uses_err_capture) { - try stream.writeAll(", err_capture="); - try self.writeInstIndex(stream, err_capture_inst); - } + fn writeSwitchBlock( + self: *Writer, + stream: *std.Io.Writer, + inst: Zir.Inst.Index, + ) !void { + const zir_switch = self.code.getSwitchBlock(inst); + var extra_index = zir_switch.end; + + try self.writeInstRef(stream, zir_switch.main_operand); self.indent += 2; - { - const info = @as(Zir.Inst.SwitchBlock.ProngInfo, @bitCast(self.code.extra[extra_index])); - extra_index += 1; - - assert(!info.is_inline); - const body = self.code.bodySlice(extra_index, info.body_len); - extra_index += body.len; + if (zir_switch.non_err_case) |non_err_case| { + if (non_err_case.operand_is_ref) try stream.writeAll(" ref"); try stream.writeAll(",\n"); try stream.splatByteAll(' ', self.indent); + + try self.writeSwitchCaptures(stream, non_err_case.capture, false, inst, &zir_switch); + try stream.writeAll("non_err => "); - try self.writeBracedBody(stream, body); + try self.writeBracedBody(stream, non_err_case.body); + try stream.writeAll(" "); + try self.writeSrcNode(stream, zir_switch.catch_or_if_src_node_offset.unwrap().?); } + if (zir_switch.else_case) |else_case| { + try stream.writeAll(",\n"); + try stream.splatByteAll(' ', self.indent); - if (extra.data.bits.has_else) { - const info = @as(Zir.Inst.SwitchBlock.ProngInfo, @bitCast(self.code.extra[extra_index])); - extra_index += 1; - const capture_text = switch (info.capture) { - .none => "", - .by_val => "by_val ", - .by_ref => "by_ref ", - }; - const inline_text = if (info.is_inline) "inline " else ""; - const body = self.code.bodySlice(extra_index, info.body_len); - extra_index += body.len; + try self.writeSwitchCaptures(stream, else_case.capture, else_case.has_tag_capture, inst, &zir_switch); + if (else_case.is_inline) try stream.writeAll("inline "); + try stream.writeAll("else => "); + try self.writeBracedBody(stream, else_case.body); + } + if (zir_switch.under_case.resolve()) |under_case| { try stream.writeAll(",\n"); try stream.splatByteAll(' ', self.indent); - try stream.print("{s}{s}else => ", .{ capture_text, inline_text }); - try self.writeBracedBody(stream, body); - } - { - const scalar_cases_len = extra.data.bits.scalar_cases_len; - var scalar_i: usize = 0; - while (scalar_i < scalar_cases_len) : (scalar_i += 1) { - const item_ref = @as(Zir.Inst.Ref, @enumFromInt(self.code.extra[extra_index])); - extra_index += 1; - const info = @as(Zir.Inst.SwitchBlock.ProngInfo, @bitCast(self.code.extra[extra_index])); - extra_index += 1; - const body = self.code.bodySlice(extra_index, info.body_len); - extra_index += info.body_len; + try self.writeSwitchCaptures(stream, under_case.capture, under_case.has_tag_capture, inst, &zir_switch); - try stream.writeAll(",\n"); - try stream.splatByteAll(' ', self.indent); - switch (info.capture) { - .none => {}, - .by_val => try stream.writeAll("by_val "), - .by_ref => try stream.writeAll("by_ref "), - } - if (info.is_inline) try stream.writeAll("inline "); - try self.writeInstRef(stream, item_ref); - try stream.writeAll(" => "); - try self.writeBracedBody(stream, body); - } + try stream.writeAll("_ => "); + try self.writeBracedBody(stream, under_case.body); } - { - var multi_i: usize = 0; - while (multi_i < multi_cases_len) : (multi_i += 1) { - const items_len = self.code.extra[extra_index]; - extra_index += 1; - const ranges_len = self.code.extra[extra_index]; - extra_index += 1; - const info = @as(Zir.Inst.SwitchBlock.ProngInfo, @bitCast(self.code.extra[extra_index])); - extra_index += 1; - const items = self.code.refSlice(extra_index, items_len); - extra_index += items_len; - try stream.writeAll(",\n"); - try stream.splatByteAll(' ', self.indent); - switch (info.capture) { - .none => {}, - .by_val => try stream.writeAll("by_val "), - .by_ref => try stream.writeAll("by_ref "), - } - if (info.is_inline) try stream.writeAll("inline "); + var case_it = zir_switch.iterateCases(); + while (case_it.next()) |case| { + try stream.writeAll(",\n"); + try stream.splatByteAll(' ', self.indent); + + const prong_info = case.prong_info; + try self.writeSwitchCaptures(stream, prong_info.capture, prong_info.has_tag_capture, inst, &zir_switch); + if (prong_info.is_inline) try stream.writeAll("inline "); + + const prong_body = self.code.bodySlice(extra_index, prong_info.body_len); + extra_index += prong_body.len; - for (items, 0..) |item_ref, item_i| { - if (item_i != 0) try stream.writeAll(", "); - try self.writeInstRef(stream, item_ref); + var first_item: bool = true; + if (case.isUnder()) { + try stream.writeAll("_"); + first_item = false; + } + for (case.item_infos) |item_info| { + if (!first_item) try stream.writeAll(", "); + first_item = false; + + switch (item_info.unwrap()) { + .enum_literal => |str_index| { + const str = self.code.nullTerminatedString(str_index); + try stream.print("\".{f}\"", .{std.zig.fmtString(str)}); + }, + .error_value => |str_index| { + const str = self.code.nullTerminatedString(str_index); + try stream.print("\"error.{f}\"", .{std.zig.fmtString(str)}); + }, + .number_literal => |zir_ref| { + try self.writeInstRef(stream, zir_ref); + }, + .body_len => |body_len| { + const item_body = self.code.bodySlice(extra_index, body_len); + extra_index += item_body.len; + try self.writeBracedDecl(stream, item_body); + }, } + } + for (case.range_infos) |range_info| { + if (!first_item) try stream.writeAll(", "); + first_item = false; - var range_i: usize = 0; - while (range_i < ranges_len) : (range_i += 1) { - const item_first = @as(Zir.Inst.Ref, @enumFromInt(self.code.extra[extra_index])); - extra_index += 1; - const item_last = @as(Zir.Inst.Ref, @enumFromInt(self.code.extra[extra_index])); - extra_index += 1; + var first_range_item = true; + for (&range_info) |item_info| { + if (!first_range_item) try stream.writeAll("..."); + first_range_item = false; - if (range_i != 0 or items.len != 0) { - try stream.writeAll(", "); + switch (item_info.unwrap()) { + .enum_literal => |str_index| { + const str = self.code.nullTerminatedString(str_index); + try stream.print("\".{f}\"", .{std.zig.fmtString(str)}); + }, + .error_value => |str_index| { + const str = self.code.nullTerminatedString(str_index); + try stream.print("\"error.{f}\"", .{std.zig.fmtString(str)}); + }, + .number_literal => |zir_ref| { + try self.writeInstRef(stream, zir_ref); + }, + .body_len => |body_len| { + const item_body = self.code.bodySlice(extra_index, body_len); + extra_index += item_body.len; + try self.writeBracedDecl(stream, item_body); + }, } - try self.writeInstRef(stream, item_first); - try stream.writeAll("..."); - try self.writeInstRef(stream, item_last); } - - const body = self.code.bodySlice(extra_index, info.body_len); - extra_index += info.body_len; - try stream.writeAll(" => "); - try self.writeBracedBody(stream, body); } - } - - self.indent -= 2; - - try stream.writeAll(") "); - try self.writeSrcNode(stream, inst_data.src_node); - } - - fn writeSwitchBlock(self: *Writer, stream: *std.Io.Writer, inst: Zir.Inst.Index) !void { - const inst_data = self.code.instructions.items(.data)[@intFromEnum(inst)].pl_node; - const extra = self.code.extraData(Zir.Inst.SwitchBlock, inst_data.payload_index); - - var extra_index: usize = extra.end; - - const multi_cases_len = if (extra.data.bits.has_multi_cases) blk: { - const multi_cases_len = self.code.extra[extra_index]; - extra_index += 1; - break :blk multi_cases_len; - } else 0; - - const tag_capture_inst: Zir.Inst.Index = if (extra.data.bits.any_has_tag_capture) blk: { - const tag_capture_inst = self.code.extra[extra_index]; - extra_index += 1; - break :blk @enumFromInt(tag_capture_inst); - } else undefined; - - try self.writeInstRef(stream, extra.data.operand); - - if (extra.data.bits.any_has_tag_capture) { - try stream.writeAll(", tag_capture="); - try self.writeInstIndex(stream, tag_capture_inst); - } - - self.indent += 2; - - const special_prongs = extra.data.bits.special_prongs; - - if (special_prongs.hasElse()) { - const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(self.code.extra[extra_index]); - const capture_text = switch (info.capture) { - .none => "", - .by_val => "by_val ", - .by_ref => "by_ref ", - }; - const inline_text = if (info.is_inline) "inline " else ""; - extra_index += 1; - const body = self.code.bodySlice(extra_index, info.body_len); - extra_index += body.len; - - try stream.writeAll(",\n"); - try stream.splatByteAll(' ', self.indent); - try stream.print("{s}{s}else => ", .{ capture_text, inline_text }); - try self.writeBracedBody(stream, body); - } - - if (special_prongs.hasUnder()) { - var single_item_ref: Zir.Inst.Ref = .none; - var items_len: u32 = 0; - var ranges_len: u32 = 0; - if (special_prongs.hasOneAdditionalItem()) { - single_item_ref = @enumFromInt(self.code.extra[extra_index]); - extra_index += 1; - } else if (special_prongs.hasManyAdditionalItems()) { - items_len = self.code.extra[extra_index]; - extra_index += 1; - ranges_len = self.code.extra[extra_index]; - extra_index += 1; - } - const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(self.code.extra[extra_index]); - extra_index += 1; - const items = self.code.refSlice(extra_index, items_len); - extra_index += items_len; - - try stream.writeAll(",\n"); - try stream.splatByteAll(' ', self.indent); - switch (info.capture) { - .none => {}, - .by_val => try stream.writeAll("by_val "), - .by_ref => try stream.writeAll("by_ref "), - } - if (info.is_inline) try stream.writeAll("inline "); - - try stream.writeAll("_"); - if (single_item_ref != .none) { - try stream.writeAll(", "); - try self.writeInstRef(stream, single_item_ref); - } - for (items) |item_ref| { - try stream.writeAll(", "); - try self.writeInstRef(stream, item_ref); - } - - var range_i: usize = 0; - while (range_i < ranges_len) : (range_i += 1) { - const item_first: Zir.Inst.Ref = @enumFromInt(self.code.extra[extra_index]); - extra_index += 1; - const item_last: Zir.Inst.Ref = @enumFromInt(self.code.extra[extra_index]); - extra_index += 1; - - try stream.writeAll(", "); - try self.writeInstRef(stream, item_first); - try stream.writeAll("..."); - try self.writeInstRef(stream, item_last); - } - - const body = self.code.bodySlice(extra_index, info.body_len); - extra_index += info.body_len; try stream.writeAll(" => "); - try self.writeBracedBody(stream, body); - } - - { - const scalar_cases_len = extra.data.bits.scalar_cases_len; - var scalar_i: usize = 0; - while (scalar_i < scalar_cases_len) : (scalar_i += 1) { - const item_ref: Zir.Inst.Ref = @enumFromInt(self.code.extra[extra_index]); - extra_index += 1; - const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(self.code.extra[extra_index]); - extra_index += 1; - const body = self.code.bodySlice(extra_index, info.body_len); - extra_index += info.body_len; - - try stream.writeAll(",\n"); - try stream.splatByteAll(' ', self.indent); - switch (info.capture) { - .none => {}, - .by_val => try stream.writeAll("by_val "), - .by_ref => try stream.writeAll("by_ref "), - } - if (info.is_inline) try stream.writeAll("inline "); - try self.writeInstRef(stream, item_ref); - try stream.writeAll(" => "); - try self.writeBracedBody(stream, body); - } - } - { - var multi_i: usize = 0; - while (multi_i < multi_cases_len) : (multi_i += 1) { - const items_len = self.code.extra[extra_index]; - extra_index += 1; - const ranges_len = self.code.extra[extra_index]; - extra_index += 1; - const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(self.code.extra[extra_index]); - extra_index += 1; - const items = self.code.refSlice(extra_index, items_len); - extra_index += items_len; - - try stream.writeAll(",\n"); - try stream.splatByteAll(' ', self.indent); - switch (info.capture) { - .none => {}, - .by_val => try stream.writeAll("by_val "), - .by_ref => try stream.writeAll("by_ref "), - } - if (info.is_inline) try stream.writeAll("inline "); - - for (items, 0..) |item_ref, item_i| { - if (item_i != 0) try stream.writeAll(", "); - try self.writeInstRef(stream, item_ref); - } - - var range_i: usize = 0; - while (range_i < ranges_len) : (range_i += 1) { - const item_first: Zir.Inst.Ref = @enumFromInt(self.code.extra[extra_index]); - extra_index += 1; - const item_last: Zir.Inst.Ref = @enumFromInt(self.code.extra[extra_index]); - extra_index += 1; - - if (range_i != 0 or items.len != 0) { - try stream.writeAll(", "); - } - try self.writeInstRef(stream, item_first); - try stream.writeAll("..."); - try self.writeInstRef(stream, item_last); - } - - const body = self.code.bodySlice(extra_index, info.body_len); - extra_index += info.body_len; - try stream.writeAll(" => "); - try self.writeBracedBody(stream, body); - } + try self.writeBracedBody(stream, prong_body); } self.indent -= 2; try stream.writeAll(") "); - try self.writeSrcNode(stream, inst_data.src_node); + try self.writeSrcNode(stream, zir_switch.switch_src_node_offset); + } + + fn writeSwitchCaptures( + self: *Writer, + stream: *std.Io.Writer, + capture: Zir.Inst.SwitchBlock.ProngInfo.Capture, + has_tag_capture: bool, + switch_inst: Zir.Inst.Index, + zir_switch: *const Zir.UnwrappedSwitchBlock, + ) !void { + if (capture != .none) { + try stream.print("{t}=", .{capture}); + const capture_inst = zir_switch.payload_capture_placeholder.unwrap() orelse switch_inst; + try self.writeInstIndex(stream, capture_inst); + try stream.writeAll(" "); + } + if (has_tag_capture) { + try stream.writeAll("tag="); + const capture_inst = zir_switch.tag_capture_placeholder.unwrap() orelse switch_inst; + try self.writeInstIndex(stream, capture_inst); + try stream.writeAll(" "); + } } fn writePlNodeField(self: *Writer, stream: *std.Io.Writer, inst: Zir.Inst.Index) !void { -- 2.54.0