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/loader" import "./compiler/llvm" import "./compiler/lower" import "./compiler/parser" import "./compiler/source" import "./compiler/token" import "./compiler/types" import "core:fmt" import "core:os" import "core:os/os2" import "core:strings" import "core:testing" @(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) stream := lexer.lex(&source_file, &diagnostics) 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) stream := lexer.lex(&source_file, &diagnostics) defer delete(stream.items) module := parser.parse(&stream, &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) stream := lexer.lex(&source_file, &diagnostics) defer delete(stream.items) module := parser.parse(&stream, &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_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) stream := lexer.lex(&source_file, &diagnostics) defer delete(stream.items) module := parser.parse(&stream, &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, module.imports[0].alias, "") testing.expect_value(t, module.imports[0].path, "../math") testing.expect_value(t, 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) stream := lexer.lex(&source_file, &diagnostics) defer delete(stream.items) module := parser.parse(&stream, &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) stream := lexer.lex(&source_file, &diagnostics) 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) module, loaded := loader.load("examples/packages/basic/app", &sources, &diagnostics) 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) module, loaded := loader.load("examples/packages/file_local/app", &sources, &diagnostics) defer ast.destroy_module(&module) hir_module := checker.check(&module, &diagnostics) 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) stream := lexer.lex(&source_file, &diagnostics) defer delete(stream.items) ast_module := parser.parse(&stream, &diagnostics) defer ast.destroy_module(&ast_module) hir_module := checker.check(&ast_module, &diagnostics) 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) stream := lexer.lex(&source_file, &diagnostics) defer delete(stream.items) ast_module := parser.parse(&stream, &diagnostics) defer ast.destroy_module(&ast_module) hir_module := checker.check(&ast_module, &diagnostics) 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) defer delete(llvm_text) second_llvm_text := llvm.emit(&ir_module, &diagnostics) 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) 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) stream := lexer.lex(&source_file, &diagnostics) defer delete(stream.items) ast_module := parser.parse(&stream, &diagnostics) defer ast.destroy_module(&ast_module) hir_module := checker.check(&ast_module, &diagnostics) 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) 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 && 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) stream := lexer.lex(&source_file, &diagnostics) defer delete(stream.items) ast_module := parser.parse(&stream, &diagnostics) defer ast.destroy_module(&ast_module) hir_module := checker.check(&ast_module, &diagnostics) defer hir.destroy_module(&hir_module) testing.expect_value(t, len(diagnostics.items), 0) found := false for function in hir_module.functions { if 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) stream := lexer.lex(&source_file, &diagnostics) defer delete(stream.items) module := parser.parse(&stream, &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, 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) stream := lexer.lex(&source_file, &diagnostics) defer delete(stream.items) ast_module := parser.parse(&stream, &diagnostics) defer ast.destroy_module(&ast_module) hir_module := checker.check(&ast_module, &diagnostics) defer hir.destroy_module(&hir_module) done_id, main_id := -1, -1 for function, id in hir_module.functions { if function.name == "done" { done_id = id } else if 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) stream := lexer.lex(&source_file, &diagnostics) defer delete(stream.items) ast_module := parser.parse(&stream, &diagnostics) defer ast.destroy_module(&ast_module) hir_module := checker.check(&ast_module, &diagnostics) 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) stream := lexer.lex(&source_file, &diagnostics) defer delete(stream.items) ast_module := parser.parse(&stream, &diagnostics) defer ast.destroy_module(&ast_module) hir_module := checker.check(&ast_module, &diagnostics) 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) stream := lexer.lex(&source_file, &diagnostics) defer delete(stream.items) ast_module := parser.parse(&stream, &diagnostics) defer ast.destroy_module(&ast_module) hir_module := checker.check(&ast_module, &diagnostics) 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) stream := lexer.lex(&source_file, &diagnostics) defer delete(stream.items) ast_module := parser.parse(&stream, &diagnostics) defer ast.destroy_module(&ast_module) hir_module := checker.check(&ast_module, &diagnostics) 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_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) 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) stream := lexer.lex(&source_file, &diagnostics) defer delete(stream.items) module := parser.parse(&stream, &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) 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) ast_module, loaded := loader.load("examples/packages/c_symbols/app", &sources, &diagnostics) defer ast.destroy_module(&ast_module) hir_module := checker.check(&ast_module, &diagnostics) 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) defer delete(llvm_text) second_llvm_text := llvm.emit(&ir_module, &diagnostics) 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) }