commit 0c26524b07df5817bbcffd4ad7725a34d8df0a1b (tree)
parent 5fe9d921f9fd8aeef76ed1c6ce764674664a5d8b
Author: Motiejus Jakštys <motiejus@jakstys.lt>
Date: Fri, 13 Feb 2026 05:10:51 +0000
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 <noreply@anthropic.com>
Diffstat:
| M | astgen.c | | | 207 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----- |
| M | astgen_test.zig | | | 49 | +++++++++++++++++++++++++++++++++++++++++++++++-- |
2 files changed, 241 insertions(+), 15 deletions(-)
diff --git 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
@@ -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;