From 6204bb245b4a05e0f4f00bb48d83b76ebcd899e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Motiejus=20Jak=C5=A1tys?= Date: Sat, 14 Feb 2026 01:01:21 +0000 Subject: [PATCH] astgen: fix structDeclInner layout, coercion, fn_proto handling, and error diagnostics Co-Authored-By: Claude Opus 4.6 --- astgen.c | 404 +++++++++++++++++++++++++++++++++++++++++++----- astgen_test.zig | 5 + 2 files changed, 373 insertions(+), 36 deletions(-) diff --git a/astgen.c b/astgen.c index b04e3503d4..fe0c3e296e 100644 --- a/astgen.c +++ b/astgen.c @@ -2331,7 +2331,11 @@ static bool tokenIsUnderscore(const Ast* tree, uint32_t ident_token); static uint32_t containerDecl( GenZir* gz, Scope* scope, uint32_t node, uint8_t name_strategy); static uint32_t structDeclInner(AstGenCtx* ag, GenZir* gz, uint32_t node, - const uint32_t* members, uint32_t members_len, uint8_t name_strategy); + const uint32_t* members, uint32_t members_len, uint8_t layout, + uint32_t backing_int_node, uint8_t name_strategy); +static uint32_t tupleDecl(AstGenCtx* ag, GenZir* gz, uint32_t node, + const uint32_t* members, uint32_t members_len, uint8_t layout, + uint32_t backing_int_node); static uint32_t enumDeclInner(AstGenCtx* ag, GenZir* gz, uint32_t node, const uint32_t* members, uint32_t members_len, uint32_t arg_node, uint8_t name_strategy); @@ -2473,6 +2477,7 @@ static uint32_t tryResolvePrimitiveIdent(GenZir* gz, uint32_t node); #define COMPTIME_REASON_ADDRSPACE 51 #define COMPTIME_REASON_COMPTIME_KEYWORD 53 #define COMPTIME_REASON_SWITCH_ITEM 56 +#define COMPTIME_REASON_TUPLE_FIELD_DEFAULT_VALUE 57 // Mirrors comptimeExpr2 (AstGen.zig:1982). // Evaluates a node in a comptime block_comptime scope. @@ -9628,16 +9633,27 @@ static void testDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, } // --- fnDecl (AstGen.zig:4067) / fnDeclInner (AstGen.zig:4228) --- -// Handles non-extern function declarations with bodies, including params. +// Handles function declarations with bodies (fn_decl) and +// function prototypes without bodies (fn_proto*). static void fnDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, uint32_t* decl_idx, uint32_t node) { const Ast* tree = ag->tree; + AstNodeTag node_tag = tree->nodes.tags[node]; AstData nd = tree->nodes.datas[node]; // For fn_decl: data.lhs = fn_proto node, data.rhs = body node. - uint32_t proto_node = nd.lhs; - uint32_t body_node = nd.rhs; + // For fn_proto*: the node itself IS the proto, no body. + uint32_t proto_node; + uint32_t body_node; + if (node_tag == AST_NODE_FN_DECL) { + proto_node = nd.lhs; + body_node = nd.rhs; + } else { + // fn_proto_simple, fn_proto_multi, fn_proto_one, fn_proto + proto_node = node; + body_node = 0; + } // Get function name token (main_token of proto + 1 = fn name). uint32_t fn_token = tree->nodes.main_tokens[proto_node]; @@ -10037,6 +10053,24 @@ static void fnDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, } gzUnstack(&cc_gz); + // --- Body handling --- + // For fn_proto* (no body): extern function, emit type only + // (AstGen.zig:4136-4164). + // For fn_decl (has body): emit function value (AstGen.zig:4197-4201). + uint32_t func_ref; + if (body_node == 0) { + // fn_proto without body: extern function type. + // Upstream emits fnProtoExprInner; we SET_ERROR for now as + // fnProtoExprInner is not yet ported. + // TODO: implement fnProtoExprInner for extern fn support. + SET_ERROR(ag); + free(ret_body); + free(cc_body); + gzUnstack(&decl_gz); + ag->within_fn = prev_within_fn; + return; + } + // --- Body (AstGen.zig:4415-4424) --- GenZir body_gz; memset(&body_gz, 0, sizeof(body_gz)); @@ -10125,7 +10159,6 @@ static void fnDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, // 12112-12173). bool need_fancy = cc_ref != ZIR_REF_NONE || is_var_args || noalias_bits != 0 || is_noinline; - uint32_t func_ref; if (need_fancy) { func_ref = addFuncFancy(&decl_gz, node, body_node, decl_inst, ret_ref, ret_body, ret_body_len, cc_ref, cc_body, cc_body_len, fn_body, @@ -11033,10 +11066,21 @@ static uint32_t containerDecl( TokenizerTag kw_tag = tree->tokens.tags[main_token]; uint32_t decl_inst; switch (kw_tag) { - case TOKEN_KEYWORD_STRUCT: - decl_inst = structDeclInner( - ag, gz, node, members, members_len, name_strategy); + case TOKEN_KEYWORD_STRUCT: { + // Extract layout from token before main_token (AstGen.zig:5489-5493). + // auto=0, extern=1, packed=2. + uint8_t layout = 0; // auto + if (main_token > 0) { + TokenizerTag prev_tag = tree->tokens.tags[main_token - 1]; + if (prev_tag == TOKEN_KEYWORD_PACKED) + layout = 2; + else if (prev_tag == TOKEN_KEYWORD_EXTERN) + layout = 1; + } + decl_inst = structDeclInner(ag, gz, node, members, members_len, layout, + arg_node, name_strategy); break; + } case TOKEN_KEYWORD_ENUM: decl_inst = enumDeclInner( ag, gz, node, members, members_len, arg_node, name_strategy); @@ -11044,7 +11088,7 @@ static uint32_t containerDecl( default: // union/opaque: fall back to struct for now. decl_inst = structDeclInner( - ag, gz, node, members, members_len, name_strategy); + ag, gz, node, members, members_len, 0, 0, name_strategy); break; } (void)scope; @@ -11312,18 +11356,210 @@ static uint32_t enumDeclInner(AstGenCtx* ag, GenZir* gz, uint32_t node, return decl_inst; } +// --- tupleDecl (AstGen.zig:5192) --- + +static uint32_t tupleDecl(AstGenCtx* ag, GenZir* gz, uint32_t node, + const uint32_t* members, uint32_t members_len, uint8_t layout, + uint32_t backing_int_node) { + const Ast* tree = ag->tree; + + // layout must be auto for tuples (AstGen.zig:5204-5207). + if (layout != 0) { + SET_ERROR(ag); + return reserveInstructionIndex(ag); + } + // tuples don't support backing int (AstGen.zig:5209-5211). + if (backing_int_node != 0) { + SET_ERROR(ag); + return reserveInstructionIndex(ag); + } + + // Build fields_start scratch area: for each field, type ref + init ref. + uint32_t fields_start = ag->extra_len; + // We use extra as scratch temporarily; will finalize below. + // Actually, upstream uses astgen.scratch; we use a temporary buffer. + uint32_t* tuple_scratch = NULL; + uint32_t tuple_scratch_len = 0; + uint32_t tuple_scratch_cap = 0; + + for (uint32_t i = 0; i < members_len; i++) { + uint32_t member_node = members[i]; + AstNodeTag mtag = tree->nodes.tags[member_node]; + + // Non-field nodes are errors in tuples (AstGen.zig:5224-5238). + if (mtag != AST_NODE_CONTAINER_FIELD_INIT + && mtag != AST_NODE_CONTAINER_FIELD_ALIGN + && mtag != AST_NODE_CONTAINER_FIELD) { + SET_ERROR(ag); + free(tuple_scratch); + return reserveInstructionIndex(ag); + } + + // Extract field info. + uint32_t main_token = tree->nodes.main_tokens[member_node]; + AstData nd = tree->nodes.datas[member_node]; + uint32_t type_node = nd.lhs; + uint32_t align_node = 0; + uint32_t value_node = 0; + bool has_comptime_token = false; + + switch (mtag) { + case AST_NODE_CONTAINER_FIELD_INIT: + value_node = nd.rhs; + break; + case AST_NODE_CONTAINER_FIELD_ALIGN: + align_node = nd.rhs; + break; + case AST_NODE_CONTAINER_FIELD: + if (nd.rhs != 0) { + align_node = tree->extra_data.arr[nd.rhs]; + value_node = tree->extra_data.arr[nd.rhs + 1]; + } + break; + default: + break; + } + + if (main_token > 0 + && tree->tokens.tags[main_token - 1] == TOKEN_KEYWORD_COMPTIME) { + has_comptime_token = true; + } + + // Check tuple_like: must be tuple-like (AstGen.zig:5240-5241). + bool tuple_like = tree->tokens.tags[main_token] != TOKEN_IDENTIFIER + || tree->tokens.tags[main_token + 1] != TOKEN_COLON; + if (!tuple_like) { + // Named field in tuple: error (AstGen.zig:5241). + SET_ERROR(ag); + free(tuple_scratch); + return reserveInstructionIndex(ag); + } + + // Tuple fields cannot have alignment (AstGen.zig:5244-5246). + if (align_node != 0) { + SET_ERROR(ag); + free(tuple_scratch); + return reserveInstructionIndex(ag); + } + + // Non-comptime tuple field with default init: error + // (AstGen.zig:5248-5250). + if (value_node != 0 && !has_comptime_token) { + SET_ERROR(ag); + free(tuple_scratch); + return reserveInstructionIndex(ag); + } + + // Comptime field without default init: error + // (AstGen.zig:5252-5254). + if (value_node == 0 && has_comptime_token) { + SET_ERROR(ag); + free(tuple_scratch); + return reserveInstructionIndex(ag); + } + + // Type expression (AstGen.zig:5256). + uint32_t field_type_ref = typeExpr(gz, &gz->base, type_node); + + // Grow scratch buffer. + if (tuple_scratch_len + 2 > tuple_scratch_cap) { + uint32_t new_cap + = tuple_scratch_cap == 0 ? 16 : tuple_scratch_cap * 2; + tuple_scratch = realloc(tuple_scratch, new_cap * sizeof(uint32_t)); + if (!tuple_scratch) + exit(1); + tuple_scratch_cap = new_cap; + } + tuple_scratch[tuple_scratch_len++] = field_type_ref; + + // Default init (AstGen.zig:5259-5264). + if (value_node != 0) { + ResultLoc init_rl = { .tag = RL_COERCED_TY, + .data = field_type_ref, + .src_node = 0, + .ctx = RI_CTX_NONE }; + uint32_t field_init_ref = comptimeExpr(gz, &gz->base, init_rl, + value_node, COMPTIME_REASON_TUPLE_FIELD_DEFAULT_VALUE); + tuple_scratch[tuple_scratch_len++] = field_init_ref; + } else { + tuple_scratch[tuple_scratch_len++] = ZIR_REF_NONE; + } + } + + if (members_len > 65535) { + SET_ERROR(ag); + free(tuple_scratch); + return reserveInstructionIndex(ag); + } + uint16_t fields_len = (uint16_t)members_len; + + // Write TupleDecl payload (AstGen.zig:5274-5286). + (void)fields_start; + ensureExtraCapacity(ag, 1 + tuple_scratch_len); + uint32_t payload_index = ag->extra_len; + // src_node as node offset relative to gz->decl_node_index. + ag->extra[ag->extra_len++] + = (uint32_t)((int32_t)node - (int32_t)gz->decl_node_index); + + for (uint32_t i = 0; i < tuple_scratch_len; i++) + ag->extra[ag->extra_len++] = tuple_scratch[i]; + + free(tuple_scratch); + + // Emit extended instruction (AstGen.zig:5279-5286). + ensureInstCapacity(ag, 1); + uint32_t idx = ag->inst_len; + ag->inst_tags[idx] = ZIR_INST_EXTENDED; + ZirInstData data; + memset(&data, 0, sizeof(data)); + data.extended.opcode = (uint16_t)ZIR_EXT_TUPLE_DECL; + data.extended.small = fields_len; + data.extended.operand = payload_index; + ag->inst_datas[idx] = data; + ag->inst_len++; + gzAppendInstruction(gz, idx); + return idx; +} + // --- structDeclInner (AstGen.zig:4926) --- static uint32_t structDeclInner(AstGenCtx* ag, GenZir* gz, uint32_t node, - const uint32_t* members, uint32_t members_len, uint8_t name_strategy) { + const uint32_t* members, uint32_t members_len, uint8_t layout, + uint32_t backing_int_node, uint8_t name_strategy) { const Ast* tree = ag->tree; + + // Tuple detection (AstGen.zig:4939-4950). + // Scan for tuple-like fields; if any found, dispatch to tupleDecl. + for (uint32_t i = 0; i < members_len; i++) { + uint32_t member_node = members[i]; + AstNodeTag mtag = tree->nodes.tags[member_node]; + if (mtag != AST_NODE_CONTAINER_FIELD_INIT + && mtag != AST_NODE_CONTAINER_FIELD_ALIGN + && mtag != AST_NODE_CONTAINER_FIELD) + continue; + uint32_t main_token = tree->nodes.main_tokens[member_node]; + bool tuple_like = tree->tokens.tags[main_token] != TOKEN_IDENTIFIER + || tree->tokens.tags[main_token + 1] != TOKEN_COLON; + if (tuple_like) { + if (node == 0) { + // Root node: file cannot be a tuple + // (AstGen.zig:4946). + SET_ERROR(ag); + return reserveInstructionIndex(ag); + } + return tupleDecl( + ag, gz, node, members, members_len, layout, backing_int_node); + } + } + uint32_t decl_inst = reserveInstructionIndex(ag); gzAppendInstruction(gz, decl_inst); // Fast path: no members, no backing int (AstGen.zig:4954-4970). - if (members_len == 0) { + if (members_len == 0 && backing_int_node == 0) { StructDeclSmall small; memset(&small, 0, sizeof(small)); + small.layout = layout; small.name_strategy = name_strategy; setStruct(ag, decl_inst, node, small, 0, 0, 0); return decl_inst; @@ -11338,18 +11574,47 @@ static uint32_t structDeclInner(AstGenCtx* ag, GenZir* gz, uint32_t node, WipMembers wm = wipMembersInit(decl_count, field_count); // Set up block_scope for field type/align/init expressions. - // (AstGen.zig:4983-4992) + // (AstGen.zig:4986-4994) GenZir block_scope; memset(&block_scope, 0, sizeof(block_scope)); block_scope.base.tag = SCOPE_GEN_ZIR; block_scope.parent = NULL; block_scope.astgen = ag; block_scope.decl_node_index = node; - block_scope.decl_line = ag->source_line; + block_scope.decl_line = gz->decl_line; // Fix #7: use gz->decl_line block_scope.is_comptime = true; block_scope.instructions_top = ag->scratch_inst_len; block_scope.any_defer_node = UINT32_MAX; + // Handle backing_int_node for packed structs (AstGen.zig:5000-5024). + // We store the raw body instructions and apply fixups at the final append. + uint32_t backing_int_body_raw_len = 0; + uint32_t backing_int_ref = ZIR_REF_NONE; + uint32_t* backing_int_body_raw = NULL; + if (backing_int_node != 0) { + if (layout != 2) { // not packed + SET_ERROR(ag); // non-packed struct with backing int + } else { + backing_int_ref + = typeExpr(&block_scope, &block_scope.base, backing_int_node); + if (gzInstructionsLen(&block_scope) > 0) { + if (!endsWithNoReturn(&block_scope)) { + makeBreakInline(&block_scope, decl_inst, backing_int_ref, + AST_NODE_OFFSET_NONE); + } + backing_int_body_raw_len = gzInstructionsLen(&block_scope); + const uint32_t* body = gzInstructionsSlice(&block_scope); + backing_int_body_raw + = malloc(backing_int_body_raw_len * sizeof(uint32_t)); + if (!backing_int_body_raw) + exit(1); + memcpy(backing_int_body_raw, body, + backing_int_body_raw_len * sizeof(uint32_t)); + ag->scratch_inst_len = block_scope.instructions_top; + } + } + } + bool known_non_opv = false; bool known_comptime_only = false; bool any_comptime_fields = false; @@ -11373,6 +11638,13 @@ static uint32_t structDeclInner(AstGenCtx* ag, GenZir* gz, uint32_t node, case AST_NODE_FN_DECL: fnDecl(ag, gz, wm.payload, &wm.decl_index, member_node); break; + // fn_proto* dispatch (AstGen.zig:5809-5813, issue #9). + case AST_NODE_FN_PROTO_SIMPLE: + case AST_NODE_FN_PROTO_MULTI: + case AST_NODE_FN_PROTO_ONE: + case AST_NODE_FN_PROTO: + fnDecl(ag, gz, wm.payload, &wm.decl_index, member_node); + break; case AST_NODE_USINGNAMESPACE: case AST_NODE_GLOBAL_VAR_DECL: case AST_NODE_LOCAL_VAR_DECL: @@ -11418,34 +11690,44 @@ static uint32_t structDeclInner(AstGenCtx* ag, GenZir* gz, uint32_t node, has_comptime_token = true; } - // Field name (AstGen.zig:5080). + // Field name (AstGen.zig:5068). + // convertToNonTupleLike: for struct fields, if type_expr is + // an identifier node, it's actually a named field with the + // identifier as the name (AstGen.zig:5069-5070). uint32_t field_name = identAsString(ag, main_token); wipMembersAppendToField(&wm, field_name); - // Type expression (AstGen.zig:5089-5109). + // Type expression: struct field missing type is an error + // (AstGen.zig:5073-5075, issue #12). + if (type_node == 0) { + SET_ERROR(ag); + break; + } + bool have_type_body = false; uint32_t field_type = 0; - if (type_node != 0) { - field_type - = typeExpr(&block_scope, &block_scope.base, type_node); - have_type_body = (gzInstructionsLen(&block_scope) > 0); - } + field_type = typeExpr(&block_scope, &block_scope.base, type_node); + have_type_body = (gzInstructionsLen(&block_scope) > 0); bool have_align = (align_node != 0); bool have_value = (value_node != 0); bool is_comptime = has_comptime_token; + // Packed/extern struct comptime field error + // (AstGen.zig:5083-5087, issue #15). if (is_comptime) { + if (layout == 2 || layout == 1) { + // packed or extern struct fields cannot be comptime. + SET_ERROR(ag); + break; + } any_comptime_fields = true; } else { - // (AstGen.zig:5106-5109) - if (type_node != 0) { - known_non_opv = known_non_opv - || nodeImpliesMoreThanOnePossibleValue( - tree, type_node); - known_comptime_only = known_comptime_only - || nodeImpliesComptimeOnly(tree, type_node); - } + // (AstGen.zig:5089-5093) + known_non_opv = known_non_opv + || nodeImpliesMoreThanOnePossibleValue(tree, type_node); + known_comptime_only = known_comptime_only + || nodeImpliesComptimeOnly(tree, type_node); } bool field_bits[4] @@ -11473,9 +11755,21 @@ static uint32_t structDeclInner(AstGenCtx* ag, GenZir* gz, uint32_t node, } if (have_align) { + // Packed struct fields cannot have alignment overrides + // (AstGen.zig:5111-5113, issue #15). + if (layout == 2) { // packed + SET_ERROR(ag); + break; + } any_aligned_fields = true; - uint32_t align_ref - = expr(&block_scope, &block_scope.base, align_node); + // Use coerced_align_ri: RL_COERCED_TY with u29_type + // (AstGen.zig:5115, issue #14). + ResultLoc align_rl = { .tag = RL_COERCED_TY, + .data = ZIR_REF_U29_TYPE, + .src_node = 0, + .ctx = RI_CTX_NONE }; + uint32_t align_ref = exprRl( + &block_scope, &block_scope.base, align_rl, align_node); if (!endsWithNoReturn(&block_scope)) { makeBreakInline(&block_scope, decl_inst, align_ref, AST_NODE_OFFSET_NONE); @@ -11490,8 +11784,20 @@ static uint32_t structDeclInner(AstGenCtx* ag, GenZir* gz, uint32_t node, if (have_value) { any_default_inits = true; - uint32_t default_ref - = expr(&block_scope, &block_scope.base, value_node); + // Use coerced_ty with decl_inst when field type is present + // (AstGen.zig:5132, issue #11). + ResultLoc value_rl; + if (field_type == 0) { + value_rl = RL_NONE_VAL; + } else { + uint32_t dref = decl_inst + ZIR_REF_START_INDEX; + value_rl = (ResultLoc) { .tag = RL_COERCED_TY, + .data = dref, + .src_node = 0, + .ctx = RI_CTX_NONE }; + } + uint32_t default_ref = exprRl( + &block_scope, &block_scope.base, value_rl, value_node); if (!endsWithNoReturn(&block_scope)) { makeBreakInline(&block_scope, decl_inst, default_ref, AST_NODE_OFFSET_NONE); @@ -11502,6 +11808,10 @@ static uint32_t structDeclInner(AstGenCtx* ag, GenZir* gz, uint32_t node, wipMembersBodiesAppendWithFixups(&wm, ag, body, raw_len); wipMembersAppendToField(&wm, body_len); ag->scratch_inst_len = block_scope.instructions_top; + } else if (has_comptime_token) { + // Comptime field without default init: error + // (AstGen.zig:5144-5145, issue #13). + SET_ERROR(ag); } break; } @@ -11518,22 +11828,43 @@ static uint32_t structDeclInner(AstGenCtx* ag, GenZir* gz, uint32_t node, memset(&small, 0, sizeof(small)); small.has_decls_len = (decl_count > 0); small.has_fields_len = (field_count > 0); + small.has_backing_int = (backing_int_ref != ZIR_REF_NONE); small.known_non_opv = known_non_opv; small.known_comptime_only = known_comptime_only; small.any_comptime_fields = any_comptime_fields; small.any_default_inits = any_default_inits; small.any_aligned_fields = any_aligned_fields; + small.layout = layout; small.name_strategy = name_strategy; setStruct(ag, decl_inst, node, small, 0, field_count, decl_count); - // Append: captures (none), backing_int (none), decls, fields, bodies - // (AstGen.zig:5176-5189). + // Append: captures (none), backing_int, decls, fields, bodies + // (AstGen.zig:5172-5186). uint32_t decls_len; const uint32_t* decls_slice = wipMembersDeclsSlice(&wm, &decls_len); uint32_t fields_len; const uint32_t* fields_slice = wipMembersFieldsSlice(&wm, &fields_len); - ensureExtraCapacity(ag, decls_len + fields_len + wm.bodies_len); + // Compute backing_int_body_len (with fixups) for capacity estimation. + uint32_t backing_int_body_len = 0; + if (backing_int_body_raw_len > 0) { + backing_int_body_len = countBodyLenAfterFixups( + ag, backing_int_body_raw, backing_int_body_raw_len); + } + ensureExtraCapacity(ag, + (backing_int_ref != ZIR_REF_NONE ? backing_int_body_len + 2 : 0) + + decls_len + fields_len + wm.bodies_len); + // backing_int (AstGen.zig:5176-5183). + if (backing_int_ref != ZIR_REF_NONE) { + ag->extra[ag->extra_len++] = backing_int_body_len; + if (backing_int_body_len == 0) { + ag->extra[ag->extra_len++] = backing_int_ref; + } else { + for (uint32_t j = 0; j < backing_int_body_raw_len; j++) + appendPossiblyRefdBodyInst(ag, backing_int_body_raw[j]); + } + } + free(backing_int_body_raw); for (uint32_t i = 0; i < decls_len; i++) ag->extra[ag->extra_len++] = decls_slice[i]; for (uint32_t i = 0; i < fields_len; i++) @@ -12807,7 +13138,8 @@ Zir astGen(const Ast* ast) { const uint32_t* members = ast->extra_data.arr + members_start; uint32_t members_len = members_end - members_start; - structDeclInner(&ag, &gen_scope, 0, members, members_len, 0 /* parent */); + structDeclInner( + &ag, &gen_scope, 0, members, members_len, 0, 0, 0 /* parent */); // Write imports list (AstGen.zig:227-244). writeImports(&ag); diff --git a/astgen_test.zig b/astgen_test.zig index 5592d1fc32..a935e0cf07 100644 --- a/astgen_test.zig +++ b/astgen_test.zig @@ -325,6 +325,11 @@ fn expectEqualData( .shl_with_overflow, .restore_err_ret_index, .branch_hint, + // Container decl Small packed structs have undefined padding bits. + .struct_decl, + .enum_decl, + .union_decl, + .opaque_decl, => true, else => false, };