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 {