Commit a5c2de5fee
Changed files (5)
doc/langref.md
@@ -141,7 +141,7 @@ StructLiteralField = "." "Symbol" "=" Expression
PrefixOp = "!" | "-" | "~" | "*" | ("&" option("const")) | "?" | "%" | "%%"
-PrimaryExpression = "Number" | "String" | "CharLiteral" | KeywordLiteral | GroupedExpression | GotoExpression | BlockExpression | "Symbol" | ("@" "Symbol" FnCallExpression) | ArrayType | FnProto | AsmExpression | ("error" "." "Symbol")
+PrimaryExpression = "Number" | "String" | "CharLiteral" | KeywordLiteral | GroupedExpression | GotoExpression | BlockExpression | "Symbol" | ("@" "Symbol" FnCallExpression) | ArrayType | (option("extern") FnProto) | AsmExpression | ("error" "." "Symbol")
ArrayType = "[" option(Expression) "]" option("const") PrefixOpExpression
src/all_types.hpp
@@ -189,6 +189,7 @@ struct AstNodeFnProto {
FnTableEntry *fn_table_entry;
bool skip;
TopLevelDecl top_level_decl;
+ Expr resolved_expr;
};
struct AstNodeFnDef {
@@ -828,6 +829,7 @@ struct TypeTableEntryFn {
bool is_var_args;
int gen_param_count;
LLVMCallConv calling_convention;
+ bool is_extern;
bool is_naked;
};
src/analyze.cpp
@@ -451,44 +451,20 @@ static TypeTableEntry *analyze_type_expr(CodeGen *g, ImportTableEntry *import, B
return resolve_type(g, *node_ptr);
}
-static void resolve_function_proto(CodeGen *g, AstNode *node, FnTableEntry *fn_table_entry,
- ImportTableEntry *import)
+static TypeTableEntry *analyze_fn_proto_type(CodeGen *g, ImportTableEntry *import, BlockContext *context,
+ TypeTableEntry *expected_type, AstNode *node, bool is_naked)
{
assert(node->type == NodeTypeFnProto);
AstNodeFnProto *fn_proto = &node->data.fn_proto;
if (fn_proto->skip) {
- return;
+ return g->builtin_types.entry_invalid;
}
TypeTableEntry *fn_type = new_type_table_entry(TypeTableEntryIdFn);
- fn_table_entry->type_entry = fn_type;
- fn_type->data.fn.calling_convention = fn_table_entry->internal_linkage ? LLVMFastCallConv : LLVMCCallConv;
-
- for (int i = 0; i < fn_proto->directives->length; i += 1) {
- AstNode *directive_node = fn_proto->directives->at(i);
- Buf *name = &directive_node->data.directive.name;
-
- if (buf_eql_str(name, "attribute")) {
- Buf *attr_name = &directive_node->data.directive.param;
- if (fn_table_entry->fn_def_node) {
- if (buf_eql_str(attr_name, "naked")) {
- fn_type->data.fn.is_naked = true;
- } else if (buf_eql_str(attr_name, "inline")) {
- fn_table_entry->is_inline = true;
- } else {
- add_node_error(g, directive_node,
- buf_sprintf("invalid function attribute: '%s'", buf_ptr(name)));
- }
- } else {
- add_node_error(g, directive_node,
- buf_sprintf("invalid function attribute: '%s'", buf_ptr(name)));
- }
- } else {
- add_node_error(g, directive_node,
- buf_sprintf("invalid directive: '%s'", buf_ptr(name)));
- }
- }
+ fn_type->data.fn.is_extern = fn_proto->is_extern || (fn_proto->visib_mod == VisibModExport);
+ fn_type->data.fn.is_naked = is_naked;
+ fn_type->data.fn.calling_convention = fn_proto->is_extern ? LLVMCCallConv : LLVMFastCallConv;
int src_param_count = node->data.fn_proto.params.length;
fn_type->size_in_bits = g->pointer_size_bytes * 8;
@@ -499,10 +475,9 @@ static void resolve_function_proto(CodeGen *g, AstNode *node, FnTableEntry *fn_t
// first, analyze the parameters and return type in order they appear in
// source code in order for error messages to be in the best order.
buf_resize(&fn_type->name, 0);
- const char *export_str = fn_table_entry->internal_linkage ? "" : "export ";
- const char *inline_str = fn_table_entry->is_inline ? "inline " : "";
+ const char *extern_str = fn_type->data.fn.is_extern ? "extern " : "";
const char *naked_str = fn_type->data.fn.is_naked ? "naked " : "";
- buf_appendf(&fn_type->name, "%s%s%sfn(", export_str, inline_str, naked_str);
+ buf_appendf(&fn_type->name, "%s%sfn(", extern_str, naked_str);
for (int i = 0; i < src_param_count; i += 1) {
AstNode *child = node->data.fn_proto.params.at(i);
assert(child->type == NodeTypeParamDecl);
@@ -525,10 +500,9 @@ static void resolve_function_proto(CodeGen *g, AstNode *node, FnTableEntry *fn_t
const char *comma = (src_param_count == 0) ? "" : ", ";
buf_appendf(&fn_type->name, "%s...", comma);
}
-
buf_appendf(&fn_type->name, ")");
if (return_type->id != TypeTableEntryIdVoid) {
- buf_appendf(&fn_type->name, " %s", buf_ptr(&return_type->name));
+ buf_appendf(&fn_type->name, " -> %s", buf_ptr(&return_type->name));
}
@@ -593,13 +567,12 @@ static void resolve_function_proto(CodeGen *g, AstNode *node, FnTableEntry *fn_t
fn_type->data.fn.gen_param_count = gen_param_index;
if (fn_proto->skip) {
- return;
+ return g->builtin_types.entry_invalid;
}
auto table_entry = import->fn_type_table.maybe_get(&fn_type->name);
if (table_entry) {
- fn_type = table_entry->value;
- fn_table_entry->type_entry = fn_type;
+ return table_entry->value;
} else {
fn_type->data.fn.raw_type_ref = LLVMFunctionType(gen_return_type->type_ref,
gen_param_types, gen_param_index, fn_type->data.fn.is_var_args);
@@ -608,8 +581,56 @@ static void resolve_function_proto(CodeGen *g, AstNode *node, FnTableEntry *fn_t
param_di_types, gen_param_index + 1, 0);
import->fn_type_table.put(&fn_type->name, fn_type);
+
+ return fn_type;
+ }
+}
+
+
+static void resolve_function_proto(CodeGen *g, AstNode *node, FnTableEntry *fn_table_entry,
+ ImportTableEntry *import)
+{
+ assert(node->type == NodeTypeFnProto);
+ AstNodeFnProto *fn_proto = &node->data.fn_proto;
+
+ if (fn_proto->skip) {
+ return;
}
+ bool is_naked = false;
+ for (int i = 0; i < fn_proto->directives->length; i += 1) {
+ AstNode *directive_node = fn_proto->directives->at(i);
+ Buf *name = &directive_node->data.directive.name;
+
+ if (buf_eql_str(name, "attribute")) {
+ Buf *attr_name = &directive_node->data.directive.param;
+ if (fn_table_entry->fn_def_node) {
+ if (buf_eql_str(attr_name, "naked")) {
+ is_naked = true;
+ } else if (buf_eql_str(attr_name, "inline")) {
+ fn_table_entry->is_inline = true;
+ } else {
+ add_node_error(g, directive_node,
+ buf_sprintf("invalid function attribute: '%s'", buf_ptr(name)));
+ }
+ } else {
+ add_node_error(g, directive_node,
+ buf_sprintf("invalid function attribute: '%s'", buf_ptr(name)));
+ }
+ } else {
+ add_node_error(g, directive_node,
+ buf_sprintf("invalid directive: '%s'", buf_ptr(name)));
+ }
+ }
+
+ TypeTableEntry *fn_type = analyze_fn_proto_type(g, import, import->block_context, nullptr, node, is_naked);
+
+ if (fn_type->id == TypeTableEntryIdInvalid) {
+ fn_proto->skip = true;
+ return;
+ }
+
+ fn_table_entry->type_entry = fn_type;
fn_table_entry->fn_value = LLVMAddFunction(g->module, buf_ptr(&fn_table_entry->symbol_name),
fn_type->data.fn.raw_type_ref);
@@ -624,7 +645,7 @@ static void resolve_function_proto(CodeGen *g, AstNode *node, FnTableEntry *fn_t
LLVMSetLinkage(fn_table_entry->fn_value, fn_table_entry->internal_linkage ?
LLVMInternalLinkage : LLVMExternalLinkage);
- if (return_type->id == TypeTableEntryIdUnreachable) {
+ if (fn_type->data.fn.src_return_type->id == TypeTableEntryIdUnreachable) {
LLVMAddFunctionAttr(fn_table_entry->fn_value, LLVMNoReturnAttribute);
}
LLVMSetFunctionCallConv(fn_table_entry->fn_value, fn_type->data.fn.calling_convention);
@@ -1353,7 +1374,29 @@ static bool types_match_const_cast_only(TypeTableEntry *expected_type, TypeTable
if (expected_type->id == TypeTableEntryIdFn &&
actual_type->id == TypeTableEntryIdFn)
{
- zig_panic("TODO types_match_const_cast_only for fns");
+ if (expected_type->data.fn.is_extern != actual_type->data.fn.is_extern) {
+ return false;
+ }
+ if (expected_type->data.fn.is_naked != actual_type->data.fn.is_naked) {
+ return false;
+ }
+ if (!types_match_const_cast_only(expected_type->data.fn.src_return_type,
+ actual_type->data.fn.src_return_type))
+ {
+ return false;
+ }
+ if (expected_type->data.fn.src_param_count != actual_type->data.fn.src_param_count) {
+ return false;
+ }
+ for (int i = 0; i < expected_type->data.fn.src_param_count; i += 1) {
+ // note it's reversed for parameters
+ if (types_match_const_cast_only(actual_type->data.fn.param_types[i],
+ expected_type->data.fn.param_types[i]))
+ {
+ return false;
+ }
+ }
+ return true;
}
@@ -2902,6 +2945,18 @@ static TypeTableEntry *analyze_array_type(CodeGen *g, ImportTableEntry *import,
}
}
+static TypeTableEntry *analyze_fn_proto_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
+ TypeTableEntry *expected_type, AstNode *node)
+{
+ TypeTableEntry *type_entry = analyze_fn_proto_type(g, import, context, expected_type, node, false);
+
+ if (type_entry->id == TypeTableEntryIdInvalid) {
+ return type_entry;
+ }
+
+ return resolve_expr_const_val_as_type(g, node, type_entry);
+}
+
static TypeTableEntry *analyze_while_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
TypeTableEntry *expected_type, AstNode *node)
{
@@ -4240,6 +4295,9 @@ static TypeTableEntry *analyze_expression(CodeGen *g, ImportTableEntry *import,
case NodeTypeArrayType:
return_type = analyze_array_type(g, import, context, expected_type, node);
break;
+ case NodeTypeFnProto:
+ return_type = analyze_fn_proto_expr(g, import, context, expected_type, node);
+ break;
case NodeTypeErrorType:
return_type = resolve_expr_const_val_as_type(g, node, g->builtin_types.entry_pure_error);
break;
@@ -4250,7 +4308,6 @@ static TypeTableEntry *analyze_expression(CodeGen *g, ImportTableEntry *import,
case NodeTypeSwitchRange:
case NodeTypeDirective:
case NodeTypeFnDecl:
- case NodeTypeFnProto:
case NodeTypeParamDecl:
case NodeTypeRoot:
case NodeTypeRootExportDecl:
@@ -4555,13 +4612,23 @@ static void collect_expr_decl_deps(CodeGen *g, ImportTableEntry *import, AstNode
collect_expr_decl_deps(g, import, node->data.switch_range.start, decl_node);
collect_expr_decl_deps(g, import, node->data.switch_range.end, decl_node);
break;
- case NodeTypeVariableDeclaration:
case NodeTypeFnProto:
+ // remember that fn proto node is used for function definitions as well
+ // as types
+ for (int i = 0; i < node->data.fn_proto.params.length; i += 1) {
+ AstNode *param = node->data.fn_proto.params.at(i);
+ collect_expr_decl_deps(g, import, param, decl_node);
+ }
+ collect_expr_decl_deps(g, import, node->data.fn_proto.return_type, decl_node);
+ break;
+ case NodeTypeParamDecl:
+ collect_expr_decl_deps(g, import, node->data.param_decl.type, decl_node);
+ break;
+ case NodeTypeVariableDeclaration:
case NodeTypeRootExportDecl:
case NodeTypeFnDef:
case NodeTypeRoot:
case NodeTypeFnDecl:
- case NodeTypeParamDecl:
case NodeTypeDirective:
case NodeTypeImport:
case NodeTypeCImport:
@@ -4705,12 +4772,8 @@ static void detect_top_level_decl_deps(CodeGen *g, ImportTableEntry *import, Ast
// determine which other top level declarations this function prototype depends on.
TopLevelDecl *decl_node = &node->data.fn_proto.top_level_decl;
decl_node->deps.init(1);
- for (int i = 0; i < node->data.fn_proto.params.length; i += 1) {
- AstNode *param_node = node->data.fn_proto.params.at(i);
- assert(param_node->type == NodeTypeParamDecl);
- collect_expr_decl_deps(g, import, param_node->data.param_decl.type, decl_node);
- }
- collect_expr_decl_deps(g, import, node->data.fn_proto.return_type, decl_node);
+
+ collect_expr_decl_deps(g, import, node, decl_node);
decl_node->name = name;
decl_node->import = import;
@@ -4999,11 +5062,12 @@ Expr *get_resolved_expr(AstNode *node) {
return &node->data.error_type.resolved_expr;
case NodeTypeSwitchExpr:
return &node->data.switch_expr.resolved_expr;
+ case NodeTypeFnProto:
+ return &node->data.fn_proto.resolved_expr;
case NodeTypeSwitchProng:
case NodeTypeSwitchRange:
case NodeTypeRoot:
case NodeTypeRootExportDecl:
- case NodeTypeFnProto:
case NodeTypeFnDef:
case NodeTypeFnDecl:
case NodeTypeParamDecl:
src/parser.cpp
@@ -503,6 +503,8 @@ static AstNode *ast_parse_if_expr(ParseContext *pc, int *token_index, bool manda
static AstNode *ast_parse_block_expr(ParseContext *pc, int *token_index, bool mandatory);
static AstNode *ast_parse_unwrap_expr(ParseContext *pc, int *token_index, bool mandatory);
static AstNode *ast_parse_prefix_op_expr(ParseContext *pc, int *token_index, bool mandatory);
+static AstNode *ast_parse_fn_proto(ParseContext *pc, int *token_index, bool mandatory,
+ ZigList<AstNode*> *directives, VisibMod visib_mod);
static void ast_expect_token(ParseContext *pc, Token *token, TokenId token_id) {
if (token->id == token_id) {
@@ -671,7 +673,7 @@ static AstNode *ast_parse_grouped_expr(ParseContext *pc, int *token_index, bool
Token *l_paren = &pc->tokens->at(*token_index);
if (l_paren->id != TokenIdLParen) {
if (mandatory) {
- ast_invalid_token_error(pc, l_paren);
+ ast_expect_token(pc, l_paren, TokenIdLParen);
} else {
return nullptr;
}
@@ -695,7 +697,7 @@ static AstNode *ast_parse_array_type_expr(ParseContext *pc, int *token_index, bo
Token *l_bracket = &pc->tokens->at(*token_index);
if (l_bracket->id != TokenIdLBracket) {
if (mandatory) {
- ast_invalid_token_error(pc, l_bracket);
+ ast_expect_token(pc, l_bracket, TokenIdLBracket);
} else {
return nullptr;
}
@@ -865,7 +867,7 @@ static AstNode *ast_parse_asm_expr(ParseContext *pc, int *token_index, bool mand
if (asm_token->id != TokenIdKeywordAsm) {
if (mandatory) {
- ast_invalid_token_error(pc, asm_token);
+ ast_expect_token(pc, asm_token, TokenIdKeywordAsm);
} else {
return nullptr;
}
@@ -905,7 +907,7 @@ static AstNode *ast_parse_asm_expr(ParseContext *pc, int *token_index, bool mand
}
/*
-PrimaryExpression : "Number" | "String" | "CharLiteral" | KeywordLiteral | GroupedExpression | GotoExpression | BlockExpression | "Symbol" | ("@" "Symbol" FnCallExpression) | ArrayType | AsmExpression | ("error" "." "Symbol")
+PrimaryExpression = "Number" | "String" | "CharLiteral" | KeywordLiteral | GroupedExpression | GotoExpression | BlockExpression | "Symbol" | ("@" "Symbol" FnCallExpression) | ArrayType | FnProto | AsmExpression | ("error" "." "Symbol")
KeywordLiteral : "true" | "false" | "null" | "break" | "continue" | "undefined" | "error"
*/
static AstNode *ast_parse_primary_expr(ParseContext *pc, int *token_index, bool mandatory) {
@@ -956,6 +958,11 @@ static AstNode *ast_parse_primary_expr(ParseContext *pc, int *token_index, bool
AstNode *node = ast_create_node(pc, NodeTypeErrorType, token);
*token_index += 1;
return node;
+ } else if (token->id == TokenIdKeywordExtern) {
+ *token_index += 1;
+ AstNode *node = ast_parse_fn_proto(pc, token_index, true, nullptr, VisibModPrivate);
+ node->data.fn_proto.is_extern = true;
+ return node;
} else if (token->id == TokenIdAtSign) {
*token_index += 1;
Token *name_tok = ast_eat_token(pc, token_index, TokenIdSymbol);
@@ -1002,6 +1009,11 @@ static AstNode *ast_parse_primary_expr(ParseContext *pc, int *token_index, bool
return array_type_node;
}
+ AstNode *fn_proto_node = ast_parse_fn_proto(pc, token_index, false, nullptr, VisibModPrivate);
+ if (fn_proto_node) {
+ return fn_proto_node;
+ }
+
AstNode *asm_expr = ast_parse_asm_expr(pc, token_index, false);
if (asm_expr) {
return asm_expr;
@@ -1055,7 +1067,7 @@ static AstNode *ast_parse_curly_suffix_expr(ParseContext *pc, int *token_index,
token = &pc->tokens->at(*token_index);
continue;
} else if (comma_tok->id != TokenIdRBrace) {
- ast_invalid_token_error(pc, comma_tok);
+ ast_expect_token(pc, comma_tok, TokenIdRBrace);
} else {
*token_index += 1;
break;
@@ -1084,7 +1096,7 @@ static AstNode *ast_parse_curly_suffix_expr(ParseContext *pc, int *token_index,
token = &pc->tokens->at(*token_index);
continue;
} else if (comma_tok->id != TokenIdRBrace) {
- ast_invalid_token_error(pc, comma_tok);
+ ast_expect_token(pc, comma_tok, TokenIdRBrace);
} else {
*token_index += 1;
break;
@@ -1555,7 +1567,7 @@ static AstNode *ast_parse_else(ParseContext *pc, int *token_index, bool mandator
if (else_token->id != TokenIdKeywordElse) {
if (mandatory) {
- ast_invalid_token_error(pc, else_token);
+ ast_expect_token(pc, else_token, TokenIdKeywordElse);
} else {
return nullptr;
}
@@ -1574,7 +1586,7 @@ static AstNode *ast_parse_if_expr(ParseContext *pc, int *token_index, bool manda
Token *if_tok = &pc->tokens->at(*token_index);
if (if_tok->id != TokenIdKeywordIf) {
if (mandatory) {
- ast_invalid_token_error(pc, if_tok);
+ ast_expect_token(pc, if_tok, TokenIdKeywordIf);
} else {
return nullptr;
}
@@ -1637,7 +1649,8 @@ static AstNode *ast_parse_return_expr(ParseContext *pc, int *token_index, bool m
kind = ReturnKindError;
*token_index += 2;
} else if (mandatory) {
- ast_invalid_token_error(pc, token);
+ ast_expect_token(pc, next_token, TokenIdKeywordReturn);
+ zig_unreachable();
} else {
return nullptr;
}
@@ -1647,7 +1660,8 @@ static AstNode *ast_parse_return_expr(ParseContext *pc, int *token_index, bool m
kind = ReturnKindMaybe;
*token_index += 2;
} else if (mandatory) {
- ast_invalid_token_error(pc, token);
+ ast_expect_token(pc, next_token, TokenIdKeywordReturn);
+ zig_unreachable();
} else {
return nullptr;
}
@@ -1655,7 +1669,8 @@ static AstNode *ast_parse_return_expr(ParseContext *pc, int *token_index, bool m
kind = ReturnKindUnconditional;
*token_index += 1;
} else if (mandatory) {
- ast_invalid_token_error(pc, token);
+ ast_expect_token(pc, token, TokenIdKeywordReturn);
+ zig_unreachable();
} else {
return nullptr;
}
@@ -1756,7 +1771,7 @@ static AstNode *ast_parse_while_expr(ParseContext *pc, int *token_index, bool ma
if (token->id != TokenIdKeywordWhile) {
if (mandatory) {
- ast_invalid_token_error(pc, token);
+ ast_expect_token(pc, token, TokenIdKeywordWhile);
} else {
return nullptr;
}
@@ -1791,7 +1806,7 @@ static AstNode *ast_parse_for_expr(ParseContext *pc, int *token_index, bool mand
if (token->id != TokenIdKeywordFor) {
if (mandatory) {
- ast_invalid_token_error(pc, token);
+ ast_expect_token(pc, token, TokenIdKeywordFor);
} else {
return nullptr;
}
@@ -1829,7 +1844,7 @@ static AstNode *ast_parse_switch_expr(ParseContext *pc, int *token_index, bool m
if (token->id != TokenIdKeywordSwitch) {
if (mandatory) {
- ast_invalid_token_error(pc, token);
+ ast_expect_token(pc, token, TokenIdKeywordSwitch);
} else {
return nullptr;
}
@@ -2082,7 +2097,7 @@ static AstNode *ast_parse_label(ParseContext *pc, int *token_index, bool mandato
Token *symbol_token = &pc->tokens->at(*token_index);
if (symbol_token->id != TokenIdSymbol) {
if (mandatory) {
- ast_invalid_token_error(pc, symbol_token);
+ ast_expect_token(pc, symbol_token, TokenIdSymbol);
} else {
return nullptr;
}
@@ -2091,7 +2106,7 @@ static AstNode *ast_parse_label(ParseContext *pc, int *token_index, bool mandato
Token *colon_token = &pc->tokens->at(*token_index + 1);
if (colon_token->id != TokenIdColon) {
if (mandatory) {
- ast_invalid_token_error(pc, colon_token);
+ ast_expect_token(pc, colon_token, TokenIdColon);
} else {
return nullptr;
}
@@ -2122,7 +2137,7 @@ static AstNode *ast_parse_block(ParseContext *pc, int *token_index, bool mandato
if (last_token->id != TokenIdLBrace) {
if (mandatory) {
- ast_invalid_token_error(pc, last_token);
+ ast_expect_token(pc, last_token, TokenIdLBrace);
} else {
return nullptr;
}
@@ -2245,7 +2260,7 @@ static AstNode *ast_parse_extern_decl(ParseContext *pc, int *token_index, bool m
Token *extern_kw = &pc->tokens->at(*token_index);
if (extern_kw->id != TokenIdKeywordExtern) {
if (mandatory) {
- ast_invalid_token_error(pc, extern_kw);
+ ast_expect_token(pc, extern_kw, TokenIdKeywordExtern);
} else {
return nullptr;
}
@@ -2591,7 +2606,9 @@ void normalize_parent_ptrs(AstNode *node) {
break;
case NodeTypeFnProto:
set_field(&node->data.fn_proto.return_type);
- set_list_fields(node->data.fn_proto.directives);
+ if (node->data.fn_proto.directives) {
+ set_list_fields(node->data.fn_proto.directives);
+ }
set_list_fields(&node->data.fn_proto.params);
break;
case NodeTypeFnDef:
test/run_tests.cpp
@@ -1866,6 +1866,23 @@ fn f(i32) {}
)SOURCE", 2,
".tmp_source.zig:2:1: error: missing function name",
".tmp_source.zig:3:6: error: missing parameter name");
+
+ add_compile_fail_case("wrong function type", R"SOURCE(
+const fns = []fn(){ a, b, c };
+fn a() -> i32 {0}
+fn b() -> i32 {1}
+fn c() -> i32 {2}
+ )SOURCE", 3,
+ ".tmp_source.zig:2:21: error: expected type 'fn()', got 'fn() -> i32'",
+ ".tmp_source.zig:2:24: error: expected type 'fn()', got 'fn() -> i32'",
+ ".tmp_source.zig:2:27: error: expected type 'fn()', got 'fn() -> i32'");
+
+ add_compile_fail_case("extern function pointer mismatch", R"SOURCE(
+const fns = [](fn(i32)->i32){ a, b, c };
+pub fn a(x: i32) -> i32 {x + 0}
+pub fn b(x: i32) -> i32 {x + 1}
+export fn c(x: i32) -> i32 {x + 2}
+ )SOURCE", 1, ".tmp_source.zig:2:37: error: expected type 'fn(i32) -> i32', got 'extern fn(i32) -> i32'");
}
//////////////////////////////////////////////////////////////////////////////