astgen: fix fnDecl anytype params, type coercion, and export linkage

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-13 23:30:47 +00:00
parent 3c55dcc3b8
commit 324c6101f4

191
astgen.c
View File

@@ -8541,9 +8541,20 @@ 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 (Ast.zig:2003-2025).
bool is_pub = (fn_token > 0
&& tree->tokens.tags[fn_token - 1] == TOKEN_KEYWORD_PUB);
// Check for 'pub', 'export' modifiers (Ast.zig:2003-2025,
// AstGen.zig:4102-4106).
bool is_pub = false;
bool is_export = false;
for (uint32_t i = fn_token; i > 0;) {
i--;
uint32_t ttag = tree->tokens.tags[i];
if (ttag == TOKEN_KEYWORD_PUB)
is_pub = true;
else if (ttag == TOKEN_KEYWORD_EXPORT)
is_export = true;
else
break;
}
// makeDeclaration on fn_proto node (AstGen.zig:4090).
uint32_t decl_inst = makeDeclaration(ag, proto_node);
@@ -8646,45 +8657,99 @@ static void fnDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts,
uint32_t param_insts[32];
uint32_t param_insts_len = 0;
for (uint32_t param_i = 0; param_i < params_len; param_i++) {
uint32_t param_type_node = param_nodes[param_i];
// Parameter iteration using token-based iterator, mirroring upstream
// FnProto.Iterator (Ast.zig:2680-2768, AstGen.zig:4260-4363).
// The params array only contains type-expression params; anytype params
// exist as tokens between/after the type-expression nodes.
uint32_t lparen = fn_name_token + 1; // '(' token
uint32_t iter_tok_i = lparen + 1; // first token after '('
bool iter_tok_flag = true; // start in token-scanning mode
uint32_t iter_param_i = 0;
ResultLoc coerced_type_ri = { .tag = RL_COERCED_TY,
.data = ZIR_REF_TYPE_TYPE,
.src_node = 0,
.ctx = RI_CTX_NONE };
// Find param name token by scanning backwards from firstToken of
// type expression (mirrors FnProto.Iterator.next, Ast.zig:2687).
// Layout: [comptime] [name] [:] type_expr
// So: type_first_tok - 1 is ':', type_first_tok - 2 is name.
uint32_t type_first_tok = firstToken(tree, param_type_node);
uint32_t name_token = 0; // 0 = no name found
while (true) {
uint32_t name_token = 0;
uint32_t comptime_noalias_token = 0;
bool is_comptime_param = false;
if (type_first_tok >= 2
&& tree->tokens.tags[type_first_tok - 1] == TOKEN_COLON) {
// Named parameter: name is at type_first_tok - 2.
uint32_t maybe_name = type_first_tok - 2;
uint32_t name_start = tree->tokens.starts[maybe_name];
char ch = tree->source[name_start];
if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')
|| ch == '_' || ch == '@') {
// Could be name or comptime/noalias keyword.
if (name_start + 8 <= tree->source_len
&& memcmp(tree->source + name_start, "comptime", 8) == 0) {
is_comptime_param = true;
} else if (name_start + 7 <= tree->source_len
&& memcmp(tree->source + name_start, "noalias", 7) == 0) {
// noalias keyword, not a name.
bool is_anytype = false;
uint32_t param_type_node = 0;
if (!iter_tok_flag) {
// Return next param from params array.
if (iter_param_i >= params_len)
break;
param_type_node = param_nodes[iter_param_i];
// Scan backwards from type expression to find
// name/comptime/noalias (Ast.zig:2698-2705).
uint32_t tok_i = firstToken(tree, param_type_node);
while (tok_i > 0) {
tok_i--;
uint32_t ttag = tree->tokens.tags[tok_i];
if (ttag == TOKEN_COLON)
continue;
if (ttag == TOKEN_IDENTIFIER) {
name_token = tok_i;
continue;
}
if (ttag == TOKEN_KEYWORD_COMPTIME
|| ttag == TOKEN_KEYWORD_NOALIAS) {
comptime_noalias_token = tok_i;
continue;
}
break;
}
iter_param_i++;
iter_tok_i = lastToken(tree, param_type_node) + 1;
// Skip comma after param for anytype scanning.
if (tree->tokens.tags[iter_tok_i] == TOKEN_COMMA)
iter_tok_i++;
iter_tok_flag = true;
} else {
name_token = maybe_name;
// Check for preceding comptime keyword.
if (maybe_name > 0) {
uint32_t prev = maybe_name - 1;
uint32_t prev_start = tree->tokens.starts[prev];
if (prev_start + 8 <= tree->source_len
&& memcmp(tree->source + prev_start, "comptime", 8)
== 0)
// Token-scanning mode: look for anytype/ellipsis params
// (Ast.zig:2721-2767).
if (tree->tokens.tags[iter_tok_i] == TOKEN_COMMA)
iter_tok_i++;
if (tree->tokens.tags[iter_tok_i] == TOKEN_R_PAREN)
break;
// Skip doc comments.
while (tree->tokens.tags[iter_tok_i] == TOKEN_DOC_COMMENT)
iter_tok_i++;
// Check for ellipsis3 (varargs) - skip for now.
if (tree->tokens.tags[iter_tok_i] == TOKEN_ELLIPSIS3)
break;
// Check for comptime/noalias prefix.
if (tree->tokens.tags[iter_tok_i] == TOKEN_KEYWORD_COMPTIME
|| tree->tokens.tags[iter_tok_i] == TOKEN_KEYWORD_NOALIAS) {
comptime_noalias_token = iter_tok_i;
iter_tok_i++;
}
// Check for name: identifier followed by colon.
if (tree->tokens.tags[iter_tok_i] == TOKEN_IDENTIFIER
&& tree->tokens.tags[iter_tok_i + 1] == TOKEN_COLON) {
name_token = iter_tok_i;
iter_tok_i += 2;
}
// Check for anytype keyword.
if (tree->tokens.tags[iter_tok_i] == TOKEN_KEYWORD_ANYTYPE) {
is_anytype = true;
iter_tok_i++;
} else {
// Not an anytype param; switch to param-array mode.
iter_tok_flag = false;
continue;
}
}
// Determine is_comptime from comptime_noalias token
// (AstGen.zig:4265-4273).
if (comptime_noalias_token != 0
&& tree->tokens.tags[comptime_noalias_token]
== TOKEN_KEYWORD_COMPTIME) {
is_comptime_param = true;
}
}
}
}
// Determine param name string (AstGen.zig:4283-4321).
// Must be resolved BEFORE type expression to match upstream string
@@ -8707,23 +8772,33 @@ static void fnDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts,
}
}
// Evaluate param type expression in a sub-block
// (AstGen.zig:4333-4337).
// Emit param instruction (AstGen.zig:4323-4345).
uint32_t param_inst_ref;
if (is_anytype) {
// anytype parameter: emit param_anytype/param_anytype_comptime
// (AstGen.zig:4323-4329).
uint32_t anytype_name_token
= name_token != 0 ? name_token : (iter_tok_i - 1);
ZirInstTag anytype_tag = is_comptime_param
? ZIR_INST_PARAM_ANYTYPE_COMPTIME
: ZIR_INST_PARAM_ANYTYPE;
uint32_t anytype_inst = addStrTok(
&decl_gz, anytype_tag, param_name_str, anytype_name_token);
param_inst_ref = anytype_inst + ZIR_REF_START_INDEX;
if (param_insts_len < 32)
param_insts[param_insts_len++] = anytype_inst;
} else {
// Type-expression parameter (AstGen.zig:4330-4344).
GenZir param_gz = makeSubBlock(&decl_gz, params_scope);
uint32_t param_type_ref
= expr(&param_gz, params_scope, param_type_node);
uint32_t param_type_ref = fullBodyExpr(
&param_gz, params_scope, coerced_type_ri, param_type_node);
if (ag->has_compile_errors)
return;
// The break_inline target is the param instruction we're about to
// create (AstGen.zig:4336-4337).
// The break_inline target is the param instruction we're about
// to create (AstGen.zig:4336-4337).
uint32_t param_inst_expected = ag->inst_len + 1;
// +1 because: the break_inline is emitted first (uses inst_len),
// then addParam emits the param instruction at inst_len.
// Actually, addParam emits the param after break_inline. The
// break_inline's block_inst field should point to the param inst.
// We know it will be at ag->inst_len after the break_inline.
makeBreakInline(&param_gz, param_inst_expected, param_type_ref,
(int32_t)param_type_node - (int32_t)param_gz.decl_node_index);
@@ -8733,12 +8808,13 @@ static void fnDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts,
uint32_t name_tok_for_src = name_token != 0
? name_token
: tree->nodes.main_tokens[param_type_node];
uint32_t param_inst = addParam(
&decl_gz, &param_gz, param_tag, name_tok_for_src, param_name_str);
uint32_t param_inst = addParam(&decl_gz, &param_gz, param_tag,
name_tok_for_src, param_name_str);
(void)param_inst_expected;
// Record param instruction index (AstGen.zig:4360).
param_inst_ref = param_inst + ZIR_REF_START_INDEX;
if (param_insts_len < 32)
param_insts[param_insts_len++] = param_inst;
}
// Create ScopeLocalVal for this param (AstGen.zig:4349-4359).
if (param_name_str != 0 && param_scope_count < 32) {
@@ -8746,7 +8822,7 @@ static void fnDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts,
lv->base.tag = SCOPE_LOCAL_VAL;
lv->parent = params_scope;
lv->gen_zir = &decl_gz;
lv->inst = param_inst + ZIR_REF_START_INDEX; // toRef()
lv->inst = param_inst_ref;
lv->token_src = name_token;
lv->name = param_name_str;
params_scope = &lv->base;
@@ -8757,7 +8833,8 @@ static void fnDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts,
GenZir ret_gz = makeSubBlock(&decl_gz, params_scope);
uint32_t ret_ref = ZIR_REF_NONE;
if (return_type_node != 0) {
ret_ref = expr(&ret_gz, params_scope, return_type_node);
ret_ref = fullBodyExpr(
&ret_gz, params_scope, coerced_type_ri, return_type_node);
if (ag->has_compile_errors)
return;
// If ret_gz produced instructions, add break_inline
@@ -8891,8 +8968,12 @@ static void fnDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts,
&decl_gz, decl_inst, func_ref, (int32_t)node - (int32_t)proto_node);
// setDeclaration (AstGen.zig:4208-4225).
DeclFlagsId decl_id
= is_pub ? DECL_ID_PUB_CONST_SIMPLE : DECL_ID_CONST_SIMPLE;
// Linkage: export > normal (AstGen.zig:4217).
DeclFlagsId decl_id;
if (is_export)
decl_id = is_pub ? DECL_ID_PUB_EXPORT_CONST : DECL_ID_EXPORT_CONST;
else
decl_id = is_pub ? DECL_ID_PUB_CONST_SIMPLE : DECL_ID_CONST_SIMPLE;
uint32_t name_str = identAsString(ag, fn_name_token);
setDeclaration(ag, decl_inst,
(SetDeclArgs) { .src_line = decl_line,