From d08206471bb430724c6fab2684756b13fda19933 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Motiejus=20Jak=C5=A1tys?= Date: Sat, 14 Feb 2026 00:46:39 +0000 Subject: [PATCH] astgen: fix switchExpr captures, underscore prong, switch_block_ref, labels, and body fixups Co-Authored-By: Claude Opus 4.6 --- astgen.c | 743 ++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 573 insertions(+), 170 deletions(-) diff --git a/astgen.c b/astgen.c index 5c691186e0..b04e3503d4 100644 --- a/astgen.c +++ b/astgen.c @@ -6677,14 +6677,105 @@ static uint32_t whileExpr( // Handles switch and switch_comma expressions. // Encoding: switch_block pl_node with SwitchBlock extra payload. +// Helper: append body instruction with ref_table fixups to pay buffer. +// Mirrors appendPossiblyRefdBodyInst (AstGen.zig:13675-13683) but writes +// to a dynamically-grown pay buffer instead of extra. +static void appendPossiblyRefdBodyInstPay(AstGenCtx* ag, uint32_t body_inst, + uint32_t** pay, uint32_t* pay_len, uint32_t* pay_cap) { + if (*pay_len >= *pay_cap) { + *pay_cap *= 2; + uint32_t* p = realloc(*pay, *pay_cap * sizeof(uint32_t)); + if (!p) + abort(); + *pay = p; + } + (*pay)[(*pay_len)++] = body_inst; + uint32_t ref_inst; + if (refTableFetchRemove(ag, body_inst, &ref_inst)) { + appendPossiblyRefdBodyInstPay(ag, ref_inst, pay, pay_len, pay_cap); + } +} + +// Helper: append body with fixups and extra refs to pay buffer. +// Mirrors appendBodyWithFixupsExtraRefsArrayList (AstGen.zig:13659-13673). +static void appendBodyWithFixupsExtraRefsPay(AstGenCtx* ag, + const uint32_t* body, uint32_t body_len, const uint32_t* extra_refs, + uint32_t extra_refs_len, uint32_t** pay, uint32_t* pay_len_p, + uint32_t* pay_cap_p) { + for (uint32_t i = 0; i < extra_refs_len; i++) { + uint32_t ref_inst; + if (refTableFetchRemove(ag, extra_refs[i], &ref_inst)) { + appendPossiblyRefdBodyInstPay( + ag, ref_inst, pay, pay_len_p, pay_cap_p); + } + } + for (uint32_t i = 0; i < body_len; i++) { + appendPossiblyRefdBodyInstPay(ag, body[i], pay, pay_len_p, pay_cap_p); + } +} + +// Helper: ensure pay buffer has capacity for `additional` more entries. +static void ensurePayCapacity( + uint32_t** pay, uint32_t* pay_cap, uint32_t pay_len, uint32_t additional) { + uint32_t needed = pay_len + additional; + if (needed > *pay_cap) { + while (*pay_cap < needed) + *pay_cap *= 2; + uint32_t* p = realloc(*pay, *pay_cap * sizeof(uint32_t)); + if (!p) + abort(); + *pay = p; + } +} + +// Helper: get values for a switch case node. For SWITCH_CASE_ONE / +// SWITCH_CASE_INLINE_ONE, returns the single value (or NULL if else). +// For SWITCH_CASE / SWITCH_CASE_INLINE, returns the SubRange values. +// Returns values count and sets *values_arr to point to the values. +// For _ONE variants, writes to single_buf and returns pointer to it. +static uint32_t switchCaseValues(const Ast* tree, uint32_t case_node, + uint32_t* single_buf, const uint32_t** values_arr) { + AstNodeTag ct = tree->nodes.tags[case_node]; + AstData cd = tree->nodes.datas[case_node]; + switch (ct) { + case AST_NODE_SWITCH_CASE_ONE: + case AST_NODE_SWITCH_CASE_INLINE_ONE: + if (cd.lhs == 0) { + *values_arr = NULL; + return 0; // else prong + } + *single_buf = cd.lhs; + *values_arr = single_buf; + return 1; + case AST_NODE_SWITCH_CASE: + case AST_NODE_SWITCH_CASE_INLINE: { + uint32_t ist = tree->extra_data.arr[cd.lhs]; + uint32_t ien = tree->extra_data.arr[cd.lhs + 1]; + *values_arr = tree->extra_data.arr + ist; + return ien - ist; + } + default: + *values_arr = NULL; + return 0; + } +} + static uint32_t switchExpr( - GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { - AstGenCtx* ag = gz->astgen; + GenZir* parent_gz, Scope* scope, ResultLoc rl, uint32_t node) { + AstGenCtx* ag = parent_gz->astgen; const Ast* tree = ag->tree; bool need_rl = nodesNeedRlContains(ag, node); - ResultLoc break_rl = breakResultInfo(gz, rl, node, need_rl); + ResultLoc break_rl = breakResultInfo(parent_gz, rl, node, need_rl); AstData nd = tree->nodes.datas[node]; + // Detect label (AstGen.zig:7652-7654 / Ast.zig switchFull). + uint32_t main_token = tree->nodes.main_tokens[node]; + uint32_t label_token = UINT32_MAX; // none + if (tree->tokens.tags[main_token] == TOKEN_IDENTIFIER) { + label_token = main_token; + // switch_token = main_token + 2 (skip label + colon) + } + // AST_NODE_SWITCH: lhs = condition node, rhs = extra index for SubRange. // SubRange[rhs] = { cases_start, cases_end }. // Case nodes are at extra_data[cases_start..cases_end]. @@ -6695,119 +6786,370 @@ static uint32_t switchExpr( const uint32_t* case_nodes_arr = tree->extra_data.arr + cases_start; uint32_t case_count = cases_end - cases_start; - // Save operand source location before evaluating (AstGen.zig:7774-7775). - advanceSourceCursorToNode(ag, cond_node); - uint32_t operand_lc_line = ag->source_line - gz->decl_line; - uint32_t operand_lc_col = ag->source_column; - - // Evaluate switch operand (AstGen.zig:7777). - uint32_t cond_ref = expr(gz, scope, cond_node); - - // --- First pass: categorize cases (AstGen.zig:7671-7762) --- + // --- First pass: categorize cases (AstGen.zig:7659-7762) --- + bool any_payload_is_ref = false; + bool any_has_tag_capture = false; + bool any_non_inline_capture = false; uint32_t scalar_cases_len = 0; uint32_t multi_cases_len = 0; bool has_else = false; + // Underscore prong tracking (AstGen.zig:7667-7670). + uint32_t underscore_case_idx = UINT32_MAX; // index into case_nodes_arr + uint32_t underscore_node = UINT32_MAX; // the `_` value node + // underscore_additional_items: 0=none, 2=under, 4=under_one_item, + // 6=under_many_items (matching SpecialProngs bit patterns). + uint32_t underscore_additional_items = 2; // .under for (uint32_t ci = 0; ci < case_count; ci++) { uint32_t cn = case_nodes_arr[ci]; AstNodeTag ct = tree->nodes.tags[cn]; AstData cd = tree->nodes.datas[cn]; + bool is_inline = (ct == AST_NODE_SWITCH_CASE_INLINE_ONE + || ct == AST_NODE_SWITCH_CASE_INLINE); - switch (ct) { - case AST_NODE_SWITCH_CASE_ONE: - case AST_NODE_SWITCH_CASE_INLINE_ONE: - if (cd.lhs == 0) - has_else = true; - else if (tree->nodes.tags[cd.lhs] == AST_NODE_SWITCH_RANGE) - multi_cases_len++; - else - scalar_cases_len++; - break; - case AST_NODE_SWITCH_CASE: - case AST_NODE_SWITCH_CASE_INLINE: - multi_cases_len++; - break; - default: - break; + // Check payload token for ref/tag capture (AstGen.zig:7673-7689). + uint32_t arrow_token = tree->nodes.main_tokens[cn]; // => + if (tree->tokens.tags[arrow_token + 1] == TOKEN_PIPE) { + uint32_t payload_token = arrow_token + 2; + uint32_t ident = payload_token; + if (tree->tokens.tags[payload_token] == TOKEN_ASTERISK) { + any_payload_is_ref = true; + ident = payload_token + 1; + } + if (tree->tokens.tags[ident + 1] == TOKEN_COMMA) { + any_has_tag_capture = true; + } + if (!tokenIsUnderscore(tree, ident)) { + any_non_inline_capture = true; + } } + + // Get values for this case. + uint32_t single_buf; + const uint32_t* values; + uint32_t values_len = switchCaseValues(tree, cn, &single_buf, &values); + + // Check for else prong (values_len == 0) (AstGen.zig:7693-7711). + if (values_len == 0) { + has_else = true; + continue; + } + + // Check for '_' prong (AstGen.zig:7714-7752). + bool case_has_underscore = false; + for (uint32_t vi = 0; vi < values_len; vi++) { + uint32_t val = values[vi]; + if (tree->nodes.tags[val] == AST_NODE_IDENTIFIER + && isUnderscoreIdent(tree, val)) { + underscore_case_idx = ci; + underscore_node = val; + switch (values_len) { + case 1: + underscore_additional_items = 2; // .under + break; + case 2: + underscore_additional_items = 4; // .under_one_item + break; + default: + underscore_additional_items = 6; // .under_many_items + break; + } + case_has_underscore = true; + } + } + if (case_has_underscore) + continue; + + // Categorize as scalar or multi (AstGen.zig:7754-7758). + if (values_len == 1 + && tree->nodes.tags[values[0]] != AST_NODE_SWITCH_RANGE) { + scalar_cases_len++; + } else { + multi_cases_len++; + } + (void)is_inline; // inline_cases_len tracking skipped (issue 13) + (void)cd; } - // Sema expects a dbg_stmt immediately before switch_block - // (AstGen.zig:7806). - emitDbgStmtForceCurrentIndex(gz, operand_lc_line, operand_lc_col); - // --- Create switch_block instruction (AstGen.zig:7809) --- - uint32_t switch_inst = makeBlockInst(ag, ZIR_INST_SWITCH_BLOCK, gz, node); + // Compute special_prongs (AstGen.zig:7764-7770). + // SpecialProngs is a 3-bit field: + // bit 0: has_else + // bits 1-2: underscore variant (0=none, 1=under, 2=under_one_item, + // 3=under_many_items) + bool has_under = (underscore_case_idx != UINT32_MAX); + uint32_t special_prongs; // 3-bit SpecialProngs enum value + { + uint32_t else_bit = has_else ? 1u : 0u; + uint32_t under_bits = has_under ? underscore_additional_items : 0u; + special_prongs = else_bit | under_bits; + } - // --- Single-pass evaluation in source order (AstGen.zig:7849-8027) --- - // Case table + payload buffer pattern (like upstream scratch). - // Table layout: [else?] [scalar_0..N] [multi_0..N] - // Each entry points to the start of that case's data in the buffer. - uint32_t table_size - = (has_else ? 1 : 0) + scalar_cases_len + multi_cases_len; + // Operand result info (AstGen.zig:7772). + ResultLoc operand_ri = any_payload_is_ref ? RL_REF_VAL : RL_NONE_VAL; + + // Save operand source location before evaluating (AstGen.zig:7774-7775). + advanceSourceCursorToNode(ag, cond_node); + uint32_t operand_lc_line = ag->source_line - parent_gz->decl_line; + uint32_t operand_lc_col = ag->source_column; + + // Evaluate switch operand (AstGen.zig:7777). + uint32_t raw_operand = exprRl(parent_gz, scope, operand_ri, cond_node); + + // Compute typeof for labeled switch continue support + // (AstGen.zig:7782-7784). + uint32_t raw_operand_ty_ref = 0; + if (label_token != UINT32_MAX) { + raw_operand_ty_ref + = addUnNode(parent_gz, ZIR_INST_TYPEOF, raw_operand, cond_node); + } + + // Sema expects a dbg_stmt immediately before switch_block(_ref) + // (AstGen.zig:7806). + emitDbgStmtForceCurrentIndex(parent_gz, operand_lc_line, operand_lc_col); + + // Create switch_block instruction (AstGen.zig:7808-7809). + ZirInstTag switch_tag = any_payload_is_ref ? ZIR_INST_SWITCH_BLOCK_REF + : ZIR_INST_SWITCH_BLOCK; + uint32_t switch_inst = makeBlockInst(ag, switch_tag, parent_gz, node); + + // Set up block_scope (AstGen.zig:7800-7826). + GenZir block_scope = makeSubBlock(parent_gz, scope); + block_scope.instructions_top = UINT32_MAX; // unstacked + block_scope.break_result_info = break_rl; + + // Label support (AstGen.zig:7811-7826). + if (label_token != UINT32_MAX) { + block_scope.continue_block = switch_inst; + // continue_result_info: for ref, use ref_coerced_ty; else coerced_ty + // (AstGen.zig:7813-7818). + if (any_payload_is_ref) { + block_scope.break_result_info + = (ResultLoc) { .tag = RL_REF_COERCED_TY, + .data = raw_operand_ty_ref, + .src_node = 0, + .ctx = RI_CTX_NONE }; + } + // Note: we store continue_result_info as break_result_info on + // block_scope for now; the label's block_inst is switch_inst. + block_scope.label_token = label_token; + block_scope.label_block_inst = switch_inst; + } + + // Allocate shared value_placeholder for tag captures + // (AstGen.zig:7833-7844). + uint32_t tag_inst = 0; + if (any_has_tag_capture) { + tag_inst = ag->inst_len; + ensureInstCapacity(ag, 1); + ag->inst_tags[tag_inst] = ZIR_INST_EXTENDED; + ZirInstData tdata; + memset(&tdata, 0, sizeof(tdata)); + tdata.extended.opcode = (uint16_t)ZIR_EXT_VALUE_PLACEHOLDER; + ag->inst_datas[tag_inst] = tdata; + ag->inst_len++; + } + + // Case scope — re-used for all cases (AstGen.zig:7829-7830). + GenZir case_scope = makeSubBlock(parent_gz, &block_scope.base); + case_scope.instructions_top = UINT32_MAX; // unstacked initially + + // --- Payload buffer (AstGen.zig:7789-7798) --- + // Table layout: [else?] [under?] [scalar_0..N] [multi_0..N] uint32_t else_tbl = 0; - uint32_t scalar_tbl = (has_else ? 1 : 0); + uint32_t under_tbl = (has_else ? 1u : 0u); + uint32_t scalar_tbl = under_tbl + (has_under ? 1u : 0u); uint32_t multi_tbl = scalar_tbl + scalar_cases_len; + uint32_t table_size = multi_tbl + multi_cases_len; uint32_t pay_cap = table_size + case_count * 16; + if (pay_cap < 64) + pay_cap = 64; uint32_t* pay = malloc(pay_cap * sizeof(uint32_t)); uint32_t pay_len = table_size; uint32_t scalar_ci = 0; uint32_t multi_ci = 0; + // --- Second pass: emit items and bodies (AstGen.zig:7849-8027) --- for (uint32_t ci = 0; ci < case_count; ci++) { uint32_t cn = case_nodes_arr[ci]; AstNodeTag ct = tree->nodes.tags[cn]; AstData cd = tree->nodes.datas[cn]; + bool is_inline = (ct == AST_NODE_SWITCH_CASE_INLINE_ONE + || ct == AST_NODE_SWITCH_CASE_INLINE); + + // Get values for this case. + uint32_t single_buf; + const uint32_t* values; + uint32_t values_len = switchCaseValues(tree, cn, &single_buf, &values); + + // Determine if this is the else, underscore, scalar, or multi case. + bool is_else_case = (values_len == 0); + bool is_underscore_case = (ci == underscore_case_idx); + bool is_multi_case = false; + if (!is_else_case && !is_underscore_case) { + is_multi_case = (values_len > 1 + || (values_len == 1 + && tree->nodes.tags[values[0]] == AST_NODE_SWITCH_RANGE)); + } + + // Parse payload token (AstGen.zig:7855-7921). + uint32_t dbg_var_name = 0; // NullTerminatedString, 0 = empty + uint32_t dbg_var_inst = 0; + uint32_t dbg_var_tag_name = 0; + uint32_t dbg_var_tag_inst = 0; + bool has_tag_capture = false; + ScopeLocalVal capture_val_scope; + ScopeLocalVal tag_scope_val; + memset(&capture_val_scope, 0, sizeof(capture_val_scope)); + memset(&tag_scope_val, 0, sizeof(tag_scope_val)); + + uint32_t capture = 0; // 0=none, 1=by_val, 2=by_ref + + uint32_t arrow_token = tree->nodes.main_tokens[cn]; + bool has_payload = (tree->tokens.tags[arrow_token + 1] == TOKEN_PIPE); + Scope* sub_scope = &case_scope.base; + + if (has_payload) { + uint32_t payload_token = arrow_token + 2; + bool capture_is_ref + = (tree->tokens.tags[payload_token] == TOKEN_ASTERISK); + uint32_t ident = payload_token + (capture_is_ref ? 1u : 0u); + + capture = capture_is_ref ? 2u : 1u; // by_ref : by_val + + if (tokenIsUnderscore(tree, ident)) { + // Discard capture (AstGen.zig:7874-7878). + if (capture_is_ref) { + SET_ERROR(ag); + free(pay); + return ZIR_REF_VOID_VALUE; + } + capture = 0; // none + // sub_scope stays as &case_scope.base + } else { + // Named capture (AstGen.zig:7880-7892). + uint32_t capture_name = identAsString(ag, ident); + capture_val_scope = (ScopeLocalVal) { + .base = { .tag = SCOPE_LOCAL_VAL }, + .parent = &case_scope.base, + .gen_zir = &case_scope, + .inst = switch_inst + ZIR_REF_START_INDEX, + .token_src = ident, + .name = capture_name, + }; + dbg_var_name = capture_name; + dbg_var_inst = switch_inst + ZIR_REF_START_INDEX; + sub_scope = &capture_val_scope.base; + } + + // Check for tag capture: ident followed by comma + // (AstGen.zig:7895-7921). + if (tree->tokens.tags[ident + 1] == TOKEN_COMMA) { + uint32_t tag_token = ident + 2; + uint32_t tag_name = identAsString(ag, tag_token); + + has_tag_capture = true; + + tag_scope_val = (ScopeLocalVal) { + .base = { .tag = SCOPE_LOCAL_VAL }, + .parent = sub_scope, + .gen_zir = &case_scope, + .inst = tag_inst + ZIR_REF_START_INDEX, + .token_src = tag_token, + .name = tag_name, + }; + dbg_var_tag_name = tag_name; + dbg_var_tag_inst = tag_inst + ZIR_REF_START_INDEX; + sub_scope = &tag_scope_val.base; + } + } + + ensurePayCapacity(&pay, &pay_cap, pay_len, 32); uint32_t hdr = pay_len; uint32_t prong_info_slot = 0; - // Ensure capacity for items (generous estimate). - if (pay_len + 32 > pay_cap) { - pay_cap *= 2; - uint32_t* p = realloc(pay, pay_cap * sizeof(uint32_t)); - if (!p) - abort(); - pay = p; - } - - switch (ct) { - case AST_NODE_SWITCH_CASE_ONE: - case AST_NODE_SWITCH_CASE_INLINE_ONE: - if (cd.lhs == 0) { - // Else: [prong_info, body...] - pay[else_tbl] = hdr; + // Determine case kind and fill item data (AstGen.zig:7924-7995). + if (is_underscore_case && is_multi_case) { + // Underscore case with additional items as multi + // (AstGen.zig:7926-7942). + pay[under_tbl] = hdr; + if (underscore_additional_items == 4) { + // One additional item (AstGen.zig:7928-7937). + // [item_ref, prong_info] + uint32_t item_node; + if (values[0] == underscore_node) + item_node = values[1]; + else + item_node = values[0]; + pay[pay_len++] = comptimeExpr(parent_gz, scope, RL_NONE_VAL, + item_node, COMPTIME_REASON_SWITCH_ITEM); prong_info_slot = pay_len++; - } else if (tree->nodes.tags[cd.lhs] == AST_NODE_SWITCH_RANGE) { - // Single range → multi case: - // [items_len=0, ranges_len=1, prong_info, first, last] - pay[multi_tbl + multi_ci++] = hdr; - pay[pay_len++] = 0; - pay[pay_len++] = 1; - prong_info_slot = pay_len++; - AstData rng = tree->nodes.datas[cd.lhs]; - pay[pay_len++] = comptimeExpr(gz, scope, RL_NONE_VAL, rng.lhs, - COMPTIME_REASON_SWITCH_ITEM); - pay[pay_len++] = comptimeExpr(gz, scope, RL_NONE_VAL, rng.rhs, - COMPTIME_REASON_SWITCH_ITEM); } else { - // Scalar: [item_ref, prong_info, body...] - pay[scalar_tbl + scalar_ci++] = hdr; - pay[pay_len++] = comptimeExpr(gz, scope, RL_NONE_VAL, cd.lhs, - COMPTIME_REASON_SWITCH_ITEM); + // Many additional items: multi format + // (AstGen.zig:7943-7977). + uint32_t nitems = 0; + uint32_t nranges = 0; + for (uint32_t vi = 0; vi < values_len; vi++) { + if (values[vi] == underscore_node) + continue; + if (tree->nodes.tags[values[vi]] == AST_NODE_SWITCH_RANGE) + nranges++; + else + nitems++; + } + pay[pay_len++] = nitems; + pay[pay_len++] = nranges; prong_info_slot = pay_len++; + // Non-range items. + for (uint32_t vi = 0; vi < values_len; vi++) { + if (values[vi] == underscore_node) + continue; + if (tree->nodes.tags[values[vi]] + != AST_NODE_SWITCH_RANGE) { + ensurePayCapacity(&pay, &pay_cap, pay_len, 1); + pay[pay_len++] + = comptimeExpr(parent_gz, scope, RL_NONE_VAL, + values[vi], COMPTIME_REASON_SWITCH_ITEM); + } + } + // Range pairs. + for (uint32_t vi = 0; vi < values_len; vi++) { + if (values[vi] == underscore_node) + continue; + if (tree->nodes.tags[values[vi]] + == AST_NODE_SWITCH_RANGE) { + AstData rng = tree->nodes.datas[values[vi]]; + ensurePayCapacity(&pay, &pay_cap, pay_len, 2); + pay[pay_len++] = comptimeExpr(parent_gz, scope, + RL_NONE_VAL, rng.lhs, COMPTIME_REASON_SWITCH_ITEM); + pay[pay_len++] = comptimeExpr(parent_gz, scope, + RL_NONE_VAL, rng.rhs, COMPTIME_REASON_SWITCH_ITEM); + } + } } - break; - case AST_NODE_SWITCH_CASE: - case AST_NODE_SWITCH_CASE_INLINE: { - // Multi-item: SubRange[lhs] of items, rhs = body. + } else if (is_else_case) { + // Else prong (AstGen.zig:7978-7981). + pay[else_tbl] = hdr; + prong_info_slot = pay_len++; + } else if (is_underscore_case) { + // Underscore-only prong, no additional items + // (AstGen.zig:7982-7986). + pay[under_tbl] = hdr; + prong_info_slot = pay_len++; + } else if (!is_multi_case) { + // Scalar case (AstGen.zig:7987-7994). + pay[scalar_tbl + scalar_ci++] = hdr; + pay[pay_len++] = comptimeExpr(parent_gz, scope, RL_NONE_VAL, + values[0], COMPTIME_REASON_SWITCH_ITEM); + prong_info_slot = pay_len++; + } else { + // Multi case (AstGen.zig:7939-7977 non-underscore path). pay[multi_tbl + multi_ci++] = hdr; - uint32_t ist = tree->extra_data.arr[cd.lhs]; - uint32_t ien = tree->extra_data.arr[cd.lhs + 1]; - uint32_t nitems = 0, nranges = 0; - for (uint32_t j = ist; j < ien; j++) { - if (tree->nodes.tags[tree->extra_data.arr[j]] - == AST_NODE_SWITCH_RANGE) + uint32_t nitems = 0; + uint32_t nranges = 0; + for (uint32_t vi = 0; vi < values_len; vi++) { + if (tree->nodes.tags[values[vi]] == AST_NODE_SWITCH_RANGE) nranges++; else nitems++; @@ -6816,128 +7158,189 @@ static uint32_t switchExpr( pay[pay_len++] = nranges; prong_info_slot = pay_len++; // Non-range items. - for (uint32_t j = ist; j < ien; j++) { - uint32_t item = tree->extra_data.arr[j]; - if (tree->nodes.tags[item] != AST_NODE_SWITCH_RANGE) { - if (pay_len + 2 > pay_cap) { - pay_cap *= 2; - uint32_t* p = realloc(pay, pay_cap * sizeof(uint32_t)); - if (!p) - abort(); - pay = p; - } - pay[pay_len++] = comptimeExpr(gz, scope, RL_NONE_VAL, item, - COMPTIME_REASON_SWITCH_ITEM); + for (uint32_t vi = 0; vi < values_len; vi++) { + if (tree->nodes.tags[values[vi]] != AST_NODE_SWITCH_RANGE) { + ensurePayCapacity(&pay, &pay_cap, pay_len, 1); + pay[pay_len++] = comptimeExpr(parent_gz, scope, + RL_NONE_VAL, values[vi], COMPTIME_REASON_SWITCH_ITEM); } } // Range pairs. - for (uint32_t j = ist; j < ien; j++) { - uint32_t item = tree->extra_data.arr[j]; - if (tree->nodes.tags[item] == AST_NODE_SWITCH_RANGE) { - AstData rng = tree->nodes.datas[item]; - if (pay_len + 2 > pay_cap) { - pay_cap *= 2; - uint32_t* p = realloc(pay, pay_cap * sizeof(uint32_t)); - if (!p) - abort(); - pay = p; - } - pay[pay_len++] = comptimeExpr(gz, scope, RL_NONE_VAL, - rng.lhs, COMPTIME_REASON_SWITCH_ITEM); - pay[pay_len++] = comptimeExpr(gz, scope, RL_NONE_VAL, - rng.rhs, COMPTIME_REASON_SWITCH_ITEM); + for (uint32_t vi = 0; vi < values_len; vi++) { + if (tree->nodes.tags[values[vi]] == AST_NODE_SWITCH_RANGE) { + AstData rng = tree->nodes.datas[values[vi]]; + ensurePayCapacity(&pay, &pay_cap, pay_len, 2); + pay[pay_len++] = comptimeExpr(parent_gz, scope, + RL_NONE_VAL, rng.lhs, COMPTIME_REASON_SWITCH_ITEM); + pay[pay_len++] = comptimeExpr(parent_gz, scope, + RL_NONE_VAL, rng.rhs, COMPTIME_REASON_SWITCH_ITEM); } } - break; - } - default: - continue; } // Evaluate body (AstGen.zig:7997-8026). - uint32_t body_node = cd.rhs; - GenZir case_scope = makeSubBlock(gz, scope); + { + // Temporarily stack case_scope on parent_gz + // (AstGen.zig:7998-8000). + case_scope.instructions_top = parent_gz->astgen->scratch_inst_len; - // Note: upstream regular switchExpr (AstGen.zig:7625) does NOT emit - // save_err_ret_index. Only switchExprErrUnion (AstGen.zig:7524) does. + if (dbg_var_name != 0) { + addDbgVar(&case_scope, ZIR_INST_DBG_VAR_VAL, dbg_var_name, + dbg_var_inst); + } + if (dbg_var_tag_name != 0) { + addDbgVar(&case_scope, ZIR_INST_DBG_VAR_VAL, dbg_var_tag_name, + dbg_var_tag_inst); + } - // Use fullBodyExpr to process body inline (AstGen.zig:8009). - uint32_t result - = fullBodyExpr(&case_scope, &case_scope.base, break_rl, body_node); - if (!refIsNoReturn(gz, result)) { - addBreak(&case_scope, ZIR_INST_BREAK, switch_inst, result, - (int32_t)body_node - (int32_t)gz->decl_node_index); + uint32_t body_node = cd.rhs; + uint32_t result = fullBodyExpr(&case_scope, sub_scope, + block_scope.break_result_info, body_node); + if (!refIsNoReturn(parent_gz, result)) { + addBreak(&case_scope, ZIR_INST_BREAK, switch_inst, result, + (int32_t)body_node - (int32_t)parent_gz->decl_node_index); + } + + uint32_t raw_body_len = gzInstructionsLen(&case_scope); + const uint32_t* body = gzInstructionsSlice(&case_scope); + + // Body fixups with extra refs (AstGen.zig:8016-8025). + uint32_t extra_refs[2]; + uint32_t extra_refs_len = 0; + extra_refs[extra_refs_len++] = switch_inst; + if (has_tag_capture) { + extra_refs[extra_refs_len++] = tag_inst; + } + + uint32_t body_len = countBodyLenAfterFixupsExtraRefs( + ag, body, raw_body_len, extra_refs, extra_refs_len); + + // Encode ProngInfo (AstGen.zig:8019-8024). + uint32_t prong_info = (body_len & 0x0FFFFFFFu) + | ((capture & 3u) << 28) | ((is_inline ? 1u : 0u) << 30) + | ((has_tag_capture ? 1u : 0u) << 31); + pay[prong_info_slot] = prong_info; + + ensurePayCapacity(&pay, &pay_cap, pay_len, body_len); + appendBodyWithFixupsExtraRefsPay(ag, body, raw_body_len, + extra_refs, extra_refs_len, &pay, &pay_len, &pay_cap); + + gzUnstack(&case_scope); } - uint32_t body_len = gzInstructionsLen(&case_scope); - const uint32_t* body = gzInstructionsSlice(&case_scope); - - pay[prong_info_slot] = body_len & 0x0FFFFFFFu; - - if (pay_len + body_len > pay_cap) { - while (pay_len + body_len > pay_cap) - pay_cap *= 2; - uint32_t* p = realloc(pay, pay_cap * sizeof(uint32_t)); - if (!p) - abort(); - pay = p; - } - for (uint32_t i = 0; i < body_len; i++) - pay[pay_len++] = body[i]; - gzUnstack(&case_scope); } + // Now add switch_block to parent (AstGen.zig:8034). + gzAppendInstruction(parent_gz, switch_inst); + // --- Serialize to extra in payload order (AstGen.zig:8036-8110) --- ensureExtraCapacity(ag, - 2 + (uint32_t)(multi_cases_len > 0 ? 1 : 0) + pay_len - table_size); + 2 + (uint32_t)(multi_cases_len > 0 ? 1 : 0) + + (any_has_tag_capture ? 1u : 0u) + pay_len - table_size); uint32_t payload_index = ag->extra_len; - ag->extra[ag->extra_len++] = cond_ref; + // SwitchBlock.operand (AstGen.zig:8042). + ag->extra[ag->extra_len++] = raw_operand; - uint32_t bits = 0; - if (multi_cases_len > 0) - bits |= 1u; - if (has_else) - bits |= (1u << 1); - bits |= (scalar_cases_len & 0x1FFFFFFu) << 7; - ag->extra[ag->extra_len++] = bits; + // SwitchBlock.bits (AstGen.zig:8043-8050). + { + uint32_t bits = 0; + if (multi_cases_len > 0) + bits |= 1u; // has_multi_cases (bit 0) + bits |= (special_prongs & 7u) << 1; // special_prongs (bits 1-3) + if (any_has_tag_capture) + bits |= (1u << 4); // any_has_tag_capture (bit 4) + if (any_non_inline_capture) + bits |= (1u << 5); // any_non_inline_capture (bit 5) + // has_continue (bit 6): set if label used for continue. + // We don't track used_for_continue precisely, so set it if label + // exists (conservative). Skipping precise tracking per issue 9. + // Actually: only set if label_token != UINT32_MAX. + // The upstream checks block_scope.label.used_for_continue; we + // don't track that, so conservatively set it when there's a label. + // This is safe: sema handles the flag as an optimization hint. + if (label_token != UINT32_MAX) + bits |= (1u << 6); // has_continue + bits |= (scalar_cases_len & 0x1FFFFFFu) << 7; // scalar_cases_len + ag->extra[ag->extra_len++] = bits; + } + // multi_cases_len (AstGen.zig:8053-8055). if (multi_cases_len > 0) ag->extra[ag->extra_len++] = multi_cases_len; - // Else prong. + // tag_inst (AstGen.zig:8057-8059). + if (any_has_tag_capture) + ag->extra[ag->extra_len++] = tag_inst; + + ag->inst_datas[switch_inst].pl_node.payload_index = payload_index; + + // Else prong (AstGen.zig:8064-8070). if (has_else) { uint32_t si = pay[else_tbl]; - uint32_t bl = pay[si] & 0x0FFFFFFFu; - for (uint32_t i = 0; i < 1 + bl; i++) - ag->extra[ag->extra_len++] = pay[si + i]; + uint32_t prong_info = pay[si]; + uint32_t bl = prong_info & 0x0FFFFFFFu; + uint32_t end = si + 1 + bl; + for (uint32_t i = si; i < end; i++) + ag->extra[ag->extra_len++] = pay[i]; } - // Scalar cases. - for (uint32_t i = 0; i < scalar_cases_len; i++) { - uint32_t si = pay[scalar_tbl + i]; - uint32_t bl = pay[si + 1] & 0x0FFFFFFFu; - for (uint32_t j = 0; j < 2 + bl; j++) - ag->extra[ag->extra_len++] = pay[si + j]; + + // Underscore prong (AstGen.zig:8071-8093). + if (has_under) { + uint32_t si = pay[under_tbl]; + uint32_t body_len_idx = si; + uint32_t end = si; + switch (underscore_additional_items) { + case 2: // none + end += 1; // just prong_info + break; + case 4: // one additional item + body_len_idx = si + 1; + end += 2; // item + prong_info + break; + case 6: // many additional items + body_len_idx = si + 2; + end += 3 + pay[si] + 2 * pay[si + 1]; // hdr + items + ranges + break; + default: + break; + } + uint32_t prong_info = pay[body_len_idx]; + uint32_t bl = prong_info & 0x0FFFFFFFu; + end += bl; + for (uint32_t i = si; i < end; i++) + ag->extra[ag->extra_len++] = pay[i]; } - // Multi cases. - for (uint32_t i = 0; i < multi_cases_len; i++) { - uint32_t si = pay[multi_tbl + i]; - uint32_t ni = pay[si]; - uint32_t nr = pay[si + 1]; - uint32_t bl = pay[si + 2] & 0x0FFFFFFFu; - uint32_t total = 3 + ni + nr * 2 + bl; - for (uint32_t j = 0; j < total; j++) - ag->extra[ag->extra_len++] = pay[si + j]; + + // Scalar and multi cases (AstGen.zig:8094-8110). + for (uint32_t i = 0; i < scalar_cases_len + multi_cases_len; i++) { + uint32_t tbl_idx = scalar_tbl + i; + uint32_t si = pay[tbl_idx]; + uint32_t body_len_idx; + uint32_t end = si; + if (tbl_idx < multi_tbl) { + // Scalar: [item, prong_info, body...] + body_len_idx = si + 1; + end += 2; + } else { + // Multi: [items_len, ranges_len, prong_info, items..., ranges...] + body_len_idx = si + 2; + uint32_t ni = pay[si]; + uint32_t nr = pay[si + 1]; + end += 3 + ni + nr * 2; + } + uint32_t prong_info = pay[body_len_idx]; + uint32_t bl = prong_info & 0x0FFFFFFFu; + end += bl; + for (uint32_t j = si; j < end; j++) + ag->extra[ag->extra_len++] = pay[j]; } free(pay); - ag->inst_datas[switch_inst].pl_node.payload_index = payload_index; - gzAppendInstruction(gz, switch_inst); - // AstGen.zig:8112-8115. bool need_result_rvalue = (break_rl.tag != rl.tag); if (need_result_rvalue) - return rvalue(gz, rl, switch_inst + ZIR_REF_START_INDEX, node); + return rvalue(parent_gz, rl, switch_inst + ZIR_REF_START_INDEX, node); return switch_inst + ZIR_REF_START_INDEX; }