astgen: fix switchExpr captures, underscore prong, switch_block_ref, labels, and body fixups

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-14 00:46:39 +00:00
parent 81ddc5c989
commit d08206471b

743
astgen.c
View File

@@ -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;
}