astgen: catch error capture, @panic, @errorName, @field, larger block scopes

- Add error capture scope to orelseCatchExpr (catch |err| now creates
  a ScopeLocalVal for the captured error variable)
- Add @panic, @errorName, @field builtins
- Increase blockExprStmts scope arrays from 64 to 128 entries
  (build.zig has 93 var decls in a single block)

corpus build.zig still skipped: needs switchExprErrUnion optimization
(catch |err| switch(err) pattern).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-14 09:15:14 +00:00
parent 3aced7124e
commit b2592f40be
2 changed files with 92 additions and 17 deletions

View File

@@ -2346,7 +2346,7 @@ static uint32_t forExpr(
GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node, bool is_statement);
static uint32_t orelseCatchExpr(GenZir* gz, Scope* scope, ResultLoc rl,
uint32_t node, ZirInstTag cond_op, ZirInstTag unwrap_op,
ZirInstTag unwrap_code_op);
ZirInstTag unwrap_code_op, uint32_t payload_token);
static uint32_t arrayInitDotExpr(
GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node);
static uint32_t switchExpr(
@@ -2475,6 +2475,7 @@ static uint32_t tryResolvePrimitiveIdent(GenZir* gz, uint32_t node);
#define COMPTIME_REASON_ARRAY_LENGTH 33
#define COMPTIME_REASON_ALIGN 50
#define COMPTIME_REASON_ADDRSPACE 51
#define COMPTIME_REASON_FIELD_NAME 42
#define COMPTIME_REASON_COMPTIME_KEYWORD 53
#define COMPTIME_REASON_SWITCH_ITEM 56
#define COMPTIME_REASON_TUPLE_FIELD_DEFAULT_VALUE 57
@@ -2923,6 +2924,51 @@ static uint32_t builtinCall(
return rvalue(gz, rl,
addPlNodeBin(gz, ZIR_INST_MAX, node, a, b), node);
}
// @panic — simpleUnOp with dbg_node (AstGen.zig:9429-9432).
if (name_len == 5 && memcmp(source + name_start, "panic", 5) == 0) {
emitDbgNode(gz, node);
AstData nd = tree->nodes.datas[node];
ResultLoc panic_rl = { .tag = RL_COERCED_TY,
.data = ZIR_REF_SLICE_CONST_U8_TYPE,
.src_node = 0, .ctx = RI_CTX_NONE };
uint32_t operand = exprRl(gz, scope, panic_rl, nd.lhs);
uint32_t result = addUnNode(gz, ZIR_INST_PANIC, operand, node);
return rvalue(gz, rl, result, node);
}
// @errorName — simpleUnOp with dbg_stmt (AstGen.zig:9391).
if (name_len == 9 && memcmp(source + name_start, "errorName", 9) == 0) {
advanceSourceCursorToMainToken(ag, gz, node);
uint32_t saved_line = ag->source_line - gz->decl_line;
uint32_t saved_col = ag->source_column;
AstData nd = tree->nodes.datas[node];
ResultLoc err_rl = { .tag = RL_COERCED_TY,
.data = ZIR_REF_ANYERROR_TYPE,
.src_node = 0, .ctx = RI_CTX_NONE };
uint32_t operand = exprRl(gz, scope, err_rl, nd.lhs);
emitDbgStmt(gz, saved_line, saved_col);
uint32_t result = addUnNode(gz, ZIR_INST_ERROR_NAME, operand, node);
return rvalue(gz, rl, result, node);
}
// @field (AstGen.zig:9288-9300).
if (name_len == 5 && memcmp(source + name_start, "field", 5) == 0) {
AstData nd = tree->nodes.datas[node];
ResultLoc field_rl = { .tag = RL_COERCED_TY,
.data = ZIR_REF_SLICE_CONST_U8_TYPE,
.src_node = 0, .ctx = RI_CTX_NONE };
if (RL_IS_REF(rl)) {
uint32_t lhs = exprRl(gz, scope, RL_REF_VAL, nd.lhs);
uint32_t fname = comptimeExpr(
gz, scope, field_rl, nd.rhs, COMPTIME_REASON_FIELD_NAME);
return addPlNodeBin(
gz, ZIR_INST_FIELD_PTR_NAMED, node, lhs, fname);
}
uint32_t lhs = expr(gz, scope, nd.lhs);
uint32_t fname = comptimeExpr(
gz, scope, field_rl, nd.rhs, COMPTIME_REASON_FIELD_NAME);
uint32_t result = addPlNodeBin(
gz, ZIR_INST_FIELD_VAL_NAMED, node, lhs, fname);
return rvalue(gz, rl, result, node);
}
// clang-format on
// TODO: handle other builtins.
@@ -4916,20 +4962,26 @@ static uint32_t exprRl(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) {
if (RL_IS_REF(rl)) {
return orelseCatchExpr(gz, scope, rl, node,
ZIR_INST_IS_NON_NULL_PTR, ZIR_INST_OPTIONAL_PAYLOAD_UNSAFE_PTR,
(ZirInstTag)0);
(ZirInstTag)0, UINT32_MAX);
} else {
return orelseCatchExpr(gz, scope, rl, node, ZIR_INST_IS_NON_NULL,
ZIR_INST_OPTIONAL_PAYLOAD_UNSAFE, (ZirInstTag)0);
ZIR_INST_OPTIONAL_PAYLOAD_UNSAFE, (ZirInstTag)0, UINT32_MAX);
}
// catch (AstGen.zig:1017-1052).
case AST_NODE_CATCH:
case AST_NODE_CATCH: {
uint32_t catch_token = ag->tree->nodes.main_tokens[node];
uint32_t pt = (ag->tree->tokens.tags[catch_token + 1] == TOKEN_PIPE)
? catch_token + 2
: UINT32_MAX;
if (RL_IS_REF(rl)) {
return orelseCatchExpr(gz, scope, rl, node,
ZIR_INST_IS_NON_ERR_PTR, ZIR_INST_ERR_UNION_PAYLOAD_UNSAFE_PTR,
ZIR_INST_ERR_UNION_CODE_PTR);
ZIR_INST_ERR_UNION_CODE_PTR, pt);
} else {
return orelseCatchExpr(gz, scope, rl, node, ZIR_INST_IS_NON_ERR,
ZIR_INST_ERR_UNION_PAYLOAD_UNSAFE, ZIR_INST_ERR_UNION_CODE);
ZIR_INST_ERR_UNION_PAYLOAD_UNSAFE, ZIR_INST_ERR_UNION_CODE,
pt);
}
}
// Block expressions (AstGen.zig:984-992).
case AST_NODE_BLOCK_TWO:
@@ -6220,10 +6272,7 @@ static uint32_t forExpr(
static uint32_t orelseCatchExpr(GenZir* gz, Scope* scope, ResultLoc rl,
uint32_t node, ZirInstTag cond_op, ZirInstTag unwrap_op,
ZirInstTag unwrap_code_op) {
// unwrap_code_op used for catch payload capture scope (not yet
// implemented in C, but passed for correctness/future use).
(void)unwrap_code_op;
ZirInstTag unwrap_code_op, uint32_t payload_token) {
AstGenCtx* ag = gz->astgen;
const Ast* tree = ag->tree;
AstData nd = tree->nodes.datas[node];
@@ -6283,10 +6332,33 @@ static uint32_t orelseCatchExpr(GenZir* gz, Scope* scope, ResultLoc rl,
if (do_err_trace && nodeMayAppendToErrorTrace(tree, nd.lhs))
addSaveErrRetIndex(&else_scope, ZIR_REF_NONE);
// Error capture scope (AstGen.zig:6102-6123).
ScopeLocalVal err_val_scope;
memset(&err_val_scope, 0, sizeof(err_val_scope));
Scope* else_sub_scope = &else_scope.base;
if (payload_token != UINT32_MAX) {
if (tokenIsUnderscore(tree, payload_token)) {
// Discard |_| — else_sub_scope stays as &else_scope.base.
} else {
uint32_t err_name = identAsString(ag, payload_token);
uint32_t err_inst
= addUnNode(&else_scope, unwrap_code_op, operand, node);
err_val_scope = (ScopeLocalVal) {
.base = { .tag = SCOPE_LOCAL_VAL },
.parent = &else_scope.base,
.gen_zir = &else_scope,
.inst = err_inst,
.token_src = payload_token,
.name = err_name,
};
else_sub_scope = &err_val_scope.base;
}
}
// Use fullBodyExpr (not expr) to inline unlabeled blocks
// (AstGen.zig:6125).
uint32_t else_result
= fullBodyExpr(&else_scope, &else_scope.base, break_rl, nd.rhs);
= fullBodyExpr(&else_scope, else_sub_scope, break_rl, nd.rhs);
if (!endsWithNoReturn(&else_scope)) {
// restoreErrRetIndex (AstGen.zig:6128-6129).
if (do_err_trace)
@@ -8234,10 +8306,10 @@ static void blockExprStmts(GenZir* gz, Scope* scope,
const uint32_t* statements, uint32_t stmt_count) {
AstGenCtx* ag = gz->astgen;
// Stack-allocated scope storage for local variables and defers.
// Max 64 local variable declarations and 64 defers per block.
ScopeLocalVal val_scopes[64];
ScopeLocalPtr ptr_scopes[64];
ScopeDefer defer_scopes[64];
// Max 128 local variable declarations and 128 defers per block.
ScopeLocalVal val_scopes[128];
ScopeLocalPtr ptr_scopes[128];
ScopeDefer defer_scopes[128];
uint32_t val_idx = 0;
uint32_t ptr_idx = 0;
uint32_t defer_idx = 0;
@@ -8314,7 +8386,7 @@ static void blockExprStmts(GenZir* gz, Scope* scope,
case AST_NODE_SIMPLE_VAR_DECL:
case AST_NODE_LOCAL_VAR_DECL:
case AST_NODE_ALIGNED_VAR_DECL:
if (val_idx < 64 && ptr_idx < 64) {
if (val_idx < 128 && ptr_idx < 128) {
varDecl(gz, cur_scope, stmt, &val_scopes[val_idx],
&ptr_scopes[ptr_idx], &cur_scope);
// Check which one was used: if scope now points to
@@ -8330,7 +8402,7 @@ static void blockExprStmts(GenZir* gz, Scope* scope,
// defer/errdefer (AstGen.zig:2580-2581).
case AST_NODE_DEFER:
case AST_NODE_ERRDEFER: {
if (defer_idx >= 64) {
if (defer_idx >= 128) {
SET_ERROR(ag);
break;
}

View File

@@ -793,6 +793,9 @@ test "astgen: corpus test_all.zig" {
}
test "astgen: corpus build.zig" {
// TODO: 6 extra instructions — missing switchExprErrUnion optimization
// (catch |err| switch(err) pattern emits SWITCH_BLOCK instead of
// SWITCH_BLOCK_ERR_UNION).
if (true) return error.SkipZigTest;
const gpa = std.testing.allocator;
try corpusCheck(gpa, @embedFile("../build.zig"));