From 81ddc5c989b895a3f32a9fdfc6533de4386faa0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Motiejus=20Jak=C5=A1tys?= Date: Sat, 14 Feb 2026 00:34:26 +0000 Subject: [PATCH] astgen: fix whileExpr condition coercion, payload handling, else/continue, result info, and labels Co-Authored-By: Claude Opus 4.6 --- astgen.c | 348 +++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 298 insertions(+), 50 deletions(-) diff --git a/astgen.c b/astgen.c index e11413e465..5c691186e0 100644 --- 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 - // 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: