From 9bc6ac6679fc200281573636d565f5ea1f91486e Mon Sep 17 00:00:00 2001 From: Motiejus Date: Sat, 14 Feb 2026 14:04:06 +0000 Subject: [PATCH] astgen: assign_destructure, u0 int type, slice_length optimization - Implement assignDestructure() and assignDestructureMaybeDecls() with RL_DESTRUCTURE result location, DestructureComponent types, rvalue handling for validate_destructure/elem_val_imm/store_node, and array init optimization. - Fix tryResolvePrimitiveIdent to allow bit_count==0 (u0/i0 types) and reject leading zeros (u01, i007). - Add nodeIsTriviallyZero and slice_length optimization for arr[start..][0..len] patterns in AST_NODE_SLICE and AST_NODE_SLICE_SENTINEL cases. Co-Authored-By: Claude Opus 4.6 --- stage0/astgen.c | 608 +++++++++++++++++++++++++++++++++++++++-- stage0/astgen_test.zig | 4 +- 2 files changed, 592 insertions(+), 20 deletions(-) diff --git a/stage0/astgen.c b/stage0/astgen.c index 4ad7237e7a..ca4122b629 100644 --- a/stage0/astgen.c +++ b/stage0/astgen.c @@ -185,6 +185,19 @@ typedef enum { RI_CTX_ASSIGNMENT, } ResultCtx; +// DestructureComponent: mirrors Zig's ResultInfo.Loc.DestructureComponent. +typedef enum { + DC_DISCARD, + DC_TYPED_PTR, + DC_INFERRED_PTR, +} DestructureComponentTag; + +typedef struct { + DestructureComponentTag tag; + uint32_t inst; // ZIR inst ref (for DC_TYPED_PTR and DC_INFERRED_PTR). + uint32_t src_node; // Only for DC_TYPED_PTR. +} DestructureComponent; + typedef enum { RL_NONE, // Just compute the value. RL_REF, // Compute a pointer to the value. @@ -194,25 +207,40 @@ typedef enum { RL_PTR, // Store result to typed pointer. data=alloc inst, src_node=node. RL_INFERRED_PTR, // Store result to inferred pointer. data=alloc inst. RL_REF_COERCED_TY, // Ref with pointer type. data=ptr_ty_inst. + RL_DESTRUCTURE, // Destructure into multiple pointers. } ResultLocTag; typedef struct { ResultLocTag tag; uint32_t data; // ZirInstRef: ty_inst for TY/COERCED_TY, alloc inst for // PTR/INFERRED_PTR. - uint32_t src_node; // Only used for RL_PTR. + uint32_t src_node; // Used for RL_PTR and RL_DESTRUCTURE. ResultCtx ctx; // ResultInfo.Context (AstGen.zig:371). + DestructureComponent* components; // Only for RL_DESTRUCTURE. + uint32_t components_len; // Only for RL_DESTRUCTURE. } ResultLoc; #define RL_NONE_VAL \ - ((ResultLoc) { \ - .tag = RL_NONE, .data = 0, .src_node = 0, .ctx = RI_CTX_NONE }) + ((ResultLoc) { .tag = RL_NONE, \ + .data = 0, \ + .src_node = 0, \ + .ctx = RI_CTX_NONE, \ + .components = NULL, \ + .components_len = 0 }) #define RL_REF_VAL \ - ((ResultLoc) { \ - .tag = RL_REF, .data = 0, .src_node = 0, .ctx = RI_CTX_NONE }) + ((ResultLoc) { .tag = RL_REF, \ + .data = 0, \ + .src_node = 0, \ + .ctx = RI_CTX_NONE, \ + .components = NULL, \ + .components_len = 0 }) #define RL_DISCARD_VAL \ - ((ResultLoc) { \ - .tag = RL_DISCARD, .data = 0, .src_node = 0, .ctx = RI_CTX_NONE }) + ((ResultLoc) { .tag = RL_DISCARD, \ + .data = 0, \ + .src_node = 0, \ + .ctx = RI_CTX_NONE, \ + .components = NULL, \ + .components_len = 0 }) #define RL_IS_REF(rl) ((rl).tag == RL_REF || (rl).tag == RL_REF_COERCED_TY) // --- Scope types (AstGen.zig:11621-11768) --- @@ -661,6 +689,13 @@ static uint32_t addUnNode( return addInstruction(gz, tag, data); } +// Mirrors GenZir.addNode (AstGen.zig:12414). +static uint32_t addNode(GenZir* gz, ZirInstTag tag, uint32_t node) { + ZirInstData data; + data.node = (int32_t)node - (int32_t)gz->decl_node_index; + return addInstruction(gz, tag, data); +} + // Mirrors GenZir.addUnTok (AstGen.zig:12497). static uint32_t addUnTok( GenZir* gz, ZirInstTag tag, uint32_t operand, uint32_t abs_tok_index) { @@ -2437,6 +2472,51 @@ static uint32_t rvalue( addPlNodeBin( gz, ZIR_INST_STORE_TO_INFERRED_PTR, node, rl.data, result); return ZIR_REF_VOID_VALUE; + case RL_DESTRUCTURE: { + // validate_destructure (AstGen.zig:11225-11258). + uint32_t ds_node = rl.src_node; + uint32_t comp_len = rl.components_len; + // Emit validate_destructure: pl_node with ValidateDestructure payload. + // Payload: { operand, destructure_node (relative), expect_len } + { + uint32_t payload_idx = gz->astgen->extra_len; + ensureExtraCapacity(gz->astgen, 3); + gz->astgen->extra[gz->astgen->extra_len++] = result; // operand + gz->astgen->extra[gz->astgen->extra_len++] + = (uint32_t)((int32_t)ds_node + - (int32_t)gz->decl_node_index); // destructure_node + gz->astgen->extra[gz->astgen->extra_len++] + = comp_len; // expect_len + addPlNodePayloadIndex( + gz, ZIR_INST_VALIDATE_DESTRUCTURE, node, payload_idx); + } + for (uint32_t i = 0; i < comp_len; i++) { + const DestructureComponent* comp = &rl.components[i]; + if (comp->tag == DC_DISCARD) + continue; + // elem_val_imm: operand=result, idx=i. + uint32_t elem_inst = reserveInstructionIndex(gz->astgen); + gz->astgen->inst_tags[elem_inst] = ZIR_INST_ELEM_VAL_IMM; + gz->astgen->inst_datas[elem_inst].elem_val_imm.operand = result; + gz->astgen->inst_datas[elem_inst].elem_val_imm.idx = i; + gzAppendInstruction(gz, elem_inst); + uint32_t elem_ref = elem_inst + ZIR_REF_START_INDEX; + switch (comp->tag) { + case DC_TYPED_PTR: + addPlNodeBin(gz, ZIR_INST_STORE_NODE, + comp->src_node != 0 ? comp->src_node : node, comp->inst, + elem_ref); + break; + case DC_INFERRED_PTR: + addPlNodeBin(gz, ZIR_INST_STORE_TO_INFERRED_PTR, node, + comp->inst, elem_ref); + break; + case DC_DISCARD: + break; // unreachable + } + } + return ZIR_REF_VOID_VALUE; + } } return result; } @@ -2472,6 +2552,10 @@ static DeferCounts countDefers(const Scope* outer_scope, Scope* inner_scope); static uint32_t exprRl(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node); static void assignStmt(GenZir* gz, Scope* scope, uint32_t infix_node); +static void assignDestructure(GenZir* gz, Scope* scope, uint32_t node); +static Scope* assignDestructureMaybeDecls(GenZir* gz, Scope* scope, + uint32_t node, ScopeLocalVal* val_scopes, uint32_t* val_idx, + ScopeLocalPtr* ptr_scopes, uint32_t* ptr_idx, uint32_t max_scopes); static void assignOp( GenZir* gz, Scope* scope, uint32_t infix_node, ZirInstTag op_tag); static void assignShift( @@ -3616,6 +3700,10 @@ static uint32_t tryResolvePrimitiveIdent(GenZir* gz, uint32_t node) { && (source[tok_start] == 'u' || source[tok_start] == 'i')) { // Zig Signedness enum: unsigned=1, signed=0 uint8_t signedness = (source[tok_start] == 'u') ? 1 : 0; + // Reject leading zeros (e.g. u01, i007) but allow u0/i0. + if (tok_len >= 3 && source[tok_start + 1] == '0') { + return ZIR_REF_NONE; + } uint16_t bit_count = 0; bool valid = true; for (uint32_t k = tok_start + 1; k < tok_end; k++) { @@ -3627,7 +3715,7 @@ static uint32_t tryResolvePrimitiveIdent(GenZir* gz, uint32_t node) { break; } } - if (valid && bit_count > 0) { + if (valid) { ZirInstData data; data.int_type.src_node = (int32_t)node - (int32_t)gz->decl_node_index; @@ -5072,6 +5160,10 @@ static uint32_t structInitExpr( = structInitExprAnon(gz, scope, node, fields, fields_len); return rvalue(gz, rl, struct_inst, node); } + case RL_DESTRUCTURE: + // Struct value cannot be destructured (AstGen.zig:1854-1859). + SET_ERROR(ag); + return ZIR_REF_VOID_VALUE; } SET_ERROR(ag); return ZIR_REF_VOID_VALUE; @@ -5256,6 +5348,31 @@ static uint32_t boolBinOp( return bool_br + ZIR_REF_START_INDEX; } +// Mirrors nodeIsTriviallyZero (AstGen.zig:10299-10313). +static bool nodeIsTriviallyZero(const Ast* tree, uint32_t node) { + if (tree->nodes.tags[node] != AST_NODE_NUMBER_LITERAL) + return false; + uint32_t tok = tree->nodes.main_tokens[node]; + uint32_t tok_start = tree->tokens.starts[tok]; + const char* source = (const char*)tree->source; + if (source[tok_start] != '0') + return false; + // Distinguish "0.." (range, token is "0") from "0.5" (float literal). + char c = source[tok_start + 1]; + if (c == '.') + return source[tok_start + 2] == '.'; + // Any alphanumeric or underscore means the token is longer than "0". + if (c >= '0' && c <= '9') + return false; + if (c >= 'a' && c <= 'z') + return false; + if (c >= 'A' && c <= 'Z') + return false; + if (c == '_') + return false; + return true; +} + // Mirrors expr (AstGen.zig:634) — main expression dispatcher. static uint32_t exprRl(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { AstGenCtx* ag = gz->astgen; @@ -5650,18 +5767,54 @@ static uint32_t exprRl(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { addPlNodeBin(gz, ZIR_INST_SLICE_START, node, lhs, start), node); } case AST_NODE_SLICE: { - // Slice[rhs]: { start, end } (AstGen.zig:908-937). + // Slice[rhs]: { start, end } (AstGen.zig:882-937). const Ast* stree = ag->tree; + uint32_t start_node = stree->extra_data.arr[nd.rhs]; + uint32_t end_node = stree->extra_data.arr[nd.rhs + 1]; + // slice_length optimization (AstGen.zig:887-906). + if (stree->nodes.tags[nd.lhs] == AST_NODE_SLICE_OPEN + && nodeIsTriviallyZero(stree, start_node)) { + AstData inner_nd = stree->nodes.datas[nd.lhs]; + uint32_t lhs = exprRl(gz, scope, RL_REF_VAL, inner_nd.lhs); + ResultLoc usize_rl = { .tag = RL_COERCED_TY, + .data = ZIR_REF_USIZE_TYPE, + .src_node = 0, + .ctx = RI_CTX_NONE, + .components = NULL, + .components_len = 0 }; + uint32_t start_ref = exprRl(gz, scope, usize_rl, inner_nd.rhs); + advanceSourceCursorToMainToken(ag, gz, node); + uint32_t saved_line = ag->source_line - gz->decl_line; + uint32_t saved_col = ag->source_column; + uint32_t len_ref = exprRl(gz, scope, usize_rl, end_node); + emitDbgStmt(gz, saved_line, saved_col); + ensureExtraCapacity(ag, 5); + uint32_t payload_index = ag->extra_len; + ag->extra[ag->extra_len++] = lhs; + ag->extra[ag->extra_len++] = start_ref; + ag->extra[ag->extra_len++] = len_ref; + ag->extra[ag->extra_len++] = ZIR_REF_NONE; // no sentinel + int32_t src_off = (int32_t)nd.lhs - (int32_t)gz->decl_node_index; + memcpy(&ag->extra[ag->extra_len], &src_off, sizeof(uint32_t)); + ag->extra_len++; + ZirInstData data; + data.pl_node.src_node + = (int32_t)node - (int32_t)gz->decl_node_index; + data.pl_node.payload_index = payload_index; + return rvalue( + gz, rl, addInstruction(gz, ZIR_INST_SLICE_LENGTH, data), node); + } + // Normal path. uint32_t lhs = exprRl(gz, scope, RL_REF_VAL, nd.lhs); advanceSourceCursorToMainToken(ag, gz, node); uint32_t saved_line = ag->source_line - gz->decl_line; uint32_t saved_col = ag->source_column; - uint32_t start_node = stree->extra_data.arr[nd.rhs]; - uint32_t end_node = stree->extra_data.arr[nd.rhs + 1]; ResultLoc usize_rl = { .tag = RL_COERCED_TY, .data = ZIR_REF_USIZE_TYPE, .src_node = 0, - .ctx = RI_CTX_NONE }; + .ctx = RI_CTX_NONE, + .components = NULL, + .components_len = 0 }; uint32_t start_ref = exprRl(gz, scope, usize_rl, start_node); uint32_t end_ref = exprRl(gz, scope, usize_rl, end_node); emitDbgStmt(gz, saved_line, saved_col); @@ -5678,20 +5831,58 @@ static uint32_t exprRl(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { } case AST_NODE_SLICE_SENTINEL: { // SliceSentinel[rhs]: { start, end, sentinel } - // (AstGen.zig:908-925). + // (AstGen.zig:882-937). const Ast* stree = ag->tree; + uint32_t start_node = stree->extra_data.arr[nd.rhs]; + uint32_t end_node = stree->extra_data.arr[nd.rhs + 1]; + uint32_t sentinel_node = stree->extra_data.arr[nd.rhs + 2]; + // slice_length optimization (AstGen.zig:887-906). + if (end_node != 0 && stree->nodes.tags[nd.lhs] == AST_NODE_SLICE_OPEN + && nodeIsTriviallyZero(stree, start_node)) { + AstData inner_nd = stree->nodes.datas[nd.lhs]; + uint32_t lhs = exprRl(gz, scope, RL_REF_VAL, inner_nd.lhs); + ResultLoc usize_rl = { .tag = RL_COERCED_TY, + .data = ZIR_REF_USIZE_TYPE, + .src_node = 0, + .ctx = RI_CTX_NONE, + .components = NULL, + .components_len = 0 }; + uint32_t start_ref = exprRl(gz, scope, usize_rl, inner_nd.rhs); + advanceSourceCursorToMainToken(ag, gz, node); + uint32_t saved_line = ag->source_line - gz->decl_line; + uint32_t saved_col = ag->source_column; + uint32_t len_ref = exprRl(gz, scope, usize_rl, end_node); + uint32_t sentinel_ref + = exprRl(gz, scope, RL_NONE_VAL, sentinel_node); + emitDbgStmt(gz, saved_line, saved_col); + ensureExtraCapacity(ag, 5); + uint32_t payload_index = ag->extra_len; + ag->extra[ag->extra_len++] = lhs; + ag->extra[ag->extra_len++] = start_ref; + ag->extra[ag->extra_len++] = len_ref; + ag->extra[ag->extra_len++] = sentinel_ref; + int32_t src_off = (int32_t)nd.lhs - (int32_t)gz->decl_node_index; + memcpy(&ag->extra[ag->extra_len], &src_off, sizeof(uint32_t)); + ag->extra_len++; + ZirInstData data; + data.pl_node.src_node + = (int32_t)node - (int32_t)gz->decl_node_index; + data.pl_node.payload_index = payload_index; + return rvalue( + gz, rl, addInstruction(gz, ZIR_INST_SLICE_LENGTH, data), node); + } + // Normal path. uint32_t lhs = exprRl(gz, scope, RL_REF_VAL, nd.lhs); advanceSourceCursorToMainToken(ag, gz, node); uint32_t saved_line = ag->source_line - gz->decl_line; uint32_t saved_col = ag->source_column; - uint32_t start_node = stree->extra_data.arr[nd.rhs]; - uint32_t end_node = stree->extra_data.arr[nd.rhs + 1]; - uint32_t sentinel_node = stree->extra_data.arr[nd.rhs + 2]; // start/end coerced to usize (AstGen.zig:911-912). ResultLoc usize_rl = { .tag = RL_COERCED_TY, .data = ZIR_REF_USIZE_TYPE, .src_node = 0, - .ctx = RI_CTX_NONE }; + .ctx = RI_CTX_NONE, + .components = NULL, + .components_len = 0 }; uint32_t start_ref = exprRl(gz, scope, usize_rl, start_node); uint32_t end_ref = (end_node != 0) ? exprRl(gz, scope, usize_rl, end_node) @@ -5702,7 +5893,9 @@ static uint32_t exprRl(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { ResultLoc sent_rl = { .tag = RL_COERCED_TY, .data = sentinel_ty, .src_node = 0, - .ctx = RI_CTX_NONE }; + .ctx = RI_CTX_NONE, + .components = NULL, + .components_len = 0 }; uint32_t sentinel_ref = exprRl(gz, scope, sent_rl, sentinel_node); emitDbgStmt(gz, saved_line, saved_col); ensureExtraCapacity(ag, 4); @@ -6123,6 +6316,10 @@ static uint32_t exprRl(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { case AST_NODE_ASSIGN_SHL_SAT: assignShiftSat(gz, scope, node); return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node); + // assign_destructure (AstGen.zig:669-674). + case AST_NODE_ASSIGN_DESTRUCTURE: + assignDestructure(gz, scope, node); + return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node); default: SET_ERROR(ag); return ZIR_REF_VOID_VALUE; @@ -6448,6 +6645,36 @@ static uint32_t arrayInitDotExpr( gz, ZIR_INST_VALIDATE_PTR_ARRAY_INIT, node, payload_index); return ZIR_REF_VOID_VALUE; } + case RL_DESTRUCTURE: { + // Destructure directly into result pointers (AstGen.zig:1552-1569). + if (elem_count != rl.components_len) { + SET_ERROR(ag); + return ZIR_REF_VOID_VALUE; + } + for (uint32_t i = 0; i < elem_count; i++) { + const DestructureComponent* comp = &rl.components[i]; + ResultLoc elem_rl; + switch (comp->tag) { + case DC_TYPED_PTR: + elem_rl = (ResultLoc) { .tag = RL_PTR, + .data = comp->inst, + .src_node = comp->src_node, + .ctx = RI_CTX_NONE }; + break; + case DC_INFERRED_PTR: + elem_rl = (ResultLoc) { .tag = RL_INFERRED_PTR, + .data = comp->inst, + .src_node = 0, + .ctx = RI_CTX_NONE }; + break; + case DC_DISCARD: + elem_rl = RL_DISCARD_VAL; + break; + } + exprRl(gz, scope, elem_rl, elements[i]); + } + return ZIR_REF_VOID_VALUE; + } } // Fallback: anon init + rvalue. @@ -8845,6 +9072,341 @@ static void assignStmt(GenZir* gz, Scope* scope, uint32_t infix_node) { } } +// --- assignDestructure (AstGen.zig:3456-3504) --- +// Handles destructure assignments where LHS is only lvalue expressions (no +// new var/const declarations). Called from exprRl and blockExprStmts. + +static void assignDestructure(GenZir* gz, Scope* scope, uint32_t node) { + emitDbgNode(gz, node); + AstGenCtx* ag = gz->astgen; + const Ast* tree = ag->tree; + + // Parse assign_destructure node: lhs=extra_index, rhs=value_expr. + AstData nd = tree->nodes.datas[node]; + uint32_t extra_start = nd.lhs; + uint32_t value_expr = nd.rhs; + uint32_t variable_count = tree->extra_data.arr[extra_start]; + const uint32_t* variables = tree->extra_data.arr + extra_start + 1; + + // Detect comptime token (AstGen.zig:3462-3464). + // Check if the first variable's first token (or the token before it) + // is keyword_comptime. + bool has_comptime = false; + if (variable_count > 0) { + uint32_t first_var = variables[0]; + AstNodeTag first_tag = tree->nodes.tags[first_var]; + uint32_t first_tok; + if (first_tag == AST_NODE_GLOBAL_VAR_DECL + || first_tag == AST_NODE_LOCAL_VAR_DECL + || first_tag == AST_NODE_ALIGNED_VAR_DECL + || first_tag == AST_NODE_SIMPLE_VAR_DECL) { + first_tok = firstToken(tree, first_var); + } else { + first_tok = firstToken(tree, first_var) - 1; + } + if (first_tok < tree->tokens.len + && tree->tokens.tags[first_tok] == TOKEN_KEYWORD_COMPTIME) { + has_comptime = true; + } + } + + if (has_comptime && gz->is_comptime) { + // Redundant comptime in already comptime scope (AstGen.zig:3466-3468). + SET_ERROR(ag); + return; + } + + // If comptime, wrap in sub-block (AstGen.zig:3471-3477). + GenZir gz_buf; + GenZir* inner_gz = gz; + if (has_comptime) { + gz_buf = makeSubBlock(gz, scope); + gz_buf.is_comptime = true; + inner_gz = &gz_buf; + } + + // Build rl_components (AstGen.zig:3479-3492). + DestructureComponent* rl_components + = malloc(variable_count * sizeof(DestructureComponent)); + if (!rl_components) + exit(1); + for (uint32_t i = 0; i < variable_count; i++) { + uint32_t variable_node = variables[i]; + // Check for `_` identifier (AstGen.zig:3481-3487). + if (tree->nodes.tags[variable_node] == AST_NODE_IDENTIFIER) { + uint32_t ident_tok = tree->nodes.main_tokens[variable_node]; + uint32_t tok_start = tree->tokens.starts[ident_tok]; + if (tree->source[tok_start] == '_' + && (tok_start + 1 >= tree->source_len + || !((tree->source[tok_start + 1] >= 'a' + && tree->source[tok_start + 1] <= 'z') + || (tree->source[tok_start + 1] >= 'A' + && tree->source[tok_start + 1] <= 'Z') + || tree->source[tok_start + 1] == '_' + || (tree->source[tok_start + 1] >= '0' + && tree->source[tok_start + 1] <= '9')))) { + rl_components[i].tag = DC_DISCARD; + rl_components[i].inst = 0; + rl_components[i].src_node = 0; + continue; + } + } + // lvalExpr: evaluate as ref (AstGen.zig:3488-3491). + rl_components[i].tag = DC_TYPED_PTR; + rl_components[i].inst + = exprRl(inner_gz, scope, RL_REF_VAL, variable_node); + rl_components[i].src_node = variable_node; + } + + // Build destructure result location and evaluate RHS + // (AstGen.zig:3494-3499). + ResultLoc ds_rl; + memset(&ds_rl, 0, sizeof(ds_rl)); + ds_rl.tag = RL_DESTRUCTURE; + ds_rl.src_node = node; + ds_rl.components = rl_components; + ds_rl.components_len = variable_count; + (void)exprRl(inner_gz, scope, ds_rl, value_expr); + + // If comptime, finish block_comptime (AstGen.zig:3501-3505). + if (has_comptime) { + uint32_t comptime_block_inst + = makeBlockInst(ag, ZIR_INST_BLOCK_COMPTIME, gz, node); + addBreak(inner_gz, ZIR_INST_BREAK_INLINE, comptime_block_inst, + ZIR_REF_VOID_VALUE, AST_NODE_OFFSET_NONE); + setBlockComptimeBody(ag, inner_gz, comptime_block_inst, + COMPTIME_REASON_COMPTIME_KEYWORD); + gzAppendInstruction(gz, comptime_block_inst); + gzUnstack(inner_gz); + } + free(rl_components); +} + +// --- assignDestructureMaybeDecls (AstGen.zig:3507-3729) --- +// Handles destructure assignments that may contain const/var declarations. +// Returns new scope containing any declared variables. + +static Scope* assignDestructureMaybeDecls(GenZir* gz, Scope* scope, + uint32_t node, ScopeLocalVal* val_scopes, uint32_t* val_idx, + ScopeLocalPtr* ptr_scopes, uint32_t* ptr_idx, uint32_t max_scopes) { + (void)val_scopes; + (void)val_idx; + emitDbgNode(gz, node); + AstGenCtx* ag = gz->astgen; + const Ast* tree = ag->tree; + + // Parse assign_destructure node. + AstData nd = tree->nodes.datas[node]; + uint32_t extra_start = nd.lhs; + uint32_t value_expr = nd.rhs; + uint32_t variable_count = tree->extra_data.arr[extra_start]; + const uint32_t* variables = tree->extra_data.arr + extra_start + 1; + + // Detect comptime token. + bool has_comptime = false; + if (variable_count > 0) { + uint32_t first_var = variables[0]; + AstNodeTag first_tag = tree->nodes.tags[first_var]; + uint32_t first_tok; + if (first_tag == AST_NODE_GLOBAL_VAR_DECL + || first_tag == AST_NODE_LOCAL_VAR_DECL + || first_tag == AST_NODE_ALIGNED_VAR_DECL + || first_tag == AST_NODE_SIMPLE_VAR_DECL) { + first_tok = firstToken(tree, first_var); + } else { + first_tok = firstToken(tree, first_var) - 1; + } + if (first_tok < tree->tokens.len + && tree->tokens.tags[first_tok] == TOKEN_KEYWORD_COMPTIME) { + has_comptime = true; + } + } + + bool is_comptime = has_comptime || gz->is_comptime; + bool value_is_comptime = tree->nodes.tags[value_expr] == AST_NODE_COMPTIME; + + if (has_comptime && gz->is_comptime) { + SET_ERROR(ag); // redundant comptime + } + + // First pass: build rl_components (AstGen.zig:3535-3620). + DestructureComponent* rl_components + = malloc(variable_count * sizeof(DestructureComponent)); + if (!rl_components) + exit(1); + bool any_lvalue_expr = false; + + for (uint32_t i = 0; i < variable_count; i++) { + uint32_t variable_node = variables[i]; + AstNodeTag vtag = tree->nodes.tags[variable_node]; + + // Check for `_` identifier (AstGen.zig:3537-3544). + if (vtag == AST_NODE_IDENTIFIER) { + uint32_t ident_tok = tree->nodes.main_tokens[variable_node]; + uint32_t tok_start = tree->tokens.starts[ident_tok]; + if (tree->source[tok_start] == '_' + && (tok_start + 1 >= tree->source_len + || !((tree->source[tok_start + 1] >= 'a' + && tree->source[tok_start + 1] <= 'z') + || (tree->source[tok_start + 1] >= 'A' + && tree->source[tok_start + 1] <= 'Z') + || tree->source[tok_start + 1] == '_' + || (tree->source[tok_start + 1] >= '0' + && tree->source[tok_start + 1] <= '9')))) { + + rl_components[i].tag = DC_DISCARD; + rl_components[i].inst = 0; + rl_components[i].src_node = 0; + continue; + } + } + + // var/const declarations (AstGen.zig:3545-3607). + if (vtag == AST_NODE_GLOBAL_VAR_DECL || vtag == AST_NODE_LOCAL_VAR_DECL + || vtag == AST_NODE_SIMPLE_VAR_DECL + || vtag == AST_NODE_ALIGNED_VAR_DECL) { + AstData vnd = tree->nodes.datas[variable_node]; + uint32_t mut_token = tree->nodes.main_tokens[variable_node]; + bool var_is_const + = (tree->tokens.tags[mut_token] == TOKEN_KEYWORD_CONST); + bool this_comptime + = is_comptime || (var_is_const && value_is_comptime); + + uint32_t type_node = vnd.lhs; + // AstGen.zig:3576-3607: typed vs inferred alloc. + if (type_node != 0) { + uint32_t type_inst = typeExpr(gz, scope, type_node); + ZirInstTag alloc_tag; + if (var_is_const) + alloc_tag = ZIR_INST_ALLOC; + else if (this_comptime) + alloc_tag = ZIR_INST_ALLOC_COMPTIME_MUT; + else + alloc_tag = ZIR_INST_ALLOC_MUT; + uint32_t ptr = addUnNode(gz, alloc_tag, type_inst, node); + rl_components[i].tag = DC_TYPED_PTR; + rl_components[i].inst = ptr; + rl_components[i].src_node = 0; + } else { + // Inferred alloc. + ZirInstTag alloc_tag; + if (var_is_const) { + alloc_tag = this_comptime + ? ZIR_INST_ALLOC_INFERRED_COMPTIME + : ZIR_INST_ALLOC_INFERRED; + } else { + alloc_tag = this_comptime + ? ZIR_INST_ALLOC_INFERRED_COMPTIME_MUT + : ZIR_INST_ALLOC_INFERRED_MUT; + } + uint32_t ptr = addNode(gz, alloc_tag, node); + rl_components[i].tag = DC_INFERRED_PTR; + rl_components[i].inst = ptr; + rl_components[i].src_node = 0; + } + continue; + } + // Lvalue expression (AstGen.zig:3609-3618). + any_lvalue_expr = true; + rl_components[i].tag = DC_TYPED_PTR; + rl_components[i].inst = 0; // will be filled in second pass + rl_components[i].src_node = variable_node; + } + + // If comptime, wrap in sub-block (AstGen.zig:3627-3632). + GenZir gz_buf; + GenZir* inner_gz = gz; + if (has_comptime) { + gz_buf = makeSubBlock(gz, scope); + gz_buf.is_comptime = true; + inner_gz = &gz_buf; + } + + // Second pass for lvalue expressions (AstGen.zig:3634-3642). + if (any_lvalue_expr) { + for (uint32_t i = 0; i < variable_count; i++) { + if (rl_components[i].tag != DC_TYPED_PTR) + continue; + AstNodeTag vtag = tree->nodes.tags[variables[i]]; + if (vtag == AST_NODE_GLOBAL_VAR_DECL + || vtag == AST_NODE_LOCAL_VAR_DECL + || vtag == AST_NODE_SIMPLE_VAR_DECL + || vtag == AST_NODE_ALIGNED_VAR_DECL) + continue; + rl_components[i].inst + = exprRl(inner_gz, scope, RL_REF_VAL, variables[i]); + } + } + + // Evaluate RHS with destructure RL (AstGen.zig:3647-3652). + ResultLoc ds_rl; + memset(&ds_rl, 0, sizeof(ds_rl)); + ds_rl.tag = RL_DESTRUCTURE; + ds_rl.src_node = node; + ds_rl.components = rl_components; + ds_rl.components_len = variable_count; + (void)exprRl(inner_gz, scope, ds_rl, value_expr); + + // If comptime, finish block_comptime (AstGen.zig:3654-3660). + if (has_comptime) { + uint32_t comptime_block_inst + = makeBlockInst(ag, ZIR_INST_BLOCK_COMPTIME, gz, node); + addBreak(inner_gz, ZIR_INST_BREAK_INLINE, comptime_block_inst, + ZIR_REF_VOID_VALUE, AST_NODE_OFFSET_NONE); + setBlockComptimeBody(ag, inner_gz, comptime_block_inst, + COMPTIME_REASON_COMPTIME_KEYWORD); + gzAppendInstruction(gz, comptime_block_inst); + gzUnstack(inner_gz); + } + + // Third pass: create scopes for declared variables (AstGen.zig:3664-3729). + Scope* cur_scope = scope; + for (uint32_t i = 0; i < variable_count; i++) { + uint32_t variable_node = variables[i]; + AstNodeTag vtag = tree->nodes.tags[variable_node]; + if (vtag != AST_NODE_LOCAL_VAR_DECL && vtag != AST_NODE_SIMPLE_VAR_DECL + && vtag != AST_NODE_ALIGNED_VAR_DECL) + continue; + + uint32_t mut_token = tree->nodes.main_tokens[variable_node]; + bool var_is_const + = (tree->tokens.tags[mut_token] == TOKEN_KEYWORD_CONST); + uint32_t raw_ptr = rl_components[i].inst; + bool resolve_inferred = (rl_components[i].tag == DC_INFERRED_PTR); + + // Resolve inferred alloc or make ptr const (AstGen.zig:3694-3700). + uint32_t final_ptr; + if (resolve_inferred) + final_ptr = addUnNode( + gz, ZIR_INST_RESOLVE_INFERRED_ALLOC, raw_ptr, variable_node); + else if (var_is_const) + final_ptr = addUnNode(gz, ZIR_INST_MAKE_PTR_CONST, raw_ptr, node); + else + final_ptr = raw_ptr; + + // Create dbg_var_ptr (AstGen.zig:3710). + uint32_t name_token = mut_token + 1; + uint32_t ident_name = identAsString(ag, name_token); + addDbgVar(gz, ZIR_INST_DBG_VAR_PTR, ident_name, final_ptr); + + // Create scope (AstGen.zig:3712-3722). + if (*ptr_idx < max_scopes) { + ptr_scopes[*ptr_idx] = (ScopeLocalPtr) { + .base = { .tag = SCOPE_LOCAL_PTR }, + .parent = cur_scope, + .name = ident_name, + .ptr = final_ptr, + }; + cur_scope = &ptr_scopes[*ptr_idx].base; + (*ptr_idx)++; + } else { + SET_ERROR(ag); + } + } + free(rl_components); + return cur_scope; +} + // --- assignOp (AstGen.zig:3731) --- // Handles compound assignment operators (+=, -=, *=, etc.). @@ -9289,6 +9851,8 @@ static void varDecl(GenZir* gz, Scope* scope, uint32_t node, // Evaluate init with RL pointing to alloc (AstGen.zig:3313-3316). ResultLoc init_rl; + init_rl.components = NULL; + init_rl.components_len = 0; if (type_node != 0) { init_rl.tag = RL_PTR; init_rl.data = var_ptr; @@ -9361,6 +9925,8 @@ static void varDecl(GenZir* gz, Scope* scope, uint32_t node, // Evaluate init with RL pointing to alloc (AstGen.zig:3395-3402). ResultLoc var_init_rl; + var_init_rl.components = NULL; + var_init_rl.components_len = 0; if (type_node != 0) { var_init_rl.tag = RL_PTR; var_init_rl.data = alloc_ref; @@ -9645,6 +10211,12 @@ static void blockExprStmts(GenZir* gz, Scope* scope, case AST_NODE_ASSIGN: assignStmt(gz, cur_scope, inner_node); break; + // assign_destructure (AstGen.zig:2578). + case AST_NODE_ASSIGN_DESTRUCTURE: + cur_scope + = assignDestructureMaybeDecls(gz, cur_scope, inner_node, + val_scopes, &val_idx, ptr_scopes, &ptr_idx, 128); + break; // Shift assignment operators (AstGen.zig:2585-2586). case AST_NODE_ASSIGN_SHL: assignShift(gz, cur_scope, inner_node, ZIR_INST_SHL); diff --git a/stage0/astgen_test.zig b/stage0/astgen_test.zig index 7c68ae82f8..d8ca263c1e 100644 --- a/stage0/astgen_test.zig +++ b/stage0/astgen_test.zig @@ -853,13 +853,13 @@ test "astgen: corpus astgen_test.zig" { } test "astgen: corpus array_list.zig" { - if (true) return error.SkipZigTest; // TODO: missing assign_destructure handler + if (true) return error.SkipZigTest; // TODO: +2 ALLOC_MUT / -2 EXTENDED tag mismatch at [6639] const gpa = std.testing.allocator; try corpusCheck(gpa, @embedFile("../lib/std/array_list.zig")); } test "astgen: corpus multi_array_list.zig" { - if (true) return error.SkipZigTest; // TODO: identifier resolution across namespace scopes + if (true) return error.SkipZigTest; // TODO: parser bug - C parser produces nodes_len=1 const gpa = std.testing.allocator; try corpusCheck(gpa, @embedFile("../lib/std/multi_array_list.zig")); }