extern variables and object-like macro consts

This commit is contained in:
2026-06-17 21:52:05 +02:00
parent f5605fd3ec
commit 19e9fbdd4b
33 changed files with 3136 additions and 116 deletions
+724
View File
@@ -992,6 +992,31 @@ main :: func() void {
testing.expect(t, sentinel_error)
}
@(test)
immutable_pointer_and_slice_bindings_preserve_mutable_pointees :: proc(t: ^testing.T) {
text := `main :: func() void {
values [2]mut u8 = [1, 2]
pointer *mut u8 :: (&values).ptr
slice []mut u8 :: values[0..]
pointer[0] = 3
slice[1] = 4
}
`
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)
testing.expect_value(t, len(diagnostics.items), 0)
}
@(test)
c_variadic_calls_promote_extras_and_emit_variadic_llvm :: proc(t: ^testing.T) {
text := `variadic :: c_func(tag c_int, ...) c_int
@@ -2118,6 +2143,14 @@ Fake_Cimport_State :: struct {
saw_options: bool,
}
Conflict_Cimport_State :: struct {
calls: int,
}
Symbol_Conflict_Cimport_State :: struct {
calls: int,
}
fake_cimport_backend :: proc(user_data: rawptr, request: cimport.Request, allocator: mem.Allocator) -> cimport.Result {
state := (^Fake_Cimport_State)(user_data)
state.calls += 1
@@ -2142,10 +2175,143 @@ fake_cimport_backend :: proc(user_data: rawptr, request: cimport.Request, alloca
variadic=true,
reason=fmt.aprintf("", allocator=allocator),
})
append(&result.variables, cimport.Variable{
name=fmt.aprintf("fake_global", allocator=allocator),
type=cimport.Type_Id(0),
mutable=true,
reason=fmt.aprintf("", allocator=allocator),
})
append(&result.macros, cimport.Macro_Constant{
name=fmt.aprintf("FAKE_MAGIC", allocator=allocator),
type=cimport.Type_Id(0),
value={kind=.Integer, type=cimport.Type_Id(0), integer=7},
reason=fmt.aprintf("", allocator=allocator),
})
result.available = true
return result
}
conflict_cimport_backend :: proc(user_data: rawptr, request: cimport.Request, allocator: mem.Allocator) -> cimport.Result {
state := (^Conflict_Cimport_State)(user_data)
state.calls += 1
result := cimport.init_result(allocator)
kind := cimport.Type_Kind.C_Int
if strings.has_suffix(request.path, "second.h") {
kind = .C_Long
}
append(&result.types, cimport.Type{kind=kind, child=cimport.INVALID_TYPE})
append(&result.variables, cimport.Variable{
name=fmt.aprintf("conflict_global", allocator=allocator),
type=cimport.Type_Id(0),
mutable=true,
reason=fmt.aprintf("", allocator=allocator),
})
result.available = true
return result
}
symbol_conflict_cimport_backend :: proc(user_data: rawptr, request: cimport.Request, allocator: mem.Allocator) -> cimport.Result {
state := (^Symbol_Conflict_Cimport_State)(user_data)
state.calls += 1
result := cimport.init_result(allocator)
append(&result.types, cimport.Type{kind=.C_Int, child=cimport.INVALID_TYPE})
if strings.has_suffix(request.path, "variable.h") {
append(&result.variables, cimport.Variable{
name=fmt.aprintf("conflict_symbol", allocator=allocator),
type=cimport.Type_Id(0),
mutable=true,
reason=fmt.aprintf("", allocator=allocator),
})
} else {
append(&result.functions, cimport.Function{
name=fmt.aprintf("conflict_symbol", allocator=allocator),
result=cimport.Type_Id(0),
reason=fmt.aprintf("", allocator=allocator),
})
}
result.available = true
return result
}
main_conflict_cimport_backend :: proc(_: rawptr, _: cimport.Request, allocator: mem.Allocator) -> cimport.Result {
result := cimport.init_result(allocator)
append(&result.types, cimport.Type{kind=.C_Int, child=cimport.INVALID_TYPE})
append(&result.variables, cimport.Variable{
name=fmt.aprintf("main", allocator=allocator),
type=cimport.Type_Id(0),
mutable=true,
reason=fmt.aprintf("", allocator=allocator),
})
result.available = true
return result
}
write_conflict_cimport_backend :: proc(_: rawptr, _: cimport.Request, allocator: mem.Allocator) -> cimport.Result {
result := cimport.init_result(allocator)
append(&result.types, cimport.Type{kind=.C_Int, child=cimport.INVALID_TYPE})
append(&result.variables, cimport.Variable{
name=fmt.aprintf("write", allocator=allocator),
type=cimport.Type_Id(0),
mutable=true,
reason=fmt.aprintf("", allocator=allocator),
})
result.available = true
return result
}
count_substring_occurrences :: proc(text, needle: string) -> int {
if len(needle) == 0 {
return 0
}
count := 0
for index := 0; index + len(needle) <= len(text); index += 1 {
if text[index:index + len(needle)] == needle {
count += 1
}
}
return count
}
find_cimport_variable :: proc(result: ^cimport.Result, name: string) -> (^cimport.Variable, bool) {
for &variable in result.variables {
if variable.name == name {
return &variable, true
}
}
return nil, false
}
count_cimport_variables :: proc(result: ^cimport.Result, name: string) -> int {
count := 0
for variable in result.variables {
if variable.name == name {
count += 1
}
}
return count
}
find_cimport_macro :: proc(result: ^cimport.Result, name: string) -> (^cimport.Macro_Constant, bool) {
for &macro in result.macros {
if macro.name == name {
return &macro, true
}
}
return nil, false
}
cimport_has_named_result :: proc(result: ^cimport.Result, name: string) -> bool {
if _, ok := find_cimport_macro(result, name); ok {
return true
}
for item in result.unsupported {
if item.name == name {
return true
}
}
return false
}
@(test)
cimport_backend_is_replaceable :: proc(t: ^testing.T) {
state := Fake_Cimport_State{available=true}
@@ -2156,10 +2322,238 @@ cimport_backend_is_replaceable :: proc(t: ^testing.T) {
testing.expect(t, result.available)
testing.expect_value(t, state.calls, 1)
testing.expect_value(t, len(result.functions), 1)
testing.expect_value(t, len(result.variables), 1)
testing.expect_value(t, len(result.macros), 1)
testing.expect_value(t, result.functions[0].name, "fake_value")
testing.expect_value(t, result.variables[0].name, "fake_global")
testing.expect_value(t, result.macros[0].name, "FAKE_MAGIC")
testing.expect(t, result.functions[0].variadic)
}
@(test)
libclang_import_preserves_external_object_and_final_macro_semantics :: proc(t: ^testing.T) {
options := cimport.Options{
include_paths=[]string{"examples/interop/header/include"},
defines=[]string{"BROLANG_FEATURE"},
}
result := cimport.import_header(
options,
"examples/interop/header/include/native.h",
target.DEFAULT,
)
defer cimport.destroy_result(&result)
testing.expect(t, result.available)
testing.expect_value(t, result.error_message, "")
tls, found_tls := find_cimport_variable(&result, "imported_tls_global")
testing.expect(t, found_tls)
if found_tls {
testing.expect(t, strings.contains(tls.reason, "thread-local C variables are not supported"))
}
const_array, found_const_array := find_cimport_variable(&result, "imported_const_array")
testing.expect(t, found_const_array)
if found_const_array {
testing.expect(t, !const_array.mutable)
testing.expect(t, const_array.type != cimport.INVALID_TYPE)
if const_array.type != cimport.INVALID_TYPE {
array_type := result.types[const_array.type]
testing.expect_value(t, array_type.kind, cimport.Type_Kind.Array)
testing.expect(t, array_type.child != cimport.INVALID_TYPE)
if array_type.child != cimport.INVALID_TYPE {
testing.expect_value(t, result.types[array_type.child].kind, cimport.Type_Kind.C_Int)
}
}
}
typedef_const_array, found_typedef_const_array := find_cimport_variable(
&result, "imported_typedef_const_array",
)
testing.expect(t, found_typedef_const_array)
if found_typedef_const_array {
testing.expect(t, !typedef_const_array.mutable)
testing.expect_value(t, typedef_const_array.reason, "")
testing.expect(t, typedef_const_array.type != cimport.INVALID_TYPE)
if typedef_const_array.type != cimport.INVALID_TYPE {
array_type := result.types[typedef_const_array.type]
testing.expect_value(t, array_type.kind, cimport.Type_Kind.Array)
testing.expect(t, !array_type.mutable)
testing.expect_value(t, array_type.count, u64(2))
}
}
redeclared_array, found_redeclared_array := find_cimport_variable(
&result, "imported_redeclared_array",
)
testing.expect(t, found_redeclared_array)
testing.expect_value(t, count_cimport_variables(&result, "imported_redeclared_array"), 1)
if found_redeclared_array {
testing.expect_value(t, redeclared_array.reason, "")
testing.expect(t, redeclared_array.type != cimport.INVALID_TYPE)
if redeclared_array.type != cimport.INVALID_TYPE {
array_type := result.types[redeclared_array.type]
testing.expect_value(t, array_type.kind, cimport.Type_Kind.Array)
testing.expect_value(t, array_type.count, u64(4))
}
}
repeated, found_repeated := find_cimport_macro(&result, "IMPORTED_REPEAT")
testing.expect(t, found_repeated)
if found_repeated {
testing.expect_value(t, repeated.value.integer, u64(123))
}
testing.expect(t, !cimport_has_named_result(&result, "IMPORTED_GUARDED"))
testing.expect(t, !cimport_has_named_result(&result, "IMPORTED_ONCE"))
negative_decimal, found_negative_decimal := find_cimport_macro(&result, "IMPORTED_NEG_DECIMAL")
testing.expect(t, found_negative_decimal)
if found_negative_decimal {
testing.expect_value(t, result.types[negative_decimal.type].kind, cimport.Type_Kind.C_Long)
testing.expect_value(t, negative_decimal.value.integer, u64(2147483648))
testing.expect(t, negative_decimal.value.negative)
}
negative_hex, found_negative_hex := find_cimport_macro(&result, "IMPORTED_NEG_HEX")
testing.expect(t, found_negative_hex)
if found_negative_hex {
testing.expect_value(t, result.types[negative_hex.type].kind, cimport.Type_Kind.C_Uint)
testing.expect_value(t, negative_hex.value.integer, u64(0x80000000))
testing.expect(t, !negative_hex.value.negative)
}
negative_uint, found_negative_uint := find_cimport_macro(&result, "IMPORTED_NEG_UINT")
testing.expect(t, found_negative_uint)
if found_negative_uint {
testing.expect_value(t, result.types[negative_uint.type].kind, cimport.Type_Kind.C_Uint)
testing.expect_value(t, negative_uint.value.integer, u64(0xffffffff))
testing.expect(t, !negative_uint.value.negative)
}
conversions, found_conversions := find_cimport_macro(&result, "IMPORTED_CONVERSIONS")
testing.expect(t, found_conversions)
if found_conversions {
testing.expect_value(t, len(conversions.values), 5)
expected_kinds := [?]cimport.Type_Kind{
.C_Int,
.C_Double,
.C_Int,
.C_Float,
.C_Int,
}
for value, index in conversions.values {
testing.expect(t, value.type != cimport.INVALID_TYPE)
if value.type != cimport.INVALID_TYPE {
testing.expect_value(t, result.types[value.type].kind, expected_kinds[index])
}
}
}
}
@(test)
final_macros_override_same_named_c_value_declarations :: proc(t: ^testing.T) {
sources := source.init_store()
defer source.destroy_store(&sources)
diagnostics := source.init_store_diagnostics(&sources)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
c_options := cimport.Options{
include_paths=[]string{"examples/interop/header/include"},
defines=[]string{"BROLANG_FEATURE"},
}
module, loaded := loader.load(
"examples/interop/header/app",
&sources,
&diagnostics,
&symbols,
c_options=c_options,
)
defer ast.destroy_module(&module)
testing.expect(t, loaded)
hir_module := checker.check(&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, "@IMPORTED_SHADOW_OBJECT = external"))
testing.expect(t, !strings.contains(llvm_text, "declare i32 @IMPORTED_SHADOW_FUNCTION("))
testing.expect(t, !strings.contains(llvm_text, "@IMPORTED_SHADOW_UNSUPPORTED = external"))
testing.expect(t, !strings.contains(llvm_text, "@IMPORTED_EMPTY_SHADOW = external"))
}
@(test)
static_inline_c_functions_route_through_generated_trampolines :: proc(t: ^testing.T) {
sources := source.init_store()
defer source.destroy_store(&sources)
diagnostics := source.init_store_diagnostics(&sources)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
c_options := cimport.Options{
include_paths=[]string{"examples/interop/header/include"},
defines=[]string{"BROLANG_FEATURE"},
}
module, loaded := loader.load(
"examples/interop/header/app",
&sources,
&diagnostics,
&symbols,
c_options=c_options,
)
defer ast.destroy_module(&module)
testing.expect(t, loaded)
// Symbols are namespaced by a header-path hash, so match by suffix.
scalar_symbol := ""
record_symbol := ""
for trampoline in module.c_trampolines {
testing.expect(t, strings.has_prefix(trampoline.symbol, "__brolang_inline_"))
if strings.has_suffix(trampoline.symbol, "_imported_inline") {
scalar_symbol = trampoline.symbol
}
if strings.has_suffix(trampoline.symbol, "_imported_inline_record") {
record_symbol = trampoline.symbol
}
// The variadic static inline is unsupported and must not be wrapped.
testing.expect(t, !strings.has_suffix(trampoline.symbol, "_imported_inline_variadic"))
}
testing.expect(t, scalar_symbol != "")
testing.expect(t, record_symbol != "")
// A static inline whose signature translates but is rejected by the loader's
// by-value layout checks keeps its cimport-assigned link_name yet must not
// emit a wrapper — it is uncallable, so the wrapper would be dead code.
bad_layout_link := ""
for function in module.functions {
if symbol.resolve(&symbols, function.name) == "imported_inline_bad_layout" {
testing.expect(t, len(function.unsupported_reason) > 0)
bad_layout_link = function.link_name
}
}
testing.expect(t, bad_layout_link != "") // cimport did generate a wrapper symbol
for trampoline in module.c_trampolines {
testing.expect(t, trampoline.symbol != bad_layout_link)
}
hir_module := checker.check(&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, fmt.tprintf("@%s(", scalar_symbol)))
testing.expect(t, strings.contains(llvm_text, fmt.tprintf("@%s(", record_symbol)))
// The internal-linkage C symbol itself is never declared or called directly.
testing.expect(t, !strings.contains(llvm_text, "@imported_inline("))
}
@(test)
loader_injects_and_caches_cimport_backend_per_compilation :: proc(t: ^testing.T) {
sources := source.init_store()
@@ -2195,6 +2589,336 @@ loader_injects_and_caches_cimport_backend_per_compilation :: proc(t: ^testing.T)
found_variadic = found_variadic || function.variadic
}
testing.expect(t, found_variadic)
found_external := false
found_macro := false
for global in module.globals {
name := symbol.resolve(&symbols, global.name)
found_external = found_external || (name == "fake_global" && global.external && global.writable)
found_macro = found_macro || name == "FAKE_MAGIC"
}
testing.expect(t, found_external)
testing.expect(t, found_macro)
}
@(test)
conflicting_external_c_globals_are_diagnosed_and_deduped :: proc(t: ^testing.T) {
sources := source.init_store()
defer source.destroy_store(&sources)
diagnostics := source.init_store_diagnostics(&sources)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
state := Conflict_Cimport_State{}
options := cimport.Options{backend={import_header=conflict_cimport_backend, user_data=&state}}
module, loaded := loader.load(
"examples/interop/header_conflict",
&sources,
&diagnostics,
&symbols,
c_options=options,
)
defer ast.destroy_module(&module)
testing.expect(t, loaded)
testing.expect_value(t, state.calls, 2)
hir_module := checker.check(&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)
found_conflict := false
for diagnostic in diagnostics.items {
if strings.contains(diagnostic.message, "conflicting external C variable declarations for 'conflict_global'") {
found_conflict = true
}
}
testing.expect(t, found_conflict)
testing.expect_value(t, count_substring_occurrences(llvm_text, "@conflict_global = external global"), 1)
testing.expect(t, strings.contains(llvm_text, "@conflict_global = external global i32"))
testing.expect(t, !strings.contains(llvm_text, "@conflict_global = external global i64"))
}
@(test)
external_c_global_and_function_link_name_conflict_is_diagnosed :: proc(t: ^testing.T) {
sources := source.init_store()
defer source.destroy_store(&sources)
diagnostics := source.init_store_diagnostics(&sources)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
state := Symbol_Conflict_Cimport_State{}
options := cimport.Options{backend={import_header=symbol_conflict_cimport_backend, user_data=&state}}
module, loaded := loader.load(
"examples/interop/header_symbol_conflict",
&sources,
&diagnostics,
&symbols,
c_options=options,
)
defer ast.destroy_module(&module)
testing.expect(t, loaded)
testing.expect_value(t, state.calls, 2)
hir_module := checker.check(&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)
found_conflict := false
for diagnostic in diagnostics.items {
if strings.contains(diagnostic.message, "external C variable 'conflict_symbol' conflicts with a C function declaration") {
found_conflict = true
}
}
testing.expect(t, found_conflict)
testing.expect(t, !strings.contains(llvm_text, "@conflict_symbol = external global"))
testing.expect(t, strings.contains(llvm_text, "declare i32 @conflict_symbol()"))
testing.expect(t, !strings.contains(llvm_text, "load i32, ptr @conflict_symbol"))
}
@(test)
external_c_global_named_main_is_omitted_for_root_entry_point :: proc(t: ^testing.T) {
sources := source.init_store()
defer source.destroy_store(&sources)
diagnostics := source.init_store_diagnostics(&sources)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
options := cimport.Options{backend={import_header=main_conflict_cimport_backend}}
module, loaded := loader.load(
"examples/interop/header_main_conflict",
&sources,
&diagnostics,
&symbols,
c_options=options,
)
defer ast.destroy_module(&module)
testing.expect(t, loaded)
hir_module := checker.check(&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)
found_conflict := false
for diagnostic in diagnostics.items {
found_conflict =
found_conflict ||
strings.contains(
diagnostic.message,
"external C variable 'main' conflicts with the program entry point",
)
}
testing.expect(t, found_conflict)
testing.expect_value(t, count_substring_occurrences(llvm_text, "define i32 @main("), 1)
testing.expect(t, !strings.contains(llvm_text, "@main = external"))
}
@(test)
external_c_global_named_main_is_omitted_for_synthesized_entry_point :: proc(t: ^testing.T) {
sources := source.init_store()
defer source.destroy_store(&sources)
diagnostics := source.init_store_diagnostics(&sources)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
options := cimport.Options{backend={import_header=main_conflict_cimport_backend}}
module, loaded := loader.load(
"examples/interop/header_main_conflict_missing",
&sources,
&diagnostics,
&symbols,
c_options=options,
)
defer ast.destroy_module(&module)
testing.expect(t, loaded)
hir_module := checker.check(&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)
found_conflict := false
found_missing_main := false
for diagnostic in diagnostics.items {
found_conflict =
found_conflict ||
strings.contains(
diagnostic.message,
"external C variable 'main' conflicts with the program entry point",
)
found_missing_main =
found_missing_main ||
strings.contains(diagnostic.message, "missing or unusable main function")
}
testing.expect(t, found_conflict)
testing.expect(t, found_missing_main)
testing.expect_value(t, count_substring_occurrences(llvm_text, "define i32 @main("), 1)
testing.expect(t, !strings.contains(llvm_text, "@main = external"))
}
@(test)
external_c_global_named_write_is_omitted_for_compiler_runtime :: proc(t: ^testing.T) {
sources := source.init_store()
defer source.destroy_store(&sources)
diagnostics := source.init_store_diagnostics(&sources)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
options := cimport.Options{backend={import_header=write_conflict_cimport_backend}}
module, loaded := loader.load(
"examples/interop/header_write_conflict",
&sources,
&diagnostics,
&symbols,
c_options=options,
)
defer ast.destroy_module(&module)
testing.expect(t, loaded)
hir_module := checker.check(&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)
found_conflict := false
for diagnostic in diagnostics.items {
found_conflict =
found_conflict ||
strings.contains(
diagnostic.message,
"external C variable 'write' conflicts with the compiler runtime",
)
}
testing.expect(t, found_conflict)
testing.expect_value(t, count_substring_occurrences(llvm_text, "declare i64 @write("), 1)
testing.expect(t, !strings.contains(llvm_text, "@write = external"))
testing.expect(t, strings.contains(llvm_text, "call void @bro.trap"))
}
@(test)
tls_reference_and_const_external_array_assignment_are_diagnosed :: proc(t: ^testing.T) {
sources := source.init_store()
defer source.destroy_store(&sources)
diagnostics := source.init_store_diagnostics(&sources)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
c_options := cimport.Options{include_paths=[]string{"examples/interop/header/include"}}
module, loaded := loader.load(
"examples/interop/header_unsupported",
&sources,
&diagnostics,
&symbols,
c_options=c_options,
)
defer ast.destroy_module(&module)
testing.expect(t, loaded)
hir_module := checker.check(&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)
found_tls := false
found_excess_aggregate := false
found_signed_narrow := false
found_shadowed_unsupported := false
found_float_overflow := false
found_empty_shadow := false
found_inline_variadic := false
not_writable_count := 0
for diagnostic in diagnostics.items {
found_tls =
found_tls ||
strings.contains(
diagnostic.message,
"C declaration 'imported_tls_global' is unavailable: thread-local C variables are not supported",
)
found_excess_aggregate =
found_excess_aggregate ||
strings.contains(
diagnostic.message,
"C declaration 'IMPORTED_TOO_MANY_COLOR' is unavailable: C macro aggregate initializer is not representable",
)
found_signed_narrow =
found_signed_narrow ||
strings.contains(
diagnostic.message,
"C declaration 'IMPORTED_SIGNED_NARROW_BAD' is unavailable: C macro aggregate initializer is not representable",
)
found_shadowed_unsupported =
found_shadowed_unsupported ||
strings.contains(
diagnostic.message,
"C declaration 'IMPORTED_SHADOW_UNSUPPORTED' is unavailable: C macro is not a supported constant",
)
found_float_overflow =
found_float_overflow ||
strings.contains(
diagnostic.message,
"C declaration 'IMPORTED_FLOAT_OVERFLOW' is unavailable: C macro is not a supported constant",
)
found_empty_shadow =
found_empty_shadow ||
strings.contains(
diagnostic.message,
"C declaration 'IMPORTED_EMPTY_SHADOW' is unavailable: C macro has no replacement value",
)
found_inline_variadic =
found_inline_variadic ||
strings.contains(
diagnostic.message,
"C declaration 'imported_inline_variadic' is unavailable: variadic static inline C functions are not supported",
)
if strings.contains(diagnostic.message, "assignment target is not writable") {
not_writable_count += 1
}
}
testing.expect(t, found_tls)
testing.expect(t, found_excess_aggregate)
testing.expect(t, found_signed_narrow)
testing.expect(t, found_shadowed_unsupported)
testing.expect(t, found_float_overflow)
testing.expect(t, found_empty_shadow)
testing.expect(t, found_inline_variadic)
testing.expect(t, not_writable_count >= 5)
testing.expect(t, !strings.contains(llvm_text, "@IMPORTED_EMPTY_SHADOW = external"))
testing.expect(
t,
strings.contains(
llvm_text,
"@imported_typedef_const_array = external constant [2 x i32]",
),
)
testing.expect_value(
t,
count_substring_occurrences(
llvm_text,
"@imported_redeclared_array = external global [4 x i32]",
),
1,
)
testing.expect(
t,
!strings.contains(
llvm_text,
"ptr @imported_const_array_record, i64 0",
),
)
testing.expect(t, !strings.contains(llvm_text, "@IMPORTED_SHADOW_UNSUPPORTED = external"))
}
@(test)