astgen: fix whileExpr condition coercion, payload handling, else/continue, result info, and labels

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

348
astgen.c
View File

@@ -2348,7 +2348,7 @@ static uint32_t arrayInitDotExpr(
static uint32_t switchExpr(
GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node);
static uint32_t whileExpr(
GenZir* gz, Scope* scope, uint32_t node, bool is_statement);
GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node, bool is_statement);
#define EVAL_TO_ERROR_NEVER 0
#define EVAL_TO_ERROR_ALWAYS 1
#define EVAL_TO_ERROR_MAYBE 2
@@ -5158,7 +5158,7 @@ static uint32_t exprRl(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) {
case AST_NODE_WHILE_SIMPLE:
case AST_NODE_WHILE_CONT:
case AST_NODE_WHILE:
return rvalue(gz, rl, whileExpr(gz, scope, node, false), node);
return whileExpr(gz, scope, rlBr(rl), node, false);
// error_value (AstGen.zig:1005).
case AST_NODE_ERROR_VALUE: {
uint32_t error_token = ag->tree->nodes.main_tokens[node] + 2;
@@ -6299,26 +6299,84 @@ static uint32_t orelseCatchExpr(GenZir* gz, Scope* scope, ResultLoc rl,
return block_inst + ZIR_REF_START_INDEX;
}
// --- whileExpr (AstGen.zig:6529-6805) ---
// Handles while_simple.
// --- whileExpr (AstGen.zig:6529-6817) ---
// Handles while_simple, while_cont, while.
// Structure: loop { cond_block { cond, condbr }, repeat }
// condbr → then { continue_block { body, break continue }, break cond }
// → else { break loop }
// condbr → then { [payload], continue_block { [cont_expr], body,
// break continue }, break cond }
// → else { [err_capture], else_body / break loop }
static uint32_t whileExpr(
GenZir* gz, Scope* scope, uint32_t node, bool is_statement) {
GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node, bool is_statement) {
AstGenCtx* ag = gz->astgen;
const Ast* tree = ag->tree;
AstNodeTag node_tag = tree->nodes.tags[node];
AstData nd = tree->nodes.datas[node];
// Compute break_rl from rl (AstGen.zig:6540-6548).
bool need_rl = nodesNeedRlContains(ag, node);
ResultLoc break_rl = breakResultInfo(gz, rl, node, need_rl);
bool need_result_rvalue = (break_rl.tag != rl.tag);
// Detect inline keyword (AstGen.zig:6558).
uint32_t main_token = tree->nodes.main_tokens[node];
bool is_inline = (main_token > 0
&& tree->tokens.tags[main_token - 1] == TOKEN_KEYWORD_INLINE);
// WHILE_SIMPLE: lhs = cond_expr, rhs = body.
// Compute label_token (AstGen.zig fullWhileComponents:2310-2317).
uint32_t label_token = UINT32_MAX;
{
uint32_t tok_i = main_token;
if (is_inline)
tok_i = main_token - 1;
if (tok_i >= 2 && tree->tokens.tags[tok_i - 2] == TOKEN_IDENTIFIER
&& tree->tokens.tags[tok_i - 1] == TOKEN_COLON)
label_token = tok_i - 2;
}
// Extract AST nodes depending on node type (Ast.zig:1925-1958).
uint32_t cond_node = nd.lhs;
uint32_t body_node = nd.rhs;
uint32_t body_node;
uint32_t else_node = 0;
uint32_t cont_expr = 0; // 0 = none
if (node_tag == AST_NODE_WHILE_SIMPLE) {
body_node = nd.rhs;
} else if (node_tag == AST_NODE_WHILE_CONT) {
// WhileCont[rhs]: { cont_expr, then_expr }
cont_expr = tree->extra_data.arr[nd.rhs];
body_node = tree->extra_data.arr[nd.rhs + 1];
} else {
// AST_NODE_WHILE: While[rhs]: { cont_expr, then_expr, else_expr }
// cont_expr is stored via OPT(): UINT32_MAX means none.
uint32_t raw_cont = tree->extra_data.arr[nd.rhs];
cont_expr = (raw_cont == UINT32_MAX) ? 0 : raw_cont;
body_node = tree->extra_data.arr[nd.rhs + 1];
else_node = tree->extra_data.arr[nd.rhs + 2];
}
// Detect payload capture (Ast.zig fullWhileComponents:2318-2321).
uint32_t payload_token = 0;
{
uint32_t last_cond_tok = lastToken(tree, cond_node);
if (last_cond_tok + 2 < tree->tokens.len
&& tree->tokens.tags[last_cond_tok + 2] == TOKEN_PIPE) {
payload_token = last_cond_tok + 3;
}
}
// Detect error token (Ast.zig fullWhileComponents:2322-2329).
uint32_t error_token = 0;
if (else_node != 0) {
uint32_t else_tok = lastToken(tree, body_node) + 1;
if (else_tok + 1 < tree->tokens.len
&& tree->tokens.tags[else_tok + 1] == TOKEN_PIPE) {
error_token = else_tok + 2;
}
}
// Detect payload_is_ref (AstGen.zig:6574-6577).
bool payload_is_ref = (payload_token != 0
&& tree->tokens.tags[payload_token] == TOKEN_ASTERISK);
// Create loop instruction (AstGen.zig:6562-6564).
ZirInstTag loop_tag = is_inline ? ZIR_INST_BLOCK_INLINE : ZIR_INST_LOOP;
@@ -6327,12 +6385,44 @@ static uint32_t whileExpr(
GenZir loop_scope = makeSubBlock(gz, scope);
loop_scope.is_inline = is_inline;
loop_scope.break_result_info = break_rl;
// Evaluate condition in cond_scope (AstGen.zig:6571-6607).
GenZir cond_scope = makeSubBlock(&loop_scope, &loop_scope.base);
GenZir cond_scope = makeSubBlock(gz, &loop_scope.base);
// Emit debug node for the condition expression (AstGen.zig:6579).
emitDbgNode(&cond_scope, cond_node);
uint32_t cond = expr(&cond_scope, &cond_scope.base, cond_node);
emitDbgNode(gz, cond_node);
uint32_t cond_inst; // the value (optional/err-union/bool)
uint32_t bool_bit; // the boolean for condbr
if (error_token != 0) {
// Error union condition (AstGen.zig:6584-6591).
ResultLoc cond_rl = payload_is_ref ? RL_REF_VAL : RL_NONE_VAL;
cond_inst
= fullBodyExpr(&cond_scope, &cond_scope.base, cond_rl, cond_node);
ZirInstTag cond_tag
= payload_is_ref ? ZIR_INST_IS_NON_ERR_PTR : ZIR_INST_IS_NON_ERR;
bool_bit = addUnNode(&cond_scope, cond_tag, cond_inst, cond_node);
} else if (payload_token != 0) {
// Optional condition (AstGen.zig:6592-6599).
ResultLoc cond_rl = payload_is_ref ? RL_REF_VAL : RL_NONE_VAL;
cond_inst
= fullBodyExpr(&cond_scope, &cond_scope.base, cond_rl, cond_node);
ZirInstTag cond_tag
= payload_is_ref ? ZIR_INST_IS_NON_NULL_PTR : ZIR_INST_IS_NON_NULL;
bool_bit = addUnNode(&cond_scope, cond_tag, cond_inst, cond_node);
} else {
// Bool condition (AstGen.zig:6600-6606).
ResultLoc coerced_bool_ri = {
.tag = RL_COERCED_TY,
.data = ZIR_REF_BOOL_TYPE,
.src_node = 0,
.ctx = RI_CTX_NONE,
};
cond_inst = fullBodyExpr(
&cond_scope, &cond_scope.base, coerced_bool_ri, cond_node);
bool_bit = cond_inst;
}
// Create condbr + cond_block (AstGen.zig:6609-6615).
ZirInstTag condbr_tag
@@ -6343,10 +6433,86 @@ static uint32_t whileExpr(
setBlockBody(ag, &cond_scope, cond_block); // unstacks cond_scope
gzAppendInstruction(&loop_scope, cond_block);
// Create continue_block (AstGen.zig:6694).
uint32_t continue_block = makeBlockInst(ag, block_tag, &loop_scope, node);
// Payload handling (AstGen.zig:6623-6690).
// Create payload instructions in the global inst array but don't add to
// any scope's scratch area yet. then_scope and continue_scope are created
// after loop_scope is finalized (to avoid scratch array overlap).
uint32_t dbg_var_name = 0;
uint32_t dbg_var_inst = 0;
uint32_t opt_payload_raw_inst = UINT32_MAX; // raw inst index, not ref
uint32_t payload_ref = 0;
uint32_t payload_ident_token = 0;
uint32_t payload_ident_name = 0;
bool payload_has_scope = false; // true if we need a ScopeLocalVal
// Add repeat to loop_scope (AstGen.zig:6696-6697).
if (error_token != 0 && payload_token != 0) {
// Error union with payload: makeUnNode (AstGen.zig:6628-6655).
ZirInstTag unwrap_tag = payload_is_ref
? ZIR_INST_ERR_UNION_PAYLOAD_UNSAFE_PTR
: ZIR_INST_ERR_UNION_PAYLOAD_UNSAFE;
ensureInstCapacity(ag, 1);
uint32_t raw_idx = ag->inst_len;
ag->inst_tags[raw_idx] = unwrap_tag;
ZirInstData d;
d.un_node.src_node = (int32_t)cond_node - (int32_t)gz->decl_node_index;
d.un_node.operand = cond_inst;
ag->inst_datas[raw_idx] = d;
ag->inst_len++;
payload_ref = raw_idx + ZIR_REF_START_INDEX;
opt_payload_raw_inst = raw_idx;
payload_ident_token = payload_token + (payload_is_ref ? 1u : 0u);
if (tokenIsUnderscore(tree, payload_ident_token)) {
if (payload_is_ref) {
SET_ERROR(ag);
return ZIR_REF_VOID_VALUE;
}
} else {
payload_ident_name = identAsString(ag, payload_ident_token);
payload_has_scope = true;
}
} else if (error_token != 0) {
// Error union without payload (AstGen.zig:6656-6658).
ensureInstCapacity(ag, 1);
uint32_t raw_idx = ag->inst_len;
ag->inst_tags[raw_idx] = ZIR_INST_ENSURE_ERR_UNION_PAYLOAD_VOID;
ZirInstData d;
d.un_node.src_node = (int32_t)node - (int32_t)gz->decl_node_index;
d.un_node.operand = cond_inst;
ag->inst_datas[raw_idx] = d;
ag->inst_len++;
opt_payload_raw_inst = raw_idx;
} else if (payload_token != 0) {
// Optional with payload: makeUnNode (AstGen.zig:6660-6686).
ZirInstTag unwrap_tag = payload_is_ref
? ZIR_INST_OPTIONAL_PAYLOAD_UNSAFE_PTR
: ZIR_INST_OPTIONAL_PAYLOAD_UNSAFE;
ensureInstCapacity(ag, 1);
uint32_t raw_idx = ag->inst_len;
ag->inst_tags[raw_idx] = unwrap_tag;
ZirInstData d;
d.un_node.src_node = (int32_t)cond_node - (int32_t)gz->decl_node_index;
d.un_node.operand = cond_inst;
ag->inst_datas[raw_idx] = d;
ag->inst_len++;
payload_ref = raw_idx + ZIR_REF_START_INDEX;
opt_payload_raw_inst = raw_idx;
payload_ident_token = payload_token + (payload_is_ref ? 1u : 0u);
if (tokenIsUnderscore(tree, payload_ident_token)) {
if (payload_is_ref) {
SET_ERROR(ag);
return ZIR_REF_VOID_VALUE;
}
} else {
payload_ident_name = identAsString(ag, payload_ident_token);
payload_has_scope = true;
}
}
// Create continue_block (AstGen.zig:6695).
// makeBlockInst doesn't add to scratch.
uint32_t continue_block_inst = makeBlockInst(ag, block_tag, gz, node);
// Add repeat to loop_scope (AstGen.zig:6697-6698).
{
ZirInstTag repeat_tag
= is_inline ? ZIR_INST_REPEAT_INLINE : ZIR_INST_REPEAT;
@@ -6356,30 +6522,70 @@ static uint32_t whileExpr(
addInstruction(&loop_scope, repeat_tag, repeat_data);
}
// Set loop body and configure break/continue (AstGen.zig:6699-6701).
// Set loop body and configure break/continue (AstGen.zig:6700-6708).
setBlockBody(ag, &loop_scope, loop_inst); // unstacks loop_scope
loop_scope.break_block = loop_inst;
loop_scope.continue_block = continue_block;
loop_scope.continue_block = continue_block_inst;
if (label_token != UINT32_MAX) {
loop_scope.label_token = label_token;
loop_scope.label_block_inst = loop_inst;
}
// Stack then_scope (AstGen.zig:6708-6709).
// Now create then_scope (AstGen.zig:6617-6621, 6711).
// loop_scope is unstacked, scratch is clean.
GenZir then_scope = makeSubBlock(gz, &cond_scope.base);
Scope* then_sub_scope = &then_scope.base;
ScopeLocalVal payload_val_scope;
memset(&payload_val_scope, 0, sizeof(payload_val_scope));
// Add continue_block to then_scope (AstGen.zig:6716).
gzAppendInstruction(&then_scope, continue_block);
if (payload_has_scope) {
payload_val_scope = (ScopeLocalVal) {
.base = { .tag = SCOPE_LOCAL_VAL },
.parent = &then_scope.base,
.gen_zir = &then_scope,
.inst = payload_ref,
.token_src = payload_ident_token,
.name = payload_ident_name,
};
dbg_var_name = payload_ident_name;
dbg_var_inst = payload_ref;
then_sub_scope = &payload_val_scope.base;
}
// Create continue_scope inside then_scope (AstGen.zig:6725).
GenZir continue_scope = makeSubBlock(&then_scope, &then_scope.base);
// Add payload instruction to then_scope (AstGen.zig:6714-6716).
if (opt_payload_raw_inst != UINT32_MAX)
gzAppendInstruction(&then_scope, opt_payload_raw_inst);
// Add dbg_var_val for payload (AstGen.zig:6717).
if (dbg_var_name != 0)
addDbgVar(
&then_scope, ZIR_INST_DBG_VAR_VAL, dbg_var_name, dbg_var_inst);
// Add continue_block to then_scope (AstGen.zig:6718).
gzAppendInstruction(&then_scope, continue_block_inst);
// Execute body (AstGen.zig:6727-6730).
emitDbgNode(&continue_scope, body_node);
fullBodyExpr(
&continue_scope, &continue_scope.base, RL_NONE_VAL, body_node);
// Emit continue expression if present (AstGen.zig:6723-6725).
// Upstream: unusedResultExpr = emitDbgNode + expr + addEnsureResult.
if (cont_expr != 0) {
emitDbgNode(&then_scope, cont_expr);
uint32_t cont_result = expr(&then_scope, then_sub_scope, cont_expr);
addEnsureResult(&then_scope, cont_result, cont_expr);
}
// Create continue_scope (AstGen.zig:6692-6694, 6727).
GenZir continue_scope = makeSubBlock(gz, then_sub_scope);
// Execute body (AstGen.zig:6728-6731).
uint32_t then_node = body_node;
emitDbgNode(&continue_scope, then_node);
uint32_t unused_result = fullBodyExpr(
&continue_scope, &continue_scope.base, RL_NONE_VAL, then_node);
addEnsureResult(&continue_scope, unused_result, then_node);
// Break continue_block if not noreturn (AstGen.zig:6735-6747).
ZirInstTag break_tag = is_inline ? ZIR_INST_BREAK_INLINE : ZIR_INST_BREAK;
if (!endsWithNoReturn(&continue_scope)) {
// dbg_stmt + dbg_empty_stmt (AstGen.zig:6737-6745).
// dbg_stmt + dbg_empty_stmt (AstGen.zig:6736-6745).
advanceSourceCursor(
ag, tree->tokens.starts[lastToken(tree, body_node)]);
ag, tree->tokens.starts[lastToken(tree, then_node)]);
emitDbgStmt(gz, ag->source_line - gz->decl_line, ag->source_column);
{
ZirInstData ext_data;
@@ -6388,36 +6594,78 @@ static uint32_t whileExpr(
ext_data.extended.operand = 0;
addInstruction(gz, ZIR_INST_EXTENDED, ext_data);
}
ZirInstTag break_tag
= is_inline ? ZIR_INST_BREAK_INLINE : ZIR_INST_BREAK;
addBreak(&continue_scope, break_tag, continue_block,
addBreak(&continue_scope, break_tag, continue_block_inst,
ZIR_REF_VOID_VALUE, AST_NODE_OFFSET_NONE);
}
setBlockBody(ag, &continue_scope, continue_block);
setBlockBody(ag, &continue_scope, continue_block_inst);
// Break cond_block from then_scope (AstGen.zig:6749).
addBreak(&then_scope, break_tag, cond_block, ZIR_REF_VOID_VALUE,
AST_NODE_OFFSET_NONE);
// Break cond_block from then_scope (AstGen.zig:7064).
{
ZirInstTag break_tag
= is_inline ? ZIR_INST_BREAK_INLINE : ZIR_INST_BREAK;
addBreak(&then_scope, break_tag, cond_block, ZIR_REF_VOID_VALUE,
AST_NODE_OFFSET_NONE);
}
// Else scope: break loop with void (AstGen.zig:6785-6788).
// Else scope (AstGen.zig:6751-6797).
GenZir else_scope = makeSubBlock(gz, &cond_scope.base);
{
ZirInstTag break_tag
= is_inline ? ZIR_INST_BREAK_INLINE : ZIR_INST_BREAK;
addBreak(&else_scope, break_tag, loop_inst, ZIR_REF_VOID_VALUE,
if (else_node != 0) {
Scope* else_sub_scope = &else_scope.base;
ScopeLocalVal error_val_scope;
memset(&error_val_scope, 0, sizeof(error_val_scope));
if (error_token != 0) {
// Error capture: else |err| (AstGen.zig:6756-6776).
ZirInstTag err_tag = payload_is_ref ? ZIR_INST_ERR_UNION_CODE_PTR
: ZIR_INST_ERR_UNION_CODE;
uint32_t err_inst
= addUnNode(&else_scope, err_tag, cond_inst, cond_node);
if (tokenIsUnderscore(tree, error_token)) {
// Discard |_| — else_sub_scope stays as &else_scope.base
} else {
uint32_t err_name = identAsString(ag, error_token);
error_val_scope = (ScopeLocalVal) {
.base = { .tag = SCOPE_LOCAL_VAL },
.parent = &else_scope.base,
.gen_zir = &else_scope,
.inst = err_inst,
.token_src = error_token,
.name = err_name,
};
addDbgVar(
&else_scope, ZIR_INST_DBG_VAR_VAL, err_name, err_inst);
else_sub_scope = &error_val_scope.base;
}
}
// Remove continue/break blocks so control flow applies to outer
// loops (AstGen.zig:6783-6784).
loop_scope.continue_block = UINT32_MAX;
loop_scope.break_block = UINT32_MAX;
uint32_t else_result = fullBodyExpr(&else_scope, else_sub_scope,
loop_scope.break_result_info, else_node);
if (is_statement) {
addEnsureResult(&else_scope, else_result, else_node);
}
if (!endsWithNoReturn(&else_scope)) {
addBreak(&else_scope, break_tag, loop_inst, else_result,
(int32_t)else_node - (int32_t)gz->decl_node_index);
}
} else {
// No else: break with void (AstGen.zig:6794-6796).
uint32_t void_result
= rvalue(&else_scope, rl, ZIR_REF_VOID_VALUE, node);
addBreak(&else_scope, break_tag, loop_inst, void_result,
AST_NODE_OFFSET_NONE);
}
// Wire up condbr (AstGen.zig:6795).
setCondBrPayload(ag, condbr, cond, &then_scope, &else_scope);
// Wire up condbr (AstGen.zig:6805).
setCondBrPayload(ag, condbr, bool_bit, &then_scope, &else_scope);
uint32_t result = loop_inst + ZIR_REF_START_INDEX;
// Apply rvalue if needed (AstGen.zig:6807-6810).
uint32_t result;
if (need_result_rvalue)
result = rvalue(gz, rl, loop_inst + ZIR_REF_START_INDEX, node);
else
result = loop_inst + ZIR_REF_START_INDEX;
// Emit ensure_result_used when used as statement (AstGen.zig:6812-6813).
// Emit ensure_result_used when used as statement (AstGen.zig:6812-6814).
if (is_statement) {
addUnNode(gz, ZIR_INST_ENSURE_RESULT_USED, result, node);
}
@@ -7736,7 +7984,7 @@ static void blockExprStmts(GenZir* gz, Scope* scope,
case AST_NODE_WHILE_SIMPLE:
case AST_NODE_WHILE_CONT:
case AST_NODE_WHILE:
(void)whileExpr(gz, cur_scope, inner_node, true);
(void)whileExpr(gz, cur_scope, RL_NONE_VAL, inner_node, true);
break;
case AST_NODE_FOR_SIMPLE:
case AST_NODE_FOR: