1795 lines
63 KiB
Odin
1795 lines
63 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)
|
|
parser_treats_c_as_contextual_only_before_func :: proc(t: ^testing.T) {
|
|
text := `c :: 5
|
|
x :: c
|
|
foreign :: c func() i32
|
|
broken :: c 5
|
|
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)
|
|
|
|
c_symbol := symbol.intern(&symbols, "c")
|
|
for tok in stream.items {
|
|
if tok.span.start < len(text) && text[tok.span.start:tok.span.end] == "c" {
|
|
testing.expect_value(t, tok.kind, token.Kind.Identifier)
|
|
testing.expect_value(t, tok.symbol, c_symbol)
|
|
}
|
|
}
|
|
testing.expect_value(t, len(module.globals), 3)
|
|
testing.expect_value(t, module.exprs[module.globals[1].expr].name, c_symbol)
|
|
testing.expect_value(t, module.exprs[module.globals[2].expr].name, c_symbol)
|
|
testing.expect(t, module.functions[0].c_abi)
|
|
testing.expect_value(t, len(diagnostics.items), 1)
|
|
testing.expect(t, strings.contains(diagnostics.items[0].message, "followed by a newline"))
|
|
}
|
|
|
|
@(test)
|
|
pratt_parser_preserves_left_associative_addition_shape :: proc(t: ^testing.T) {
|
|
source_file := source.Source{path="test.bro", text="value :: 1 + 2 + 3\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)
|
|
module := parser.parse(&stream, &source_file, &diagnostics)
|
|
defer ast.destroy_module(&module)
|
|
|
|
root := module.exprs[module.globals[0].expr]
|
|
testing.expect_value(t, len(diagnostics.items), 0)
|
|
testing.expect_value(t, root.kind, ast.Expr_Kind.Add)
|
|
testing.expect_value(t, module.exprs[root.left].kind, ast.Expr_Kind.Add)
|
|
testing.expect_value(t, module.exprs[root.right].integer, i64(3))
|
|
}
|
|
|
|
nested_expression_source :: proc(call: bool, depth: int) -> string {
|
|
builder := strings.builder_make()
|
|
defer strings.builder_destroy(&builder)
|
|
if call {
|
|
strings.write_string(&builder, "identity :: func(value i32) i32 { return value }\nvalue :: ")
|
|
for _ in 0..<depth {
|
|
strings.write_string(&builder, "identity(")
|
|
}
|
|
} else {
|
|
strings.write_string(&builder, "value :: ")
|
|
for _ in 0..<depth {
|
|
strings.write_byte(&builder, '(')
|
|
}
|
|
}
|
|
strings.write_byte(&builder, '1')
|
|
for _ in 0..<depth {
|
|
strings.write_byte(&builder, ')')
|
|
}
|
|
strings.write_string(&builder, "\nmain :: func() void {}\n")
|
|
return strings.clone(strings.to_string(builder))
|
|
}
|
|
|
|
parse_nesting_result :: proc(text: string) -> (count: int, found_budget: bool) {
|
|
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)
|
|
for diagnostic in diagnostics.items {
|
|
found_budget = found_budget || strings.contains(diagnostic.message, "expression nesting exceeds 256 levels")
|
|
}
|
|
return len(diagnostics.items), found_budget
|
|
}
|
|
|
|
@(test)
|
|
parser_enforces_explicit_expression_nesting_budget :: proc(t: ^testing.T) {
|
|
modes := [?]bool{false, true}
|
|
for call in modes {
|
|
at_limit := nested_expression_source(call, parser.MAX_EXPRESSION_NESTING)
|
|
defer delete(at_limit)
|
|
count, found := parse_nesting_result(at_limit)
|
|
testing.expect_value(t, count, 0)
|
|
testing.expect(t, !found)
|
|
|
|
over_limit := nested_expression_source(call, parser.MAX_EXPRESSION_NESTING+1)
|
|
defer delete(over_limit)
|
|
_, found = parse_nesting_result(over_limit)
|
|
testing.expect(t, found)
|
|
}
|
|
}
|
|
|
|
@(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",
|
|
"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)
|
|
source_store_owns_buffers_indexes_lines_and_deduplicates_diagnostics :: proc(t: ^testing.T) {
|
|
store := source.init_store()
|
|
defer source.destroy_store(&store)
|
|
bytes := make([]byte, len("one\ntwo\n"))
|
|
copy(bytes, "one\ntwo\n")
|
|
source_id := source.add_source_owned(&store, "owned.bro", bytes)
|
|
bytes[0] = 'O'
|
|
|
|
diagnostics := source.init_store_diagnostics(&store)
|
|
defer source.destroy_diagnostics(&diagnostics)
|
|
span := source.Span{file=source_id, start=4, end=7}
|
|
first := source.add(&diagnostics, span, "same")
|
|
second := source.addf(&diagnostics, span, "%s", "same")
|
|
other := source.add(&diagnostics, span, "other")
|
|
formatted := source.format(&diagnostics, other)
|
|
defer delete(formatted)
|
|
|
|
testing.expect_value(t, store.items[source_id].text, "One\ntwo\n")
|
|
testing.expect_value(t, len(store.items[source_id].line_starts), 3)
|
|
testing.expect_value(t, first, second)
|
|
testing.expect_value(t, len(diagnostics.items), 2)
|
|
testing.expect(t, strings.contains(formatted, "owned.bro:2:1:"))
|
|
}
|
|
|
|
@(test)
|
|
maximum_signed_i64_literal_parses_exactly :: proc(t: ^testing.T) {
|
|
source_file := source.Source{path="test.bro", text="value :: 9223372036854775807\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)
|
|
module := parser.parse(&stream, &source_file, &diagnostics)
|
|
defer ast.destroy_module(&module)
|
|
|
|
testing.expect_value(t, len(diagnostics.items), 0)
|
|
testing.expect_value(t, module.exprs[module.globals[0].expr].integer, i64(9223372036854775807))
|
|
}
|
|
|
|
@(test)
|
|
malformed_ir_emits_traps_and_typed_sentinels :: proc(t: ^testing.T) {
|
|
module := ir.init_module()
|
|
defer ir.destroy_module(&module)
|
|
instructions := make([]ir.Instruction, 3)
|
|
instructions[0] = ir.Instruction{op=.Store, type=types.I8, a=-1, b=-1, diagnostic=-1}
|
|
instructions[1] = ir.Instruction{op=.Add_Checked, type=types.I32, a=-1, b=-1, diagnostic=-1}
|
|
instructions[2] = ir.Instruction{op=.Return, type=types.I32, a=1, b=-1, diagnostic=-1}
|
|
append(&module.functions, ir.Function{
|
|
link_name=strings.clone("main"),
|
|
calling_convention=.C,
|
|
implementation=.Definition,
|
|
linkage=.External,
|
|
is_main=true,
|
|
result=types.I32,
|
|
instructions=instructions,
|
|
})
|
|
source_file := source.Source{path="test.bro", text=""}
|
|
diagnostics := source.init_diagnostics(&source_file)
|
|
defer source.destroy_diagnostics(&diagnostics)
|
|
symbols := symbol.init_table()
|
|
defer symbol.destroy_table(&symbols)
|
|
text := llvm.emit(&module, &diagnostics, &symbols)
|
|
defer delete(text)
|
|
|
|
testing.expect(t, strings.contains(text, "call void @bro.trap"))
|
|
testing.expect(t, strings.contains(text, "%v0 = add i8 0, -86"))
|
|
testing.expect(t, strings.contains(text, "%v1 = add i32 0, -1431655766"))
|
|
testing.expect(t, strings.contains(text, "@bro.trap(ptr %message, i64 %length) noreturn"))
|
|
testing.expect(t, !strings.contains(text, "%v-1"))
|
|
llvm_path := "/tmp/brolang-test-malformed-recovery.ll"
|
|
output := "/tmp/brolang-test-malformed-recovery"
|
|
defer _ = os.remove(llvm_path)
|
|
defer _ = os.remove(output)
|
|
testing.expect(t, os.write_entire_file(llvm_path, transmute([]byte)text))
|
|
testing.expect(t, backend.compile(llvm_path, output))
|
|
}
|
|
|
|
@(test)
|
|
hundred_thousand_term_runtime_addition_uses_iterative_pipeline :: proc(t: ^testing.T) {
|
|
builder := strings.builder_make()
|
|
defer strings.builder_destroy(&builder)
|
|
strings.write_string(&builder, "sum :: func(value i32) i32 { return value")
|
|
for _ in 0..<100_000 {
|
|
strings.write_string(&builder, " + 1")
|
|
}
|
|
strings.write_string(&builder, " }\nmain :: func() void { _ = sum(0) }\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)
|
|
ir_module := lower.lower(&hir_module)
|
|
defer ir.destroy_module(&ir_module)
|
|
|
|
instruction_count := 0
|
|
for function in ir_module.functions {
|
|
instruction_count += len(function.instructions)
|
|
}
|
|
testing.expect_value(t, len(diagnostics.items), 0)
|
|
testing.expect(t, instruction_count > 100_000)
|
|
}
|
|
|
|
@(test)
|
|
deep_global_cycle_detection_uses_iterative_dfs :: proc(t: ^testing.T) {
|
|
count := 50_000
|
|
ast_module := ast.init_module()
|
|
defer ast.destroy_module(&ast_module)
|
|
source_file := source.Source{path="test.bro", text=""}
|
|
diagnostics := source.init_diagnostics(&source_file)
|
|
defer source.destroy_diagnostics(&diagnostics)
|
|
symbols := symbol.init_table()
|
|
defer symbol.destroy_table(&symbols)
|
|
name := symbol.intern(&symbols, "value")
|
|
state := checker.Checker{
|
|
ast_module=&ast_module,
|
|
diagnostics=&diagnostics,
|
|
symbols=&symbols,
|
|
module=hir.init_module(),
|
|
allocator=context.allocator,
|
|
}
|
|
defer hir.destroy_module(&state.module)
|
|
defer delete(state.cycle_stack)
|
|
|
|
for id in 0..<count {
|
|
append(&ast_module.globals, ast.Global{name=name})
|
|
dependencies: [dynamic]int
|
|
dependencies.allocator = context.allocator
|
|
append(&dependencies, (id+1)%count)
|
|
append(&state.module.globals, hir.Global{name=name, dependencies=dependencies})
|
|
}
|
|
states := make([]u8, count)
|
|
defer delete(states)
|
|
checker.detect_global_cycles_visit(&state, 0, states)
|
|
|
|
testing.expect_value(t, len(diagnostics.items), 1)
|
|
for global in state.module.globals {
|
|
testing.expect(t, global.problematic)
|
|
}
|
|
}
|
|
|
|
@(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)
|
|
}
|