commit 81ddc5c989b895a3f32a9fdfc6533de4386faa0b (tree)
parent f14f47424d57b143a936d2ab3998b38ecef616a0
Author: Motiejus Jakštys <motiejus@jakstys.lt>
Date: Sat, 14 Feb 2026 00:34:26 +0000
astgen: fix whileExpr condition coercion, payload handling, else/continue, result info, and labels
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat:
| M | astgen.c | | | 352 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------ |
1 file changed, 300 insertions(+), 52 deletions(-)
diff --git a/astgen.c b/astgen.c
@@ -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
+
+ 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:6696-6697).
+ // 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);
-
- // Create continue_scope inside then_scope (AstGen.zig:6725).
- GenZir continue_scope = makeSubBlock(&then_scope, &then_scope.base);
-
- // Execute body (AstGen.zig:6727-6730).
- emitDbgNode(&continue_scope, body_node);
- fullBodyExpr(
- &continue_scope, &continue_scope.base, RL_NONE_VAL, body_node);
+ 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;
+ }
+
+ // 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);
+
+ // 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);
-
- // 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);
- }
+ 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);
- // 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: