bare func declaration identities (comptime)

This commit is contained in:
2026-07-18 14:05:55 +02:00
parent 85693e57e1
commit e889a99e55
8 changed files with 512 additions and 74 deletions
+162 -8
View File
@@ -2829,17 +2829,21 @@ main func() void {
@(test)
comptime_function_parameters_specialize_and_lower_directly :: proc(t: ^testing.T) {
text := `Callback_Config :: struct { call @func(value i32) i32 }
text := `Callback_Config :: struct { call func(value i32) i32 }
Callback_Choice :: union(enum) {
call func(value i32) i32
empty void
}
increment func(value i32) i32 { return value + 1 }
decrement func(value i32) i32 { return value - 1 }
external c_func(value i32) i32
apply func($callback @func(value i32) i32, value i32) i32 {
apply func($callback func(value i32) i32, value i32) i32 {
return callback(value)
}
apply_c func($callback *c_func(value i32) i32, value i32) i32 {
apply_c func($callback c_func(value i32) i32, value i32) i32 {
return callback(value)
}
@@ -2847,6 +2851,33 @@ apply_config func($config Callback_Config, value i32) i32 {
return config.call(value)
}
apply_array func($callbacks [2]func(value i32) i32, value i32) i32 {
return callbacks[0](value) + callbacks[1](value)
}
apply_optional func($callback ?func(value i32) i32, value i32) i32 {
return callback?(value)
}
apply_choice func($choice Callback_Choice, value i32) i32 {
return match choice {
.call |callback|: callback(value)
.empty: value
}
}
apply_pointer func($callback @func(value i32) i32, value i32) i32 {
return callback(value)
}
call_pointer func(callback @func(value i32) i32, value i32) i32 {
return callback(value)
}
materialize func($callback func(value i32) i32, value i32) i32 {
return call_pointer(callback, value)
}
main func() i32 {
a i32 :: apply(increment, 1)
b i32 :: apply(increment, 2)
@@ -2854,7 +2885,12 @@ main func() i32 {
d i32 :: apply(func(value i32) i32 { return value + 2 }, 4)
e i32 :: apply_c(external, 5)
f i32 :: apply_config(Callback_Config {call = increment}, 6)
return a + b + c + d + e + f
g i32 :: apply_array([increment, decrement], 7)
h i32 :: apply_optional(increment, 8)
i i32 :: apply_choice(Callback_Choice {call = increment}, 9)
j i32 :: apply_pointer(increment, 10)
k i32 :: materialize(increment, 11)
return a + b + c + d + e + f + g + h + i + j + k
}
`
stable_names: [dynamic]string
@@ -2875,11 +2911,21 @@ main func() i32 {
apply_count := 0
callback_specializations := 0
found_materialization := false
for function in ir_module.functions {
plain_apply := strings.contains(function.link_name, "bro__p0__apply__")
callback_specialization := plain_apply ||
strings.contains(function.link_name, "bro__p0__apply_c__") ||
strings.contains(function.link_name, "bro__p0__apply_config__")
strings.contains(function.link_name, "bro__p0__apply_config__") ||
strings.contains(function.link_name, "bro__p0__apply_array__") ||
strings.contains(function.link_name, "bro__p0__apply_optional__") ||
strings.contains(function.link_name, "bro__p0__apply_choice__") ||
strings.contains(function.link_name, "bro__p0__apply_pointer__")
if strings.contains(function.link_name, "bro__p0__materialize__") {
for instruction in function.instructions {
found_materialization = found_materialization || instruction.op == .Function_Address
}
}
if !callback_specialization {
continue
}
@@ -2906,7 +2952,8 @@ main func() i32 {
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect_value(t, apply_count, 3)
testing.expect_value(t, callback_specializations, 5)
testing.expect_value(t, callback_specializations, 9)
testing.expect(t, found_materialization)
ir.destroy_module(&ir_module)
hir.destroy_module(&hir_module)
@@ -2917,6 +2964,110 @@ main func() i32 {
}
}
@(test)
comptime_only_function_identities_reject_runtime_storage_and_abi_use :: proc(t: ^testing.T) {
text := `Callback :: alias func(value i32) i32
Config :: struct { callback Callback }
Bad_C :: c_struct { callback c_func(value i32) i32 }
increment func(value i32) i32 { return value + 1 }
bad_param func(callback Callback) i32 { return callback(1) }
bad_config func(config Config) i32 { return config.callback(1) }
bad_result func() Callback { return increment }
stored Callback = increment
main func() void {
local Callback = increment
_ = bad_result()
}
`
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)
found_param := false
found_config := false
found_result := false
found_global := false
found_local := false
found_c_field := false
for diagnostic in diagnostics.items {
found_param = found_param || strings.contains(diagnostic.message, "parameter 'callback' has a comptime-only type")
found_config = found_config || strings.contains(diagnostic.message, "parameter 'config' has a comptime-only type")
found_result = found_result || strings.contains(diagnostic.message, "has a comptime-only result")
found_global = found_global || strings.contains(diagnostic.message, "global 'stored' has a comptime-only type")
found_local = found_local || strings.contains(diagnostic.message, "local 'local' has a comptime-only type")
found_c_field = found_c_field || strings.contains(diagnostic.message, "c_struct fields must have C-layout-compatible types")
}
testing.expect(t, found_param)
testing.expect(t, found_config)
testing.expect(t, found_result)
testing.expect(t, found_global)
testing.expect(t, found_local)
testing.expect(t, found_c_field)
}
@(test)
function_pointers_do_not_coerce_back_to_bare_identities :: proc(t: ^testing.T) {
text := `Callback :: alias func(value i32) i32
increment func(value i32) i32 { return value + 1 }
apply func($callback Callback, value i32) i32 { return callback(value) }
pointer @func(value i32) i32 :: increment
main func() i32 { return apply(pointer, 1) }
`
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)
found := false
for diagnostic in diagnostics.items {
found = found || strings.contains(diagnostic.message, "comptime argument")
}
testing.expect(t, found)
}
@(test)
bodyless_c_function_identity_is_not_comptime_executable :: proc(t: ^testing.T) {
text := `external c_func(value i32) i32
apply_c func($callback c_func(value i32) i32, value i32) i32 { return callback(value) }
answer :: $apply_c(external, 1)
main func() i32 { return answer }
`
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)
found := false
for diagnostic in diagnostics.items {
found = found || strings.contains(diagnostic.message, "runtime-only")
}
testing.expect(t, found)
}
@(test)
milestone_37_expand_loop_control_must_be_statically_resolvable :: proc(t: ^testing.T) {
text := `main func() void {
@@ -4732,7 +4883,7 @@ main func() void {
found := false
for diagnostic in diagnostics.items {
found = found || strings.contains(diagnostic.message, "call target is not a function pointer")
found = found || strings.contains(diagnostic.message, "call target is not callable")
}
testing.expect(t, found)
}
@@ -8591,10 +8742,12 @@ Point :: alias facade.RenamedPoint
counter :: alias facade.counter
answer :: alias facade.answer
`
app_text := `dep :: import "../dep"
app_text := `dep :: import "../dep"
facade :: import "../facade"
top :: import "../top"
apply func($callback func() i32) i32 { return callback() }
main func() i32 {
box top.Box(i32) :: top.Box(i32) { value = 2 }
point top.Point :: top.Point { value = 3 }
@@ -8603,6 +8756,7 @@ main func() i32 {
top.counter = 7
if box.value != 2 or point.value != 3 { return 1 }
if scalar != 5 or top.answer() != 40 or facade.local_answer() != 40 or dep.counter != 7 { return 2 }
if apply(top.answer) != 40 or apply(facade.answer) != 40 { return 3 }
_ = maybe
return 0
}