Files
brolang/compiler_tests.odin
T
2026-06-12 18:07:21 +02:00

1542 lines
53 KiB
Odin

package main
import compiler_core "./compiler"
import "./compiler/ast"
import "./compiler/backend"
import "./compiler/checker"
import "./compiler/hir"
import "./compiler/ir"
import "./compiler/lexer"
import "./compiler/linker"
import "./compiler/loader"
import "./compiler/llvm"
import "./compiler/lower"
import "./compiler/parser"
import "./compiler/source"
import "./compiler/symbol"
import "./compiler/token"
import "./compiler/types"
import "core:fmt"
import "core:os"
import "core:os/os2"
import "core:strings"
import "core:testing"
@(test)
symbol_table_deduplicates_and_owns_spellings :: proc(t: ^testing.T) {
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
buffer := [5]byte{'a', 'l', 'p', 'h', 'a'}
alpha := symbol.intern(&symbols, string(buffer[:]))
duplicate := symbol.intern(&symbols, "alpha")
beta := symbol.intern(&symbols, "beta")
buffer[0] = 'x'
testing.expect_value(t, alpha, duplicate)
testing.expect(t, alpha != beta)
testing.expect_value(t, symbol.resolve(&symbols, alpha), "alpha")
testing.expect_value(t, symbol.resolve(&symbols, beta), "beta")
testing.expect_value(t, symbol.intern(&symbols, ""), symbol.INVALID)
testing.expect_value(t, symbol.resolve(&symbols, symbol.INVALID), "")
testing.expect(t, !symbol.is_valid(symbol.INVALID))
testing.expect(t, symbol.is_valid(alpha))
}
@(test)
compact_tokens_intern_only_identifiers_and_preserve_parser_text :: proc(t: ^testing.T) {
text := `other :: import "../math"
value :: 42
main :: func() void { _ = value }
`
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)
value_symbol := symbol.intern(&symbols, "value")
sink_symbol := symbol.intern(&symbols, "_")
value_count := 0
for tok in stream.items {
#partial switch tok.kind {
case .Identifier:
if tok.symbol == value_symbol {
value_count += 1
}
case .Underscore:
testing.expect_value(t, tok.symbol, sink_symbol)
case:
testing.expect_value(t, tok.symbol, symbol.INVALID)
}
}
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect_value(t, value_count, 2)
testing.expect_value(t, module.imports[0].path, "../math")
testing.expect_value(t, module.exprs[module.globals[0].expr].integer, i64(42))
testing.expect_value(t, module.statements[module.functions[0].body[0]].name, sink_symbol)
}
@(test)
lexer_preserves_newlines_and_skips_comments :: proc(t: ^testing.T) {
source_file := source.Source{path="test.bro", text="# comment\nmain :: func() void {}\n"}
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)
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect_value(t, stream.items[0].kind, token.Kind.Newline)
testing.expect_value(t, stream.items[1].kind, token.Kind.Identifier)
}
@(test)
parser_accepts_grouped_params_and_multiline_statements :: proc(t: ^testing.T) {
text := `sum :: func(a,
b int) int {
return (a
+ b)
}
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)
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect_value(t, len(module.functions), 2)
testing.expect_value(t, len(module.functions[0].params), 2)
}
@(test)
parser_accepts_single_statement_one_line_functions :: proc(t: ^testing.T) {
text := `give :: func() i8 { return 7 }
main :: func() void { _ = give() }
`
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)
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect_value(t, len(module.functions), 2)
testing.expect_value(t, len(module.functions[0].body), 1)
testing.expect_value(t, len(module.functions[1].body), 1)
testing.expect_value(t, module.statements[module.functions[0].body[0]].kind, ast.Stmt_Kind.Return)
testing.expect_value(t, module.statements[module.functions[1].body[0]].kind, ast.Stmt_Kind.Assignment)
}
@(test)
parser_distinguishes_bodyless_declarations_and_definitions :: proc(t: ^testing.T) {
text := `foreign :: c func(value i32) i32
defined :: c func(value i32) i32
{
return value
}
native :: func() i32
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)
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect_value(t, len(module.functions), 4)
testing.expect(t, !module.functions[0].has_body)
testing.expect(t, module.functions[0].c_abi)
testing.expect(t, module.functions[1].has_body)
testing.expect(t, module.functions[1].c_abi)
testing.expect_value(t, len(module.functions[1].body), 1)
testing.expect(t, !module.functions[2].has_body)
testing.expect(t, !module.functions[2].c_abi)
testing.expect(t, module.functions[3].has_body)
}
@(test)
cli_parses_ordered_link_options_and_rejects_invalid_forms :: proc(t: ^testing.T) {
options, valid := parse_cli_args([]string{
"brolang",
"app",
"--link",
"native.c",
"-o",
"app.out",
"--library-path",
"vendor/lib",
"--library",
"thing",
"--link",
"helper.o",
})
defer delete(options.link_arguments)
testing.expect(t, valid)
testing.expect_value(t, options.input_path, "app")
testing.expect_value(t, options.output_path, "app.out")
testing.expect_value(t, len(options.link_arguments), 4)
testing.expect_value(t, options.link_arguments[0].kind, linker.Kind.Input)
testing.expect_value(t, options.link_arguments[0].value, "native.c")
testing.expect_value(t, options.link_arguments[1].kind, linker.Kind.Library_Path)
testing.expect_value(t, options.link_arguments[2].kind, linker.Kind.Library)
testing.expect_value(t, options.link_arguments[3].value, "helper.o")
_, unknown_valid := parse_cli_args([]string{"brolang", "app", "-o", "out", "--unknown", "value"})
_, incomplete_valid := parse_cli_args([]string{"brolang", "app", "-o"})
_, duplicate_output_valid := parse_cli_args([]string{"brolang", "app", "-o", "one", "-o", "two"})
_, duplicate_empty_output_valid := parse_cli_args([]string{"brolang", "app", "-o", "", "-o", "two"})
testing.expect(t, !unknown_valid)
testing.expect(t, !incomplete_valid)
testing.expect(t, !duplicate_output_valid)
testing.expect(t, !duplicate_empty_output_valid)
}
@(test)
parser_accepts_bare_and_aliased_imports :: proc(t: ^testing.T) {
text := `import
"../math"
other :: import "../math"
escaped :: import "dir\"name\\tail"
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)
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect_value(t, len(module.imports), 3)
testing.expect_value(t, symbol.resolve(&symbols, module.imports[0].alias), "")
testing.expect_value(t, module.imports[0].path, "../math")
testing.expect_value(t, symbol.resolve(&symbols, module.imports[1].alias), "other")
testing.expect_value(t, module.imports[2].path, "dir\"name\\tail")
}
@(test)
parser_rejects_chained_package_access :: proc(t: ^testing.T) {
text := `main :: func() void {
_ = first.second.value
}
`
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)
testing.expect(t, len(diagnostics.items) > 0)
}
@(test)
lexer_diagnoses_invalid_import_strings :: proc(t: ^testing.T) {
text := "import \"bad\\q\"\nimport \"unterminated\n"
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)
testing.expect_value(t, len(diagnostics.items), 2)
}
@(test)
package_loader_discovers_lexical_immediate_bro_files :: 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)
module, loaded := loader.load("examples/packages/basic/app", &sources, &diagnostics, &symbols)
defer ast.destroy_module(&module)
testing.expect(t, loaded)
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect_value(t, len(module.packages), 2)
testing.expect_value(t, len(module.files), 3)
testing.expect(t, strings.has_suffix(sources.items[module.files[0].source].path, "/main.bro"))
testing.expect(t, strings.has_suffix(sources.items[module.files[1].source].path, "/value.bro"))
testing.expect(t, strings.has_suffix(sources.items[module.files[2].source].path, "/math.bro"))
}
@(test)
multi_source_diagnostics_report_the_originating_file :: 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)
module, loaded := loader.load("examples/packages/file_local/app", &sources, &diagnostics, &symbols)
defer ast.destroy_module(&module)
hir_module := checker.check(&module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
testing.expect(t, loaded)
found := false
for _, diagnostic_id in diagnostics.items {
message := source.format(&diagnostics, diagnostic_id)
if strings.contains(message, "/b.bro:2:9:") &&
strings.contains(message, "unknown package alias 'math'") {
found = true
}
delete(message)
}
testing.expect(t, found)
}
@(test)
semantic_lookup_diagnoses_wrong_declaration_kinds :: proc(t: ^testing.T) {
text := `value :: 1
give :: func() i8 {
return 1
}
main :: func() void {
_ = value()
_ = give
}
`
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_global := false
found_function := false
for diagnostic in diagnostics.items {
found_global = found_global || strings.contains(diagnostic.message, "'value' is a global, not a function")
found_function = found_function || strings.contains(diagnostic.message, "'give' is a function, not a global value")
}
testing.expect(t, found_global)
testing.expect(t, found_function)
}
@(test)
pipeline_emits_specialized_calling_conventions_and_checked_add :: proc(t: ^testing.T) {
text := `sum_c :: c func(a, b int) int {
return a + b
}
sum_bro :: func(a, b int) int {
return a + b
}
main :: func() void {
_ = sum_c(1, 2)
_ = sum_bro(1, 2)
}
`
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)
second_llvm_text := llvm.emit(&ir_module, &diagnostics, &symbols)
defer delete(second_llvm_text)
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect_value(t, llvm_text, second_llvm_text)
testing.expect(t, strings.contains(llvm_text, "define i8 @bro_c__p0__sum_c__i8__i8"))
testing.expect(t, strings.contains(llvm_text, "define internal fastcc i8 @bro__p0__sum_bro__i8__i8"))
testing.expect(t, strings.contains(llvm_text, "@llvm.sadd.with.overflow.i8"))
}
@(test)
pipeline_emits_only_referenced_foreign_declarations_with_exact_names :: proc(t: ^testing.T) {
text := `used :: c func(a, b i32) i32
unused :: c func() i32
bodyful :: c func(value i32) i32 {
return value
}
main :: func() void {
_ = used(1, 2)
_ = bodyful(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 i32 @used(i32, i32)"))
testing.expect(t, strings.contains(llvm_text, "call i32 @used(i32 1, i32 2)"))
testing.expect(t, !strings.contains(llvm_text, "@unused("))
testing.expect(t, strings.contains(llvm_text, "define i32 @bro_c__p0__bodyful__i32"))
}
@(test)
invalid_foreign_declarations_are_eagerly_diagnosed_and_calls_trap :: proc(t: ^testing.T) {
text := `bad :: c func(value int) int
native :: func() i32
main :: func() void {
_ = bad(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)
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_parameter := false
found_result := false
found_native := false
for diagnostic in diagnostics.items {
found_parameter = found_parameter || strings.contains(diagnostic.message, "requires concrete parameter types")
found_result = found_result || strings.contains(diagnostic.message, "requires a concrete or void result type")
found_native = found_native || strings.contains(diagnostic.message, "must use 'c func'")
}
testing.expect(t, found_parameter)
testing.expect(t, found_result)
testing.expect(t, found_native)
testing.expect(t, !strings.contains(llvm_text, "@bad("))
testing.expect(t, strings.contains(llvm_text, "call void @bro.trap"))
}
@(test)
duplicate_foreign_symbols_across_packages_are_poisoned :: 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)
ast_module, loaded := loader.load("examples/packages/foreign_duplicate/app", &sources, &diagnostics, &symbols)
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)
duplicate_count := 0
for diagnostic in diagnostics.items {
if strings.contains(diagnostic.message, "duplicate foreign symbol 'same'") {
duplicate_count += 1
}
}
testing.expect(t, loaded)
testing.expect_value(t, duplicate_count, 2)
testing.expect(t, !strings.contains(llvm_text, "@same("))
testing.expect(t, strings.contains(llvm_text, "call void @bro.trap"))
}
@(test)
bodyless_root_main_recovers_as_a_trap_definition :: proc(t: ^testing.T) {
text := "main :: c func() i32\n"
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)
found := false
for diagnostic in diagnostics.items {
found = found || strings.contains(diagnostic.message, "main must have a body")
}
testing.expect(t, found)
testing.expect_value(t, len(ir_module.functions), 1)
testing.expect_value(t, ir_module.functions[0].implementation, ir.Implementation.Definition)
testing.expect(t, strings.contains(llvm_text, "define i32 @main()"))
testing.expect(t, !strings.contains(llvm_text, "declare i32 @main()"))
}
@(test)
literal_addition_trees_fold_with_contextual_types :: proc(t: ^testing.T) {
text := `return_i16 :: func() i16 {
return 1 + 2
}
take_i16 :: func(value i16) i16 {
return value
}
take_int :: func(value int) int {
return value
}
main :: func() void {
local i16 :: 1 + 2
_ = 100 + (20 + 8)
_ = return_i16()
_ = take_i16(1 + 2)
_ = take_int(127 + 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)
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, "@bro__p0__take_int__i16"))
for function in ir_module.functions {
for instruction in function.instructions {
testing.expect(t, instruction.op != ir.Opcode.Add_Checked)
}
}
for function in hir_module.functions {
for statement_id in function.body {
statement := hir_module.statements[statement_id]
if statement.expr < 0 {
continue
}
expr := hir_module.exprs[statement.expr]
if expr.kind == .Integer {
testing.expect(t, types.equal(expr.type, types.I16))
}
if expr.kind == .Call && symbol.resolve(&symbols, function.name) == "main" && len(expr.args) > 0 {
arg := hir_module.exprs[expr.args[0]]
testing.expect_value(t, arg.kind, hir.Expr_Kind.Integer)
testing.expect(t, types.equal(arg.type, types.I16))
}
}
}
}
@(test)
runtime_arithmetic_does_not_inherit_result_context :: proc(t: ^testing.T) {
text := `widen_after_add :: func(value i8) i16 {
return value + 1
}
main :: func() void {
_ = widen_after_add(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)
testing.expect_value(t, len(diagnostics.items), 0)
found := false
for function in hir_module.functions {
if symbol.resolve(&symbols, function.name) != "widen_after_add" {
continue
}
found = true
statement := hir_module.statements[function.body[0]]
widen := hir_module.exprs[statement.expr]
add := hir_module.exprs[widen.left]
testing.expect_value(t, widen.kind, hir.Expr_Kind.Widen)
testing.expect(t, types.equal(widen.type, types.I16))
testing.expect_value(t, add.kind, hir.Expr_Kind.Add)
testing.expect(t, types.equal(add.type, types.I8))
}
testing.expect(t, found)
}
@(test)
parser_recovers_after_invalid_tokens :: proc(t: ^testing.T) {
text := `broken @ declaration
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)
testing.expect(t, len(diagnostics.items) > 0)
testing.expect_value(t, len(module.functions), 1)
testing.expect_value(t, symbol.resolve(&symbols, module.functions[0].name), "main")
}
@(test)
return_sink_and_unconsumed_values_have_distinct_hir :: proc(t: ^testing.T) {
text := `give :: func() i8 {
return 1
}
done :: func() void {
return _
}
main :: func() void {
done()
_ = give()
give()
}
`
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)
done_id, main_id := -1, -1
for function, id in hir_module.functions {
if symbol.resolve(&symbols, function.name) == "done" {
done_id = id
} else if symbol.resolve(&symbols, function.name) == "main" {
main_id = id
}
}
testing.expect(t, done_id >= 0)
testing.expect(t, main_id >= 0)
testing.expect_value(t, hir_module.statements[hir_module.functions[done_id].body[0]].kind, hir.Stmt_Kind.Return)
testing.expect_value(t, hir_module.statements[hir_module.functions[done_id].body[0]].expr, -1)
main := hir_module.functions[main_id]
testing.expect_value(t, hir_module.statements[main.body[0]].kind, hir.Stmt_Kind.Expression)
testing.expect_value(t, hir_module.statements[main.body[1]].kind, hir.Stmt_Kind.Sink)
testing.expect_value(t, hir_module.statements[main.body[2]].kind, hir.Stmt_Kind.Trap)
}
@(test)
recursive_specialization_reaches_a_fixed_point :: proc(t: ^testing.T) {
text := `a :: func(value int) i32 {
return b(value)
}
b :: func(value int) i32 {
return a(value)
}
main :: func() void {
_ = a(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)
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect_value(t, len(hir_module.functions), 3)
}
@(test)
long_generic_call_chain_reaches_a_fixed_point :: proc(t: ^testing.T) {
builder := strings.builder_make()
defer strings.builder_destroy(&builder)
for index in 0 ..< 70 {
fmt.sbprintf(&builder, "f%d :: func(value int) int ", index)
strings.write_string(&builder, "{ return ")
if index == 69 {
strings.write_string(&builder, "value")
} else {
fmt.sbprintf(&builder, "f%d(value)", index+1)
}
strings.write_string(&builder, " }\n")
}
strings.write_string(&builder, "main :: func() i32 { return f0(1) }\n")
source_file := source.Source{path="test.bro", text=strings.to_string(builder)}
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)
testing.expect_value(t, len(hir_module.functions), 71)
}
@(test)
globals_and_generic_results_reach_a_shared_fixed_point :: proc(t: ^testing.T) {
text := `derived :: identity(base)
base :: make()
identity :: func(value int) int {
return value
}
make :: func() int {
return 1
}
main :: func() i32 {
return derived
}
`
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)
testing.expect_value(t, len(hir_module.functions), 3)
for global in hir_module.globals {
testing.expect(t, types.equal(global.type, types.I8))
}
}
@(test)
unused_function_signatures_are_validated_eagerly :: proc(t: ^testing.T) {
text := `broken :: func(value, value i8, nope void) 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)
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_duplicate := false
found_void := false
for diagnostic in diagnostics.items {
found_duplicate = found_duplicate || strings.contains(diagnostic.message, "duplicate parameter 'value'")
found_void = found_void || strings.contains(diagnostic.message, "void is only valid as a function result type")
}
testing.expect(t, found_duplicate)
testing.expect(t, found_void)
}
run_command_success :: proc(command: []string) -> bool {
state, stdout, stderr, err := os2.process_exec(
os2.Process_Desc{command=command},
context.allocator,
)
delete(stdout)
delete(stderr)
return err == nil && state.exit_code == 0
}
prepare_native_object :: proc(output: string) -> bool {
local_cache := fmt.aprintf("ZIG_LOCAL_CACHE_DIR=%s-zig-cache", output)
defer delete(local_cache)
global_cache := fmt.aprintf("ZIG_GLOBAL_CACHE_DIR=%s-zig-global-cache", output)
defer delete(global_cache)
return run_command_success([]string{
"/usr/bin/env",
local_cache,
global_cache,
"zig",
"cc",
"-c",
"examples/interop/manual/native.c",
"-o",
output,
})
}
prepare_native_archive :: proc(object, archive: string) -> bool {
return run_command_success([]string{
"/usr/bin/ar",
"-rcs",
archive,
object,
})
}
run_executable :: proc(path: string) -> os2.Process_State {
state, stdout, stderr, _ := os2.process_exec(
os2.Process_Desc{command=[]string{path}},
context.allocator,
)
delete(stdout)
delete(stderr)
return state
}
@(test)
valid_program_compiles_and_runs :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-valid"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/programs/prototype", output)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 0)
}
@(test)
foreign_function_links_from_c_source :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-foreign-source"
defer _ = os.remove(output)
arguments := []linker.Argument{{kind=.Input, value="examples/interop/manual/native.c"}}
status := compiler_core.compile_package("examples/interop/manual", output, arguments)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 42)
}
@(test)
foreign_function_links_from_object :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-foreign-object"
object := "/tmp/brolang-test-foreign-object.o"
defer _ = os.remove(output)
defer _ = os.remove(object)
testing.expect(t, prepare_native_object(object))
arguments := []linker.Argument{{kind=.Input, value=object}}
status := compiler_core.compile_package("examples/interop/manual", output, arguments)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 42)
}
@(test)
foreign_function_links_from_direct_library :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-foreign-direct-library"
object := "/tmp/brolang-test-foreign-direct-library.o"
archive := "/tmp/brolang-test-foreign-direct-library.a"
defer _ = os.remove(output)
defer _ = os.remove(object)
defer _ = os.remove(archive)
testing.expect(t, prepare_native_object(object))
testing.expect(t, prepare_native_archive(object, archive))
arguments := []linker.Argument{{kind=.Input, value=archive}}
status := compiler_core.compile_package("examples/interop/manual", output, arguments)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 42)
}
@(test)
foreign_function_links_from_named_library :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-foreign-named-library"
object := "/tmp/brolang-test-foreign-named-library.o"
archive := "/tmp/libbrolang-test-foreign-named.a"
defer _ = os.remove(output)
defer _ = os.remove(object)
defer _ = os.remove(archive)
testing.expect(t, prepare_native_object(object))
testing.expect(t, prepare_native_archive(object, archive))
arguments := []linker.Argument{
{kind=.Library_Path, value="/tmp"},
{kind=.Library, value="brolang-test-foreign-named"},
}
status := compiler_core.compile_package("examples/interop/manual", output, arguments)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 42)
}
@(test)
one_line_main_compiles_and_runs :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-one-line-main"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/programs/one_line", output)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 7)
}
@(test)
folded_constant_addition_compiles_and_runs :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-constant-fold"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/programs/constant_fold", output)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 0)
}
@(test)
unused_invalid_global_does_not_trap :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-invalid-unused"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/programs/invalid_unused_global", output)
testing.expect_value(t, status, 1)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 0)
}
@(test)
used_invalid_global_traps :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-invalid-used"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/programs/invalid_used_global", output)
testing.expect_value(t, status, 1)
state := run_executable(output)
testing.expect(t, !state.success)
}
@(test)
main_int_is_constrained_to_i32 :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-main-int"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/programs/main_int", output)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 3)
}
@(test)
main_i32_returns_directly :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-main-i32"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/programs/main_i32", output)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 4)
}
@(test)
transitive_problematic_global_is_deferred :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-transitive-unused"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/programs/invalid_transitive_unused_global", output)
testing.expect_value(t, status, 1)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 0)
}
@(test)
checked_addition_traps_on_overflow :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-overflow"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/programs/overflow", output)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect(t, !state.success)
}
@(test)
constant_that_does_not_fit_context_produces_trap_executable :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-constant-context-error"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/programs/constant_context_error", output)
testing.expect_value(t, status, 1)
state := run_executable(output)
testing.expect(t, !state.success)
}
@(test)
constant_beyond_i64_produces_trap_executable :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-constant-i64-overflow"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/programs/constant_i64_overflow", output)
testing.expect_value(t, status, 1)
state := run_executable(output)
testing.expect(t, !state.success)
}
@(test)
same_line_statements_are_diagnosed :: proc(t: ^testing.T) {
text := "main :: func() void { _ = 1 _ = 2 }\n"
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)
testing.expect(t, len(diagnostics.items) > 0)
}
@(test)
missing_main_produces_trap_executable :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-missing-main"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/programs/missing_main", output)
testing.expect_value(t, status, 1)
state := run_executable(output)
testing.expect(t, !state.success)
}
@(test)
backend_failure_preserves_existing_output :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-preserved-output"
defer _ = os.remove(output)
previous := "previous artifact"
testing.expect(t, os.write_entire_file(output, transmute([]byte)previous))
testing.expect(t, !backend.compile("/definitely/not/llvm.ll", output))
data, ok := os.read_entire_file(output)
defer delete(data)
testing.expect(t, ok)
testing.expect_value(t, string(data), previous)
}
@(test)
backend_translates_link_arguments_without_reordering_them :: proc(t: ^testing.T) {
arguments := []linker.Argument{
{kind=.Input, value="native.c"},
{kind=.Library_Path, value="vendor/lib"},
{kind=.Library, value="thing"},
{kind=.Input, value="helper.o"},
}
command := backend.build_command("module.ll", "program", arguments)
defer backend.destroy_command(command)
expected := []string{
"/usr/bin/env",
"ZIG_LOCAL_CACHE_DIR=/tmp/brolang-zig-cache",
"ZIG_GLOBAL_CACHE_DIR=/tmp/brolang-zig-global-cache",
"zig",
"cc",
"-Wno-override-module",
"module.ll",
"native.c",
"-Lvendor/lib",
"-lthing",
"helper.o",
"-o",
"program",
}
testing.expect_value(t, len(command), len(expected))
for value, index in expected {
testing.expect_value(t, command[index], value)
}
}
@(test)
mutable_local_reassignment_uses_runtime_storage :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-mutable"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/programs/mutable_local", output)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 3)
}
@(test)
implicit_narrowing_produces_trap_executable :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-narrowing"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/programs/narrowing_error", output)
testing.expect_value(t, status, 1)
state := run_executable(output)
testing.expect(t, !state.success)
}
@(test)
valid_runtime_global_initializes_before_main :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-runtime-global"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/programs/runtime_global", output)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 0)
}
@(test)
unused_global_cycle_is_deferred :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-cycle-unused"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/programs/cycle_unused", output)
testing.expect_value(t, status, 1)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 0)
}
@(test)
used_global_cycle_traps :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-cycle-used"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/programs/cycle_used", output)
testing.expect_value(t, status, 1)
state := run_executable(output)
testing.expect(t, !state.success)
}
@(test)
malformed_typed_values_still_produce_executable :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-malformed-typed"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/programs/malformed_typed_recovery", output)
testing.expect_value(t, status, 1)
state := run_executable(output)
testing.expect(t, !state.success)
}
@(test)
unused_invalid_function_body_is_not_checked :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-invalid-unused-function"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/programs/invalid_unused_function", output)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 0)
}
@(test)
used_invalid_function_is_diagnosed_and_traps :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-invalid-used-function"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/programs/invalid_used_function", output)
testing.expect_value(t, status, 1)
state := run_executable(output)
testing.expect(t, !state.success)
}
@(test)
function_mediated_problematic_global_is_deferred :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-function-global-unused"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/programs/function_global_unused", output)
testing.expect_value(t, status, 1)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 0)
}
@(test)
function_mediated_problematic_global_traps_when_used :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-function-global-used"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/programs/function_global_used", output)
testing.expect_value(t, status, 1)
state := run_executable(output)
testing.expect(t, !state.success)
}
@(test)
package_files_merge_and_qualified_imports_run :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-package-basic"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/packages/basic/app", output)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 3)
}
@(test)
unused_import_is_diagnosed_but_remains_executable :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-package-unused"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/packages/unused/app", output)
testing.expect_value(t, status, 1)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 0)
}
@(test)
unused_missing_package_does_not_trap :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-package-missing-unused"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/packages/missing_unused/app", output)
testing.expect_value(t, status, 1)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 0)
}
@(test)
referenced_missing_package_traps_at_reference :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-package-missing-used"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/packages/missing_used/app", output)
testing.expect_value(t, status, 1)
state, stdout, stderr, _ := os2.process_exec(
os2.Process_Desc{command=[]string{output}},
context.allocator,
)
defer delete(stdout)
defer delete(stderr)
testing.expect(t, !state.success)
testing.expect(t, strings.contains(string(stderr), "/missing_used/app/main.bro:4:6:"))
}
@(test)
imports_are_file_local :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-package-file-local"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/packages/file_local/app", output)
testing.expect_value(t, status, 1)
state := run_executable(output)
testing.expect(t, !state.success)
}
@(test)
package_import_cycles_are_valid :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-package-cycle"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/packages/cycle/app", output)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 4)
}
@(test)
imported_main_is_an_ordinary_callable_function :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-package-imported-main"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/packages/imported_main/app", output)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 7)
}
@(test)
same_named_c_functions_in_packages_do_not_collide :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-package-c-symbols"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/packages/c_symbols/app", output)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 0)
}
@(test)
invalid_root_package_preserves_existing_output :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-package-preserved-output"
defer _ = os.remove(output)
previous := "previous artifact"
testing.expect(t, os.write_entire_file(output, transmute([]byte)previous))
testing.expect_value(t, compiler_core.compile_package("examples/programs/prototype/main.bro", output), 2)
data, ok := os.read_entire_file(output)
defer delete(data)
testing.expect(t, ok)
testing.expect_value(t, string(data), previous)
}
@(test)
empty_root_package_is_an_infrastructure_error :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-package-empty"
defer _ = os.remove(output)
testing.expect_value(t, compiler_core.compile_package("examples/packages/empty", output), 2)
testing.expect(t, !os.exists(output))
}
@(test)
same_package_can_be_imported_under_distinct_aliases :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-package-aliases"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/packages/aliases/app", output)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 4)
}
@(test)
duplicate_same_file_import_keeps_first_binding :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-package-duplicate"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/packages/duplicate/app", output)
testing.expect_value(t, status, 1)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 2)
}
@(test)
identical_imports_in_different_files_are_independent :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-package-repeated"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/packages/repeated/app", output)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 4)
}
@(test)
imports_do_not_reexport_members :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-package-non-transitive"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/packages/non_transitive/app", output)
testing.expect_value(t, status, 1)
state := run_executable(output)
testing.expect(t, !state.success)
}
@(test)
self_import_via_dot_is_valid :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-package-self"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/packages/self_import/app", output)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 5)
}
@(test)
unused_absolute_import_is_diagnosed_without_trapping :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-package-absolute"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/packages/absolute/app", output)
testing.expect_value(t, status, 1)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 0)
}
@(test)
unreferenced_problematic_imported_global_is_deferred :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-package-problematic-unused"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/packages/problematic_unused/app", output)
testing.expect_value(t, status, 1)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 0)
}
@(test)
valid_eager_global_in_unused_package_still_initializes :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-package-eager-unused"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/packages/eager_unused/app", output)
testing.expect_value(t, status, 1)
state := run_executable(output)
testing.expect(t, !state.success)
}
@(test)
cross_package_global_initialization_cycle_traps_when_used :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-package-global-cycle"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/packages/global_cycle/app", output)
testing.expect_value(t, status, 1)
state := run_executable(output)
testing.expect(t, !state.success)
}
@(test)
cross_package_generic_specializes_from_folded_argument :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-package-generic"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/packages/generic/app", output)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 128)
}
@(test)
lazy_function_body_marks_import_as_used_without_resolving_it :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-package-lazy-import"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/packages/lazy_import/app", output)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 0)
}
@(test)
qualified_global_inference_ignores_same_named_local :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-package-qualified-shadow"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/packages/qualified_shadow/app", output)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 300)
}
@(test)
cross_package_recursive_specialization_reaches_fixed_point :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-package-recursive"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/packages/recursive/app", output)
testing.expect_value(t, status, 0)
testing.expect(t, os.exists(output))
}
@(test)
imported_main_does_not_satisfy_root_main_requirement :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-package-missing-root-main"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/packages/missing_root_main/app", output)
testing.expect_value(t, status, 1)
state := run_executable(output)
testing.expect(t, !state.success)
}
@(test)
package_llvm_is_deterministic_and_symbols_include_package_ids :: 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)
ast_module, loaded := loader.load("examples/packages/c_symbols/app", &sources, &diagnostics, &symbols)
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)
second_llvm_text := llvm.emit(&ir_module, &diagnostics, &symbols)
defer delete(second_llvm_text)
testing.expect(t, loaded)
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect_value(t, llvm_text, second_llvm_text)
testing.expect(t, strings.contains(llvm_text, "define i8 @bro_c__p1__same"))
testing.expect(t, strings.contains(llvm_text, "define i8 @bro_c__p2__same"))
testing.expect(t, strings.contains(llvm_text, "define i32 @main()"))
}
@(test)
invalid_default_alias_requires_an_explicit_alias :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-package-default-alias-invalid"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/packages/default_alias_invalid/app", output)
testing.expect_value(t, status, 1)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 0)
}
@(test)
explicit_alias_allows_invalid_directory_basename :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-package-explicit-alias"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/packages/explicit_alias/app", output)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 3)
}
@(test)
import_alias_conflict_is_diagnosed_without_trapping :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-package-alias-conflict"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/packages/alias_conflict/app", output)
testing.expect_value(t, status, 1)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 7)
}
@(test)
empty_imported_package_is_a_source_diagnostic :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-package-empty-import"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/packages/empty_import/app", output)
testing.expect_value(t, status, 1)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 0)
}
@(test)
non_directory_import_is_a_source_diagnostic :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-package-file-import"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/packages/file_import/app", output)
testing.expect_value(t, status, 1)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 0)
}
@(test)
import_is_available_before_its_textual_declaration :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-package-import-order"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/packages/import_order/app", output)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 6)
}
@(test)
deeply_nested_child_packages_load_recursively :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-package-deep"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/packages/deep/app", output)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 9)
}