function pointers and callbacks

This commit is contained in:
2026-06-15 21:09:46 +02:00
parent 3b7c3fcbd0
commit f5605fd3ec
21 changed files with 1927 additions and 122 deletions
+120 -11
View File
@@ -204,6 +204,34 @@ main :: func() void {}
nullable_child.many && nullable_child.has_sentinel)
}
@(test)
parser_accepts_c_function_pointer_types :: proc(t: ^testing.T) {
text := `take :: c_func(callback ?*c_func(value c_int) c_int) void
main :: func() void {}
`
source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
stream := lexer.lex(&source_file, &diagnostics, &symbols)
defer delete(stream.items)
module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&module)
optional, optional_ok := types.node(&module.type_store, module.functions[0].params[0].type)
pointer, pointer_ok := types.node(&module.type_store, optional.child)
function, function_ok := types.node(&module.type_store, pointer.child)
params := types.params_for(&module.type_store, pointer.child)
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect(t, optional_ok && optional.kind == .Optional)
testing.expect(t, pointer_ok && pointer.kind == .Pointer && pointer.many && !pointer.mutable)
testing.expect(t, function_ok && function.kind == .Function && function.c_abi && !function.variadic)
testing.expect(t, function.child == types.C_INT)
testing.expect_value(t, len(params), 1)
testing.expect(t, params[0].type == types.C_INT)
}
@(test)
parser_diagnoses_malformed_sentinel_pointer_types :: proc(t: ^testing.T) {
text := `bad :: func(value [*0]u8) void {}
@@ -1026,13 +1054,18 @@ main :: func() void {
@(test)
c_variadic_restrictions_and_extra_argument_types_are_diagnosed :: proc(t: ^testing.T) {
text := `foreign :: c_func(...) void
text := `Record :: c_struct {
value c_int
}
foreign :: c_func(...) void
requires :: c_func(value c_int, ...) void
native :: func(...) void
bodyful :: c_func(...) void {}
main :: func() void {
values [1]u8 :: [1]
record Record :: Record { value = 1 }
foreign(values)
foreign(record)
requires()
}
`
@@ -1072,15 +1105,21 @@ variadicness_is_part_of_c_function_signature_compatibility :: proc(t: ^testing.T
}
@(test)
c_structs_are_pointer_only_and_may_be_opaque :: proc(t: ^testing.T) {
c_structs_are_by_value_and_may_be_opaque :: proc(t: ^testing.T) {
text := `Defined :: c_struct {
value c_int
}
Opaque :: c_struct
Empty :: c_struct {}
Bad :: c_struct {
values []i32
}
read :: c_func(value @Defined) c_int
bad_param :: c_func(value Defined) void
bad_result :: c_func() Defined
main :: func() void {}
pass :: c_func(value Defined) Defined
bad_opaque :: c_func(value Opaque) void
main :: func() void {
_ = pass(Defined { value = 1 })
}
`
source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
@@ -1094,14 +1133,72 @@ main :: func() void {}
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
found_param := false
found_result := false
found_opaque := false
found_bad_layout := false
found_empty := false
for diagnostic in diagnostics.items {
found_param = found_param || strings.contains(diagnostic.message, "cannot be passed by value")
found_result = found_result || strings.contains(diagnostic.message, "cannot be returned by value")
found_opaque = found_opaque || strings.contains(diagnostic.message, "cannot be passed by value")
found_bad_layout = found_bad_layout || strings.contains(diagnostic.message, "C-layout-compatible")
found_empty = found_empty || strings.contains(diagnostic.message, "at least one field")
}
testing.expect(t, found_param)
testing.expect(t, found_result)
testing.expect(t, found_opaque)
testing.expect(t, found_bad_layout)
testing.expect(t, found_empty)
}
@(test)
aarch64_c_record_abi_classifies_fixed_parameters_and_results :: proc(t: ^testing.T) {
text := `Small :: c_struct {
left c_int
right c_int
}
Medium :: c_struct {
first c_int
second c_int
third c_int
}
Hfa :: c_struct {
x c_float
y c_float
}
Large :: c_struct {
first c_long
second c_long
third c_long
}
small :: c_func(value Small) Small
medium :: c_func(value Medium) Medium
hfa :: c_func(value Hfa) Hfa
large :: c_func(value Large) Large
main :: func() void {
_ = small(Small { left = 1, right = 2 })
_ = medium(Medium { first = 1, second = 2, third = 3 })
_ = hfa(Hfa { x = 1.0, y = 2.0 })
_ = large(Large { first = 1, second = 2, third = 3 })
}
`
source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
stream := lexer.lex(&source_file, &diagnostics, &symbols)
defer delete(stream.items)
ast_module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&ast_module)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
ir_module := lower.lower(&hir_module)
defer ir.destroy_module(&ir_module)
llvm_text := llvm.emit(&ir_module, &diagnostics, &symbols)
defer delete(llvm_text)
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect(t, strings.contains(llvm_text, "declare i64 @small(i64)"))
testing.expect(t, strings.contains(llvm_text, "declare [2 x i64] @medium([2 x i64])"))
testing.expect(t, strings.contains(llvm_text, "@hfa([2 x float])"))
testing.expect(t, strings.contains(llvm_text, "declare void @large(ptr sret("))
testing.expect(t, strings.contains(llvm_text, "call void @llvm.memcpy.p0.p0.i64"))
}
@(test)
@@ -1697,6 +1794,18 @@ restricted_c_header_imports_compile_and_link :: proc(t: ^testing.T) {
testing.expect_value(t, state.exit_code, 0)
}
@(test)
by_value_c_records_and_unions_compile_and_link :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-records"
defer _ = os.remove(output)
arguments := []linker.Argument{{kind=.Input, value="examples/interop/records/native.c"}}
c_options := cimport.Options{include_paths=[]string{"examples/interop/records/include"}}
status := compiler_core.compile_package("examples/interop/records/app", output, arguments, target.DEFAULT, c_options)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 1)
}
@(test)
unsupported_c_header_members_diagnose_only_when_referenced :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-header-unsupported"