From 324c6101f467c34ce5281e263521afdf2f3687be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Motiejus=20Jak=C5=A1tys?= Date: Fri, 13 Feb 2026 23:30:47 +0000 Subject: [PATCH] astgen: fix fnDecl anytype params, type coercion, and export linkage Co-Authored-By: Claude Opus 4.6 --- astgen.c | 221 +++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 151 insertions(+), 70 deletions(-) diff --git a/astgen.c b/astgen.c index 52e6836f57..269dab2b7f 100644 --- a/astgen.c +++ b/astgen.c @@ -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,44 +8657,98 @@ 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. - } 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) - is_comptime_param = true; - } + 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 { + // 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). @@ -8707,38 +8772,49 @@ static void fnDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, } } - // Evaluate param type expression in a sub-block - // (AstGen.zig:4333-4337). - GenZir param_gz = makeSubBlock(&decl_gz, params_scope); - uint32_t param_type_ref - = expr(¶m_gz, params_scope, param_type_node); + // 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 = fullBodyExpr( + ¶m_gz, params_scope, coerced_type_ri, param_type_node); - if (ag->has_compile_errors) - return; + if (ag->has_compile_errors) + return; - // 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(¶m_gz, param_inst_expected, param_type_ref, - (int32_t)param_type_node - (int32_t)param_gz.decl_node_index); + // 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; + makeBreakInline(¶m_gz, param_inst_expected, param_type_ref, + (int32_t)param_type_node - (int32_t)param_gz.decl_node_index); - // Create param instruction (AstGen.zig:4341-4343). - ZirInstTag param_tag - = is_comptime_param ? ZIR_INST_PARAM_COMPTIME : ZIR_INST_PARAM; - 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, ¶m_gz, param_tag, name_tok_for_src, param_name_str); - (void)param_inst_expected; - // Record param instruction index (AstGen.zig:4360). - if (param_insts_len < 32) - param_insts[param_insts_len++] = param_inst; + // Create param instruction (AstGen.zig:4341-4343). + ZirInstTag param_tag + = is_comptime_param ? ZIR_INST_PARAM_COMPTIME : ZIR_INST_PARAM; + 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, ¶m_gz, param_tag, + name_tok_for_src, param_name_str); + (void)param_inst_expected; + 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,