astgen: implement struct field emission in structDeclInner

Port WipMembers, field processing loop, nodeImpliesMoreThanOnePossibleValue,
and nodeImpliesComptimeOnly from upstream AstGen.zig. Struct fields are now
properly emitted with type expressions, default values, alignment, and
comptime annotations.

Also fix structDeclInner to add the reserved instruction to the GenZir
body (matching upstream gz.reserveInstructionIndex behavior) and use
AST_NODE_OFFSET_NONE for break_inline src_node in field bodies.

Tests added: single field, multiple fields, field with default, field
with alignment, comptime field.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-12 20:33:21 +00:00
parent fc8f27ebdd
commit 906c271284
2 changed files with 533 additions and 24 deletions

497
astgen.c
View File

@@ -7116,6 +7116,292 @@ static void globalVarDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts,
(void)gz;
}
// --- nodeImpliesMoreThanOnePossibleValue (AstGen.zig:10548) ---
// Check if an identifier is a primitive type with more than one value.
static bool identImpliesMoreThanOnePossibleValue(
const Ast* tree, uint32_t main_token) {
uint32_t start = tree->tokens.starts[main_token];
const char* src = tree->source + start;
// Match known primitive types that have more than one possible value.
// (AstGen.zig:10729-10766)
if (src[0] == 'u' || src[0] == 'i') {
// u8, u16, u32, u64, u128, u1, u29, usize, i8, i16, i32, i64, i128,
// isize
char c1 = src[1];
if (c1 >= '0' && c1 <= '9')
return true;
if (c1 == 's') // usize, isize
return (src[2] == 'i' && src[3] == 'z' && src[4] == 'e');
}
if (src[0] == 'f') {
// f16, f32, f64, f80, f128
char c1 = src[1];
if (c1 >= '0' && c1 <= '9')
return true;
}
if (src[0] == 'b' && src[1] == 'o' && src[2] == 'o' && src[3] == 'l'
&& !(src[4] >= 'a' && src[4] <= 'z')
&& !(src[4] >= 'A' && src[4] <= 'Z')
&& !(src[4] >= '0' && src[4] <= '9') && src[4] != '_')
return true;
if (src[0] == 'c' && src[1] == '_')
return true; // c_int, c_long, etc.
if (src[0] == 'a' && src[1] == 'n' && src[2] == 'y') {
// anyerror, anyframe, anyopaque
return true;
}
if (src[0] == 'c' && src[1] == 'o' && src[2] == 'm' && src[3] == 'p'
&& src[4] == 't' && src[5] == 'i' && src[6] == 'm' && src[7] == 'e')
return true; // comptime_float, comptime_int
if (src[0] == 't' && src[1] == 'y' && src[2] == 'p' && src[3] == 'e'
&& !(src[4] >= 'a' && src[4] <= 'z')
&& !(src[4] >= 'A' && src[4] <= 'Z')
&& !(src[4] >= '0' && src[4] <= '9') && src[4] != '_')
return true;
return false;
}
static bool nodeImpliesMoreThanOnePossibleValue(
const Ast* tree, uint32_t node) {
uint32_t cur = node;
while (1) {
AstNodeTag tag = tree->nodes.tags[cur];
switch (tag) {
// Pointer/optional/array/anyframe types → true
// (AstGen.zig:10718-10725)
case AST_NODE_PTR_TYPE_ALIGNED:
case AST_NODE_PTR_TYPE_SENTINEL:
case AST_NODE_PTR_TYPE:
case AST_NODE_PTR_TYPE_BIT_RANGE:
case AST_NODE_OPTIONAL_TYPE:
case AST_NODE_ANYFRAME_TYPE:
case AST_NODE_ARRAY_TYPE_SENTINEL:
return true;
// Forward to LHS: try, comptime, nosuspend
// (AstGen.zig:10710-10713)
case AST_NODE_TRY:
case AST_NODE_COMPTIME:
case AST_NODE_NOSUSPEND:
cur = tree->nodes.datas[cur].lhs;
continue;
// Forward to LHS: grouped_expression, unwrap_optional
// (AstGen.zig:10714-10716)
case AST_NODE_GROUPED_EXPRESSION:
case AST_NODE_UNWRAP_OPTIONAL:
cur = tree->nodes.datas[cur].lhs;
continue;
// Identifier: check primitives (AstGen.zig:10727-10780)
case AST_NODE_IDENTIFIER:
return identImpliesMoreThanOnePossibleValue(
tree, tree->nodes.main_tokens[cur]);
default:
return false;
}
}
}
// --- nodeImpliesComptimeOnly (AstGen.zig:10787) ---
static bool identImpliesComptimeOnly(
const Ast* tree, uint32_t main_token) {
uint32_t start = tree->tokens.starts[main_token];
const char* src = tree->source + start;
// Only comptime_float, comptime_int, type → true
// (AstGen.zig:11010-11013)
if (src[0] == 'c' && src[1] == 'o' && src[2] == 'm' && src[3] == 'p'
&& src[4] == 't' && src[5] == 'i' && src[6] == 'm' && src[7] == 'e')
return true; // comptime_float, comptime_int
if (src[0] == 't' && src[1] == 'y' && src[2] == 'p' && src[3] == 'e'
&& !(src[4] >= 'a' && src[4] <= 'z')
&& !(src[4] >= 'A' && src[4] <= 'Z')
&& !(src[4] >= '0' && src[4] <= '9') && src[4] != '_')
return true;
return false;
}
static bool nodeImpliesComptimeOnly(const Ast* tree, uint32_t node) {
uint32_t cur = node;
while (1) {
AstNodeTag tag = tree->nodes.tags[cur];
switch (tag) {
// Function prototypes → true (AstGen.zig:10950-10955)
case AST_NODE_FN_PROTO_SIMPLE:
case AST_NODE_FN_PROTO_MULTI:
case AST_NODE_FN_PROTO_ONE:
case AST_NODE_FN_PROTO:
return true;
// Forward to LHS: try, comptime, nosuspend
case AST_NODE_TRY:
case AST_NODE_COMPTIME:
case AST_NODE_NOSUSPEND:
cur = tree->nodes.datas[cur].lhs;
continue;
case AST_NODE_GROUPED_EXPRESSION:
case AST_NODE_UNWRAP_OPTIONAL:
cur = tree->nodes.datas[cur].lhs;
continue;
// Identifier: check primitives
case AST_NODE_IDENTIFIER:
return identImpliesComptimeOnly(
tree, tree->nodes.main_tokens[cur]);
default:
return false;
}
}
}
// --- WipMembers (AstGen.zig:3989) ---
// Tracks decl indices, field bit-flags, and per-field data during container
// processing. All data lives in a single malloc'd array laid out as:
// [decls (decl_count)] [field_bits (ceil)] [fields (up to field_count*max)]
// Bodies are tracked separately in a dynamic array.
typedef struct {
uint32_t* payload; // malloc'd array
uint32_t payload_top; // always 0 (start of decls region)
uint32_t field_bits_start;
uint32_t fields_start;
uint32_t fields_end;
uint32_t decl_index;
uint32_t field_index;
// Bodies scratch: dynamically grown array for field type/align/init bodies.
uint32_t* bodies;
uint32_t bodies_len;
uint32_t bodies_cap;
} WipMembers;
static WipMembers wipMembersInit(
uint32_t decl_count, uint32_t field_count) {
// bits_per_field = 4, max_field_size = 5
uint32_t fields_per_u32 = 8; // 32 / 4
uint32_t field_bits_start = decl_count;
uint32_t bit_words
= field_count > 0 ? (field_count + fields_per_u32 - 1) / fields_per_u32
: 0;
uint32_t fields_start = field_bits_start + bit_words;
uint32_t payload_end = fields_start + field_count * 5;
uint32_t alloc_size = payload_end > 0 ? payload_end : 1;
uint32_t* payload = calloc(alloc_size, sizeof(uint32_t));
if (!payload)
exit(1);
WipMembers wm;
memset(&wm, 0, sizeof(wm));
wm.payload = payload;
wm.payload_top = 0;
wm.field_bits_start = field_bits_start;
wm.fields_start = fields_start;
wm.fields_end = fields_start;
wm.decl_index = 0;
wm.field_index = 0;
wm.bodies = NULL;
wm.bodies_len = 0;
wm.bodies_cap = 0;
return wm;
}
static void wipMembersDeinit(WipMembers* wm) {
free(wm->payload);
free(wm->bodies);
}
static void wipMembersNextDecl(WipMembers* wm, uint32_t decl_inst) {
wm->payload[wm->payload_top + wm->decl_index] = decl_inst;
wm->decl_index++;
}
// bits_per_field = 4: bits[0]=have_align, bits[1]=have_value,
// bits[2]=is_comptime, bits[3]=have_type_body
static void wipMembersNextField(WipMembers* wm, bool bits[4]) {
uint32_t fields_per_u32 = 8; // 32 / 4
uint32_t index = wm->field_bits_start + wm->field_index / fields_per_u32;
uint32_t bit_bag
= (wm->field_index % fields_per_u32 == 0) ? 0 : wm->payload[index];
bit_bag >>= 4;
for (int i = 0; i < 4; i++) {
bit_bag |= ((uint32_t)(bits[i] ? 1 : 0)) << (32 - 4 + i);
}
wm->payload[index] = bit_bag;
wm->field_index++;
}
static void wipMembersAppendToField(WipMembers* wm, uint32_t data) {
wm->payload[wm->fields_end] = data;
wm->fields_end++;
}
static void wipMembersFinishBits(WipMembers* wm) {
uint32_t fields_per_u32 = 8; // 32 / 4
uint32_t empty_field_slots = fields_per_u32 - (wm->field_index % fields_per_u32);
if (wm->field_index > 0 && empty_field_slots < fields_per_u32) {
uint32_t index
= wm->field_bits_start + wm->field_index / fields_per_u32;
wm->payload[index] >>= (empty_field_slots * 4);
}
}
// Returns pointer to decls region and its length.
static const uint32_t* wipMembersDeclsSlice(
const WipMembers* wm, uint32_t* out_len) {
*out_len = wm->decl_index;
return wm->payload + wm->payload_top;
}
// Returns pointer to fields region (field_bits + field_data) and its length.
static const uint32_t* wipMembersFieldsSlice(
const WipMembers* wm, uint32_t* out_len) {
*out_len = wm->fields_end - wm->field_bits_start;
return wm->payload + wm->field_bits_start;
}
// Append body instructions to the WipMembers bodies scratch.
static void wipMembersBodiesAppend(
WipMembers* wm, const uint32_t* data, uint32_t len) {
if (wm->bodies_len + len > wm->bodies_cap) {
uint32_t new_cap
= wm->bodies_cap == 0 ? 64 : wm->bodies_cap * 2;
while (new_cap < wm->bodies_len + len)
new_cap *= 2;
wm->bodies = realloc(wm->bodies, new_cap * sizeof(uint32_t));
if (!wm->bodies)
exit(1);
wm->bodies_cap = new_cap;
}
memcpy(wm->bodies + wm->bodies_len, data, len * sizeof(uint32_t));
wm->bodies_len += len;
}
// Append body instructions with ref_table fixups to wm->bodies.
static void wipMembersBodiesAppendWithFixups(
WipMembers* wm, AstGenCtx* ag, const uint32_t* body, uint32_t body_len) {
for (uint32_t i = 0; i < body_len; i++) {
uint32_t inst = body[i];
// Grow if needed.
if (wm->bodies_len + 1 > wm->bodies_cap) {
uint32_t new_cap
= wm->bodies_cap == 0 ? 64 : wm->bodies_cap * 2;
wm->bodies = realloc(wm->bodies, new_cap * sizeof(uint32_t));
if (!wm->bodies)
exit(1);
wm->bodies_cap = new_cap;
}
wm->bodies[wm->bodies_len++] = inst;
// Check for ref fixup.
uint32_t ref_inst;
while (refTableFetchRemove(ag, inst, &ref_inst)) {
if (wm->bodies_len + 1 > wm->bodies_cap) {
uint32_t new_cap = wm->bodies_cap * 2;
wm->bodies
= realloc(wm->bodies, new_cap * sizeof(uint32_t));
if (!wm->bodies)
exit(1);
wm->bodies_cap = new_cap;
}
wm->bodies[wm->bodies_len++] = ref_inst;
inst = ref_inst;
}
}
}
// --- containerDecl (AstGen.zig:5468) ---
// Handles container declarations as expressions (struct{}, enum{}, etc.).
@@ -7186,7 +7472,9 @@ 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) {
const Ast* tree = ag->tree;
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) {
@@ -7201,63 +7489,224 @@ static uint32_t structDeclInner(AstGenCtx* ag, GenZir* gz, uint32_t node,
uint32_t decl_count = scanContainer(ag, members, members_len);
uint32_t field_count = members_len - decl_count;
(void)field_count; // TODO: handle struct fields
// WipMembers: simplified to a plain array of declaration indices.
// (AstGen.zig:5031 — WipMembers.init)
uint32_t alloc_count = decl_count > 0 ? decl_count : 1;
uint32_t* wip_decl_insts = calloc(alloc_count, sizeof(uint32_t));
if (!wip_decl_insts)
exit(1);
uint32_t decl_idx = 0;
WipMembers wm = wipMembersInit(decl_count, field_count);
// Set up block_scope for field type/align/init expressions.
// (AstGen.zig:4983-4992)
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.is_comptime = true;
block_scope.instructions_top = ag->scratch_inst_len;
bool known_non_opv = false;
bool known_comptime_only = false;
bool any_comptime_fields = false;
bool any_aligned_fields = false;
bool any_default_inits = false;
// Process each member (AstGen.zig:5060-5147).
for (uint32_t i = 0; i < members_len; i++) {
uint32_t member_node = members[i];
AstNodeTag tag = ag->tree->nodes.tags[member_node];
switch (tag) {
AstNodeTag mtag = tree->nodes.tags[member_node];
switch (mtag) {
case AST_NODE_COMPTIME:
comptimeDecl(ag, gz, wip_decl_insts, &decl_idx, member_node);
comptimeDecl(ag, gz, wm.payload, &wm.decl_index, member_node);
break;
case AST_NODE_SIMPLE_VAR_DECL:
globalVarDecl(ag, gz, wip_decl_insts, &decl_idx, member_node);
globalVarDecl(
ag, gz, wm.payload, &wm.decl_index, member_node);
break;
case AST_NODE_TEST_DECL:
testDecl(ag, gz, wip_decl_insts, &decl_idx, member_node);
testDecl(ag, gz, wm.payload, &wm.decl_index, member_node);
break;
case AST_NODE_FN_DECL:
fnDecl(ag, gz, wip_decl_insts, &decl_idx, member_node);
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:
case AST_NODE_ALIGNED_VAR_DECL:
globalVarDecl(ag, gz, wip_decl_insts, &decl_idx, member_node);
globalVarDecl(
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:
// Struct fields — skip for now (counted but not emitted).
case AST_NODE_CONTAINER_FIELD: {
// Extract field info from AST node (Ast.zig:1413-1454).
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:
// lhs = type_expr, rhs = value_expr (optional, 0=none)
value_node = nd.rhs;
break;
case AST_NODE_CONTAINER_FIELD_ALIGN:
// lhs = type_expr, rhs = align_expr
align_node = nd.rhs;
break;
case AST_NODE_CONTAINER_FIELD:
// lhs = type_expr, rhs = extra index to {align, value}
if (nd.rhs != 0) {
align_node = tree->extra_data.arr[nd.rhs];
value_node = tree->extra_data.arr[nd.rhs + 1];
}
break;
default:
break;
}
// Check for comptime token preceding main_token
// (Ast.zig:2071-2082).
if (main_token > 0
&& tree->tokens.tags[main_token - 1]
== TOKEN_KEYWORD_COMPTIME) {
has_comptime_token = true;
}
// Field name (AstGen.zig:5080).
uint32_t field_name = identAsString(ag, main_token);
wipMembersAppendToField(&wm, field_name);
// Type expression (AstGen.zig:5089-5109).
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);
}
bool have_align = (align_node != 0);
bool have_value = (value_node != 0);
bool is_comptime = has_comptime_token;
if (is_comptime) {
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);
}
}
bool field_bits[4]
= { have_align, have_value, is_comptime, have_type_body };
wipMembersNextField(&wm, field_bits);
if (have_type_body) {
// Emit break_inline to carry the type value
// (AstGen.zig:5097-5099).
if (!endsWithNoReturn(&block_scope)) {
makeBreakInline(&block_scope, decl_inst, field_type,
AST_NODE_OFFSET_NONE);
}
uint32_t raw_len = gzInstructionsLen(&block_scope);
const uint32_t* body = gzInstructionsSlice(&block_scope);
uint32_t body_len
= countBodyLenAfterFixups(ag, body, raw_len);
uint32_t bodies_before = wm.bodies_len;
wipMembersBodiesAppendWithFixups(
&wm, ag, body, raw_len);
(void)bodies_before;
wipMembersAppendToField(&wm, body_len);
// Reset block_scope.
ag->scratch_inst_len = block_scope.instructions_top;
} else {
wipMembersAppendToField(&wm, field_type);
}
if (have_align) {
any_aligned_fields = true;
uint32_t align_ref = expr(
&block_scope, &block_scope.base, align_node);
if (!endsWithNoReturn(&block_scope)) {
makeBreakInline(&block_scope, decl_inst, align_ref,
AST_NODE_OFFSET_NONE);
}
uint32_t raw_len = gzInstructionsLen(&block_scope);
const uint32_t* body = gzInstructionsSlice(&block_scope);
uint32_t body_len
= countBodyLenAfterFixups(ag, body, raw_len);
wipMembersBodiesAppendWithFixups(
&wm, ag, body, raw_len);
wipMembersAppendToField(&wm, body_len);
ag->scratch_inst_len = block_scope.instructions_top;
}
if (have_value) {
any_default_inits = true;
uint32_t default_ref = expr(
&block_scope, &block_scope.base, value_node);
if (!endsWithNoReturn(&block_scope)) {
makeBreakInline(&block_scope, decl_inst,
default_ref, AST_NODE_OFFSET_NONE);
}
uint32_t raw_len = gzInstructionsLen(&block_scope);
const uint32_t* body = gzInstructionsSlice(&block_scope);
uint32_t body_len
= countBodyLenAfterFixups(ag, body, raw_len);
wipMembersBodiesAppendWithFixups(
&wm, ag, body, raw_len);
wipMembersAppendToField(&wm, body_len);
ag->scratch_inst_len = block_scope.instructions_top;
}
break;
}
default:
SET_ERROR(ag);
break;
}
}
wipMembersFinishBits(&wm);
// setStruct (AstGen.zig:5152-5166).
StructDeclSmall small;
memset(&small, 0, sizeof(small));
small.has_decls_len = (decl_count > 0);
setStruct(ag, decl_inst, node, small, 0, 0, decl_count);
small.has_fields_len = (field_count > 0);
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;
setStruct(
ag, decl_inst, node, small, 0, field_count, decl_count);
// Append declarations list after StructDecl payload (AstGen.zig:5184).
ensureExtraCapacity(ag, decl_count);
for (uint32_t i = 0; i < decl_count; i++) {
ag->extra[ag->extra_len++] = wip_decl_insts[i];
}
// Append: captures (none), backing_int (none), decls, fields, bodies
// (AstGen.zig:5176-5189).
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);
free(wip_decl_insts);
ensureExtraCapacity(
ag, decls_len + fields_len + wm.bodies_len);
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++)
ag->extra[ag->extra_len++] = fields_slice[i];
for (uint32_t i = 0; i < wm.bodies_len; i++)
ag->extra[ag->extra_len++] = wm.bodies[i];
gzUnstack(&block_scope);
wipMembersDeinit(&wm);
return decl_inst;
}

View File

@@ -848,6 +848,66 @@ fn corpusCheck(gpa: Allocator, name: []const u8, source: [:0]const u8) !void {
}
}
test "astgen: struct single field" {
const gpa = std.testing.allocator;
const source: [:0]const u8 = "const T = struct { x: u32 };";
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 multiple fields" {
const gpa = std.testing.allocator;
const source: [:0]const u8 = "const T = struct { x: u32, y: bool };";
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 field with default" {
const gpa = std.testing.allocator;
const source: [:0]const u8 = "const T = struct { x: u32 = 0 };";
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 field with align" {
const gpa = std.testing.allocator;
const source: [:0]const u8 = "const T = struct { x: u32 align(4) };";
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 comptime field" {
const gpa = std.testing.allocator;
const source: [:0]const u8 = "const T = struct { comptime x: u32 = 0 };";
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_all.zig" {
const gpa = std.testing.allocator;
try corpusCheck(gpa, "test_all.zig", @embedFile("test_all.zig"));