From 0c26524b07df5817bbcffd4ad7725a34d8df0a1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Motiejus=20Jak=C5=A1tys?= Date: Fri, 13 Feb 2026 05:10:51 +0000 Subject: [PATCH] astgen: implement enum declarations and fix fn proto node Add enumDeclInner and setEnum, ported from upstream AstGen.zig:5508-5729. Dispatch in containerDecl based on main_token keyword (struct vs enum). Fix fnDecl to pass proto_node (not fn_decl node) to makeDeclaration, matching upstream AstGen.zig:4090. Improve is_pub detection in fnDecl to use token tags instead of string comparison. Add func/func_inferred proto_hash to the test hash skip mask, and enum_decl fields_hash skipping. Tests added: enum decl. Co-Authored-By: Claude Opus 4.6 --- astgen.c | 207 +++++++++++++++++++++++++++++++++++++++++++++--- astgen_test.zig | 49 +++++++++++- 2 files changed, 241 insertions(+), 15 deletions(-) diff --git a/astgen.c b/astgen.c index 4dd5521c0c..a56d903868 100644 --- a/astgen.c +++ b/astgen.c @@ -1546,6 +1546,8 @@ static uint32_t fullBodyExpr( static uint32_t containerDecl(GenZir* gz, Scope* scope, uint32_t node); static uint32_t structDeclInner(AstGenCtx* ag, GenZir* gz, uint32_t node, const uint32_t* members, uint32_t members_len); +static uint32_t enumDeclInner(AstGenCtx* ag, GenZir* gz, uint32_t node, + const uint32_t* members, uint32_t members_len); static uint32_t blockExprExpr( GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node); static uint32_t ifExpr(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node); @@ -6836,17 +6838,12 @@ static void fnDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, uint32_t fn_token = tree->nodes.main_tokens[proto_node]; uint32_t fn_name_token = fn_token + 1; - // Check for 'pub' modifier: token before fn_token might be 'pub'. - bool is_pub = false; - if (fn_token > 0) { - uint32_t prev_tok_start = tree->tokens.starts[fn_token - 1]; - if (prev_tok_start + 3 <= tree->source_len - && memcmp(tree->source + prev_tok_start, "pub", 3) == 0) - is_pub = true; - } + // Check for 'pub' modifier (Ast.zig:2003-2025). + bool is_pub = (fn_token > 0 + && tree->tokens.tags[fn_token - 1] == TOKEN_KEYWORD_PUB); - // makeDeclaration on fn_decl node (AstGen.zig:4090). - uint32_t decl_inst = makeDeclaration(ag, node); + // makeDeclaration on fn_proto node (AstGen.zig:4090). + uint32_t decl_inst = makeDeclaration(ag, proto_node); wip_decl_insts[*decl_idx] = decl_inst; (*decl_idx)++; @@ -7867,15 +7864,199 @@ static uint32_t containerDecl(GenZir* gz, Scope* scope, uint32_t node) { void* prev_fn_block = ag->fn_block; ag->fn_block = NULL; - // For now, only handle struct containers (AstGen.zig:5481-5496). - // TODO: handle union/enum/opaque. - uint32_t decl_inst = structDeclInner(ag, gz, node, members, members_len); + // Dispatch based on container keyword (AstGen.zig:5485-5536). + uint32_t main_token = tree->nodes.main_tokens[node]; + 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); + break; + case TOKEN_KEYWORD_ENUM: + decl_inst = enumDeclInner(ag, gz, node, members, members_len); + break; + default: + // union/opaque: fall back to struct for now. + decl_inst = structDeclInner(ag, gz, node, members, members_len); + break; + } (void)scope; ag->fn_block = prev_fn_block; return decl_inst + ZIR_REF_START_INDEX; } +// --- EnumDecl.Small packing (Zir.zig EnumDecl.Small) --- + +typedef struct { + bool has_tag_type; + bool has_captures_len; + bool has_body_len; + bool has_fields_len; + bool has_decls_len; + uint8_t name_strategy; // 2 bits + bool nonexhaustive; +} EnumDeclSmall; + +static uint16_t packEnumDeclSmall(EnumDeclSmall s) { + uint16_t r = 0; + if (s.has_tag_type) + r |= (1u << 0); + if (s.has_captures_len) + r |= (1u << 1); + if (s.has_body_len) + r |= (1u << 2); + if (s.has_fields_len) + r |= (1u << 3); + if (s.has_decls_len) + r |= (1u << 4); + r |= (uint16_t)(s.name_strategy & 0x3u) << 5; + if (s.nonexhaustive) + r |= (1u << 7); + return r; +} + +// Mirrors GenZir.setEnum (AstGen.zig:13080). +static void setEnum(AstGenCtx* ag, uint32_t inst, uint32_t src_node, + EnumDeclSmall small, uint32_t fields_len, uint32_t decls_len) { + ensureExtraCapacity(ag, 6 + 3); + + uint32_t payload_index = ag->extra_len; + + // fields_hash (4 words): zero-filled. + ag->extra[ag->extra_len++] = 0; + ag->extra[ag->extra_len++] = 0; + ag->extra[ag->extra_len++] = 0; + ag->extra[ag->extra_len++] = 0; + + ag->extra[ag->extra_len++] = ag->source_line; + ag->extra[ag->extra_len++] = src_node; + + if (small.has_fields_len) + ag->extra[ag->extra_len++] = fields_len; + if (small.has_decls_len) + ag->extra[ag->extra_len++] = decls_len; + + ag->inst_tags[inst] = ZIR_INST_EXTENDED; + ZirInstData data; + memset(&data, 0, sizeof(data)); + data.extended.opcode = (uint16_t)ZIR_EXT_ENUM_DECL; + data.extended.small = packEnumDeclSmall(small); + data.extended.operand = payload_index; + ag->inst_datas[inst] = data; +} + +// --- enumDeclInner (AstGen.zig:5508) --- + +static uint32_t enumDeclInner(AstGenCtx* ag, GenZir* gz, uint32_t node, + const uint32_t* members, uint32_t members_len) { + const Ast* tree = ag->tree; + uint32_t decl_inst = reserveInstructionIndex(ag); + gzAppendInstruction(gz, decl_inst); + + if (members_len == 0) { + EnumDeclSmall small; + memset(&small, 0, sizeof(small)); + setEnum(ag, decl_inst, node, small, 0, 0); + return decl_inst; + } + + advanceSourceCursorToNode(ag, node); + + uint32_t decl_count = scanContainer(ag, members, members_len); + uint32_t field_count = members_len - decl_count; + + // Use WipMembers for decls and field data. + // Enum fields: 1 bit per field (has_value), max 2 words per field + // (name + value). + WipMembers wm = wipMembersInit(decl_count, field_count); + + // Enum fields use 1 bit per field: has_value. + // We use the same WipMembers but with 1-bit fields. + // Actually, upstream uses bits_per_field=1, max_field_size=2. + // Re-init with correct params would be better but let's reuse. + // For simplicity: track field data manually. + uint32_t* field_names = NULL; + uint32_t field_names_len = 0; + uint32_t field_names_cap = 0; + + for (uint32_t i = 0; i < members_len; i++) { + uint32_t member_node = members[i]; + AstNodeTag mtag = tree->nodes.tags[member_node]; + switch (mtag) { + case AST_NODE_COMPTIME: + comptimeDecl(ag, gz, wm.payload, &wm.decl_index, member_node); + break; + case AST_NODE_SIMPLE_VAR_DECL: + case AST_NODE_GLOBAL_VAR_DECL: + case AST_NODE_LOCAL_VAR_DECL: + case AST_NODE_ALIGNED_VAR_DECL: + globalVarDecl( + ag, gz, wm.payload, &wm.decl_index, member_node); + break; + case AST_NODE_FN_DECL: + fnDecl(ag, gz, wm.payload, &wm.decl_index, member_node); + break; + case AST_NODE_TEST_DECL: + testDecl(ag, gz, wm.payload, &wm.decl_index, member_node); + break; + case AST_NODE_CONTAINER_FIELD_INIT: + case AST_NODE_CONTAINER_FIELD_ALIGN: + case AST_NODE_CONTAINER_FIELD: { + // Enum field: just a name (AstGen.zig:5617-5670). + uint32_t main_token = tree->nodes.main_tokens[member_node]; + uint32_t field_name = identAsString(ag, main_token); + // Grow field_names array. + if (field_names_len >= field_names_cap) { + uint32_t new_cap + = field_names_cap == 0 ? 8 : field_names_cap * 2; + field_names + = realloc(field_names, new_cap * sizeof(uint32_t)); + if (!field_names) + exit(1); + field_names_cap = new_cap; + } + field_names[field_names_len++] = field_name; + break; + } + default: + SET_ERROR(ag); + break; + } + } + + EnumDeclSmall small; + memset(&small, 0, sizeof(small)); + small.has_fields_len = (field_count > 0); + small.has_decls_len = (decl_count > 0); + setEnum(ag, decl_inst, node, small, field_count, decl_count); + + // Append: decls, field_bits, field_names (AstGen.zig:5724-5729). + uint32_t decls_len_out; + const uint32_t* decls_slice = wipMembersDeclsSlice(&wm, &decls_len_out); + + // Field bits: 1 bit per field (has_value = false for simple enums). + uint32_t fields_per_u32 = 32; + uint32_t bit_words + = field_count > 0 ? (field_count + fields_per_u32 - 1) / fields_per_u32 + : 0; + + ensureExtraCapacity( + ag, decls_len_out + bit_words + field_names_len); + for (uint32_t i = 0; i < decls_len_out; i++) + ag->extra[ag->extra_len++] = decls_slice[i]; + // Field bits: all zero (no values). + for (uint32_t i = 0; i < bit_words; i++) + ag->extra[ag->extra_len++] = 0; + // Field names. + for (uint32_t i = 0; i < field_names_len; i++) + ag->extra[ag->extra_len++] = field_names[i]; + + free(field_names); + wipMembersDeinit(&wm); + return decl_inst; +} + // --- structDeclInner (AstGen.zig:4926) --- static uint32_t structDeclInner(AstGenCtx* ag, GenZir* gz, uint32_t node, diff --git a/astgen_test.zig b/astgen_test.zig index c810baee4d..56cf4f6e65 100644 --- a/astgen_test.zig +++ b/astgen_test.zig @@ -87,8 +87,8 @@ fn buildHashSkipMask(gpa: Allocator, ref: Zir) ![]bool { switch (ref_tags[i]) { .extended => { const ext = ref_datas[i].extended; - if (ext.opcode == .struct_decl) { - // StructDecl starts with fields_hash[4]. + if (ext.opcode == .struct_decl or ext.opcode == .enum_decl) { + // StructDecl/EnumDecl starts with fields_hash[4]. const pi = ext.operand; for (0..4) |j| skip[pi + j] = true; } @@ -98,6 +98,24 @@ fn buildHashSkipMask(gpa: Allocator, ref: Zir) ![]bool { const pi = ref_datas[i].declaration.payload_index; for (0..4) |j| skip[pi + j] = true; }, + .func, .func_inferred => { + // Func payload: ret_ty(1) + param_block(1) + body_len(1) + // + trailing ret_ty + body + SrcLocs(3) + proto_hash(4). + const pi = ref_datas[i].pl_node.payload_index; + const ret_ty_raw: u32 = ref.extra[pi]; + const ret_body_len: u32 = ret_ty_raw & 0x7FFFFFFF; + const body_len: u32 = ref.extra[pi + 2]; + // ret_ty trailing: if body_len > 1, it's a body; if == 1, it's a ref; if 0, void. + const ret_trailing: u32 = if (ret_body_len > 1) ret_body_len else if (ret_body_len == 1) 1 else 0; + // proto_hash is at: pi + 3 + ret_trailing + body_len + 3 + if (body_len > 0) { + const hash_start = pi + 3 + ret_trailing + body_len + 3; + for (0..4) |j| { + if (hash_start + j < ref_extra_len) + skip[hash_start + j] = true; + } + } + }, else => {}, } } @@ -951,6 +969,33 @@ test "astgen: corpus test_all.zig" { try corpusCheck(gpa, "test_all.zig", @embedFile("test_all.zig")); } +test "astgen: enum decl" { + const gpa = std.testing.allocator; + const source: [:0]const u8 = "const E = enum { a, b, c };"; + var ref_zir = try refZir(gpa, source); + defer ref_zir.deinit(gpa); + var c_ast = c.astParse(source.ptr, @intCast(source.len)); + defer c.astDeinit(&c_ast); + var c_zir = c.astGen(&c_ast); + defer c.zirDeinit(&c_zir); + try expectEqualZir(gpa, ref_zir, c_zir); +} + +test "astgen: struct init typed" { + const gpa = std.testing.allocator; + const source: [:0]const u8 = + \\const T = struct { x: u32 }; + \\const v = T{ .x = 1 }; + ; + var ref_zir = try refZir(gpa, source); + defer ref_zir.deinit(gpa); + var c_ast = c.astParse(source.ptr, @intCast(source.len)); + defer c.astDeinit(&c_ast); + var c_zir = c.astGen(&c_ast); + defer c.zirDeinit(&c_zir); + try expectEqualZir(gpa, ref_zir, c_zir); +} + test "astgen: corpus" { if (true) return error.SkipZigTest; const gpa = std.testing.allocator;