c variadic calls

This commit is contained in:
2026-06-14 15:53:45 +02:00
parent 638ca57f5c
commit 2d3d0bd266
21 changed files with 409 additions and 35 deletions
+154
View File
@@ -229,6 +229,38 @@ main :: func() void {}
testing.expect(t, module.functions[3].has_body)
}
@(test)
parser_accepts_terminal_c_variadic_markers_and_recovers_nonterminal_markers :: proc(t: ^testing.T) {
text := `fixed :: c_func(value c_int, ...) c_int
zero :: c_func(...) void
bad :: c_func(..., value c_int) c_int
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)
found_ellipsis := false
for tok in stream.items {
found_ellipsis = found_ellipsis || tok.kind == .Ellipsis
}
testing.expect(t, found_ellipsis)
testing.expect(t, module.functions[0].variadic)
testing.expect_value(t, len(module.functions[0].params), 1)
testing.expect(t, module.functions[1].variadic)
testing.expect_value(t, len(module.functions[1].params), 0)
testing.expect(t, module.functions[2].variadic)
testing.expect_value(t, len(module.functions[2].params), 1)
testing.expect_value(t, len(diagnostics.items), 1)
testing.expect(t, strings.contains(diagnostics.items[0].message, "final parameter"))
}
@(test)
parser_treats_c_as_contextual_only_before_func :: proc(t: ^testing.T) {
text := `c :: 5
@@ -680,6 +712,14 @@ c_primitives_remain_distinct_with_apple_silicon_representations :: proc(t: ^test
testing.expect_value(t, types.representation(types.C_FLOAT), types.F32)
testing.expect_value(t, types.representation(types.C_DOUBLE), types.F64)
testing.expect_value(t, types.representation(types.C_LONGDOUBLE), types.F64)
testing.expect_value(t, types.c_vararg_promotion(types.I8), types.C_INT)
testing.expect_value(t, types.c_vararg_promotion(types.U16), types.C_INT)
testing.expect_value(t, types.c_vararg_promotion(types.C_CHAR), types.C_INT)
testing.expect_value(t, types.c_vararg_promotion(types.C_USHORT), types.C_INT)
testing.expect_value(t, types.c_vararg_promotion(types.F32), types.C_DOUBLE)
testing.expect_value(t, types.c_vararg_promotion(types.C_FLOAT), types.C_DOUBLE)
testing.expect_value(t, types.c_vararg_promotion(types.U32), types.U32)
testing.expect_value(t, types.c_vararg_promotion(types.C_DOUBLE), types.C_DOUBLE)
testing.expect_value(t, target.llvm_triple(target.DEFAULT), "arm64-apple-macosx13.0.0")
}
@@ -744,6 +784,113 @@ main :: func() void {
testing.expect(t, strings.contains(llvm_text, "orelse_some"))
}
@(test)
c_variadic_calls_promote_extras_and_emit_variadic_llvm :: proc(t: ^testing.T) {
text := `variadic :: c_func(tag c_int, ...) c_int
zero :: c_func(...) void
main :: func() void {
narrow i8 :: -2
unsigned u16 :: 3
float_value f32 :: 4.0
c_float_value c_float :: 5.0
pointer *u8 :: "ok".ptr
nullable ?*u8 :: pointer
zero(pointer)
_ = variadic(7, narrow, unsigned, float_value, c_float_value, pointer, nullable)
}
`
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)
promotions := 0
for expr in hir_module.exprs {
if expr.kind == .C_Vararg_Promote {
promotions += 1
}
}
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect_value(t, promotions, 4)
found_hir_variadic := false
for function in hir_module.functions {
found_hir_variadic = found_hir_variadic || function.variadic
}
found_ir_variadic := false
for function in ir_module.functions {
found_ir_variadic = found_ir_variadic || function.variadic
}
testing.expect(t, found_hir_variadic)
testing.expect(t, found_ir_variadic)
testing.expect(t, strings.contains(llvm_text, "declare i32 @variadic(i32, ...)"))
testing.expect(t, strings.contains(llvm_text, "declare void @zero(...)"))
testing.expect(t, strings.contains(llvm_text, "sext i8"))
testing.expect(t, strings.contains(llvm_text, "zext i16"))
testing.expect(t, strings.contains(llvm_text, "fpext float"))
testing.expect(t, strings.contains(llvm_text, "call void (...) @zero(ptr"))
testing.expect(t, strings.contains(llvm_text, "call i32 (i32, ...) @variadic(i32 7, i32"))
testing.expect(t, strings.contains(llvm_text, "double"))
testing.expect(t, strings.contains(llvm_text, "ptr"))
}
@(test)
c_variadic_restrictions_and_extra_argument_types_are_diagnosed :: proc(t: ^testing.T) {
text := `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]
foreign(values)
requires()
}
`
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)
restricted := 0
found_extra := false
found_arity := false
for diagnostic in diagnostics.items {
if strings.contains(diagnostic.message, "must be a bodyless 'c_func' declaration") {
restricted += 1
}
found_extra = found_extra || strings.contains(diagnostic.message, "C variadic argument must be a concrete scalar or pointer")
found_arity = found_arity || strings.contains(diagnostic.message, "expects at least 1 arguments")
}
testing.expect_value(t, restricted, 2)
testing.expect(t, found_extra)
testing.expect(t, found_arity)
}
@(test)
variadicness_is_part_of_c_function_signature_compatibility :: proc(t: ^testing.T) {
fixed := ast.Function{result=types.C_INT}
variadic := ast.Function{result=types.C_INT, variadic=true}
testing.expect(t, !checker.function_signatures_equal(fixed, variadic))
testing.expect(t, !loader.function_signatures_equal(fixed, nil, types.C_INT, true))
}
@(test)
c_structs_are_pointer_only_and_may_be_opaque :: proc(t: ^testing.T) {
text := `Defined :: c_struct {
@@ -1683,6 +1830,7 @@ fake_cimport_backend :: proc(user_data: rawptr, request: cimport.Request, alloca
append(&result.functions, cimport.Function{
name=fmt.aprintf("fake_value", allocator=allocator),
result=cimport.Type_Id(0),
variadic=true,
reason=fmt.aprintf("", allocator=allocator),
})
result.available = true
@@ -1700,6 +1848,7 @@ cimport_backend_is_replaceable :: proc(t: ^testing.T) {
testing.expect_value(t, state.calls, 1)
testing.expect_value(t, len(result.functions), 1)
testing.expect_value(t, result.functions[0].name, "fake_value")
testing.expect(t, result.functions[0].variadic)
}
@(test)
@@ -1732,6 +1881,11 @@ loader_injects_and_caches_cimport_backend_per_compilation :: proc(t: ^testing.T)
testing.expect_value(t, len(module.imports), 2)
testing.expect_value(t, module.imports[0].target, module.imports[1].target)
testing.expect_value(t, len(module.functions), 2)
found_variadic := false
for function in module.functions {
found_variadic = found_variadic || function.variadic
}
testing.expect(t, found_variadic)
}
@(test)