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>
This commit is contained in:
2026-02-13 05:10:51 +00:00
parent 5fe9d921f9
commit 0c26524b07
2 changed files with 241 additions and 15 deletions

207
astgen.c
View File

@@ -1546,6 +1546,8 @@ static uint32_t fullBodyExpr(
static uint32_t containerDecl(GenZir* gz, Scope* scope, uint32_t node); static uint32_t containerDecl(GenZir* gz, Scope* scope, uint32_t node);
static uint32_t structDeclInner(AstGenCtx* ag, GenZir* gz, uint32_t node, static uint32_t structDeclInner(AstGenCtx* ag, GenZir* gz, uint32_t node,
const uint32_t* members, uint32_t members_len); 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( static uint32_t blockExprExpr(
GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node); GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node);
static uint32_t ifExpr(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_token = tree->nodes.main_tokens[proto_node];
uint32_t fn_name_token = fn_token + 1; uint32_t fn_name_token = fn_token + 1;
// Check for 'pub' modifier: token before fn_token might be 'pub'. // Check for 'pub' modifier (Ast.zig:2003-2025).
bool is_pub = false; bool is_pub = (fn_token > 0
if (fn_token > 0) { && tree->tokens.tags[fn_token - 1] == TOKEN_KEYWORD_PUB);
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;
}
// makeDeclaration on fn_decl node (AstGen.zig:4090). // makeDeclaration on fn_proto node (AstGen.zig:4090).
uint32_t decl_inst = makeDeclaration(ag, node); uint32_t decl_inst = makeDeclaration(ag, proto_node);
wip_decl_insts[*decl_idx] = decl_inst; wip_decl_insts[*decl_idx] = decl_inst;
(*decl_idx)++; (*decl_idx)++;
@@ -7867,15 +7864,199 @@ static uint32_t containerDecl(GenZir* gz, Scope* scope, uint32_t node) {
void* prev_fn_block = ag->fn_block; void* prev_fn_block = ag->fn_block;
ag->fn_block = NULL; ag->fn_block = NULL;
// For now, only handle struct containers (AstGen.zig:5481-5496). // Dispatch based on container keyword (AstGen.zig:5485-5536).
// TODO: handle union/enum/opaque. uint32_t main_token = tree->nodes.main_tokens[node];
uint32_t decl_inst = structDeclInner(ag, gz, node, members, members_len); 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; (void)scope;
ag->fn_block = prev_fn_block; ag->fn_block = prev_fn_block;
return decl_inst + ZIR_REF_START_INDEX; 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) --- // --- structDeclInner (AstGen.zig:4926) ---
static uint32_t structDeclInner(AstGenCtx* ag, GenZir* gz, uint32_t node, static uint32_t structDeclInner(AstGenCtx* ag, GenZir* gz, uint32_t node,

View File

@@ -87,8 +87,8 @@ fn buildHashSkipMask(gpa: Allocator, ref: Zir) ![]bool {
switch (ref_tags[i]) { switch (ref_tags[i]) {
.extended => { .extended => {
const ext = ref_datas[i].extended; const ext = ref_datas[i].extended;
if (ext.opcode == .struct_decl) { if (ext.opcode == .struct_decl or ext.opcode == .enum_decl) {
// StructDecl starts with fields_hash[4]. // StructDecl/EnumDecl starts with fields_hash[4].
const pi = ext.operand; const pi = ext.operand;
for (0..4) |j| skip[pi + j] = true; 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; const pi = ref_datas[i].declaration.payload_index;
for (0..4) |j| skip[pi + j] = true; 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 => {}, else => {},
} }
} }
@@ -951,6 +969,33 @@ test "astgen: corpus test_all.zig" {
try corpusCheck(gpa, "test_all.zig", @embedFile("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" { test "astgen: corpus" {
if (true) return error.SkipZigTest; if (true) return error.SkipZigTest;
const gpa = std.testing.allocator; const gpa = std.testing.allocator;