reflection foundation, tuples, debug.print
This commit is contained in:
+275
-13
@@ -2258,7 +2258,12 @@ milestone_33_injects_explicit_io_provider_and_runs_std_io :: proc(t: ^testing.T)
|
||||
defer delete(stdout)
|
||||
defer delete(stderr)
|
||||
testing.expect_value(t, state.exit_code, 0)
|
||||
testing.expect_value(t, string(stdout), "io-ok\n")
|
||||
testing.expect_value(t, string(stdout),
|
||||
"io-ok 37 {bro}\n" +
|
||||
"bounds=-128/255 -32768/65535 -2147483648/4294967295 -9223372036854775808/18446744073709551615 " +
|
||||
"-9223372036854775808/18446744073709551615 -128/127 -128/255 -32768/65535 -2147483648/4294967295 " +
|
||||
"-9223372036854775808/18446744073709551615 -9223372036854775808/18446744073709551615 0/0 1/1 -1/1\n",
|
||||
)
|
||||
}
|
||||
|
||||
@(test)
|
||||
@@ -2266,6 +2271,7 @@ milestone_33_rejects_non_io_and_extra_main_parameters :: proc(t: ^testing.T) {
|
||||
cases := [?]string{
|
||||
"main func(value i32) void {}\n",
|
||||
"main func(left, right i32) void {}\n",
|
||||
"main func($value i32) void {}\n",
|
||||
}
|
||||
for text in cases {
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
@@ -2279,7 +2285,7 @@ milestone_33_rejects_non_io_and_extra_main_parameters :: proc(t: ^testing.T) {
|
||||
for diagnostic in diagnostics.items {
|
||||
found = found || strings.contains(
|
||||
diagnostic.message,
|
||||
"take no parameters or one @std/io Io",
|
||||
"take no parameters or one @std/process Init",
|
||||
)
|
||||
}
|
||||
testing.expect(t, found)
|
||||
@@ -2292,6 +2298,239 @@ milestone_33_rejects_non_io_and_extra_main_parameters :: proc(t: ^testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
milestone_37_rejects_direct_io_main_parameter :: proc(t: ^testing.T) {
|
||||
directory := "/tmp/brolang-test-direct-io-main"
|
||||
main_path := "/tmp/brolang-test-direct-io-main/main.bro"
|
||||
text := `io :: import "@std/io"
|
||||
main func(system io.Io) void {
|
||||
_ = system
|
||||
}
|
||||
`
|
||||
_ = os2.remove_all(directory)
|
||||
defer _ = os2.remove_all(directory)
|
||||
testing.expect(t, os.make_directory(directory) == nil)
|
||||
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text))
|
||||
|
||||
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(directory, &sources, &diagnostics, &symbols, project_root_path=".")
|
||||
defer ast.destroy_module(&ast_module)
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
|
||||
found := false
|
||||
for diagnostic in diagnostics.items {
|
||||
found = found || strings.contains(
|
||||
diagnostic.message,
|
||||
"take no parameters or one @std/process Init",
|
||||
)
|
||||
}
|
||||
testing.expect(t, loaded)
|
||||
testing.expect(t, found)
|
||||
}
|
||||
|
||||
@(test)
|
||||
milestone_37_validates_process_init_and_hidden_system_provider :: proc(t: ^testing.T) {
|
||||
Case :: struct {
|
||||
io_source: string,
|
||||
process_source: string,
|
||||
message: string,
|
||||
}
|
||||
cases := []Case{
|
||||
{
|
||||
io_source=`Io :: struct { value i32 }
|
||||
system func() Io { return Io {value = 0} }
|
||||
`,
|
||||
process_source=`io :: import "@std/io"
|
||||
Init :: struct { io io.Io }
|
||||
`,
|
||||
message="does not provide the required 'hide system func() Io'",
|
||||
},
|
||||
{
|
||||
io_source=`Io :: struct { value i32 }
|
||||
hide system func() Io { return Io {value = 0} }
|
||||
`,
|
||||
process_source=`io :: import "@std/io"
|
||||
Init :: struct { io io.Io, extra i32 }
|
||||
`,
|
||||
message="Init must be an auto-layout record containing exactly 'io io.Io'",
|
||||
},
|
||||
}
|
||||
for test_case, index in cases {
|
||||
root := fmt.tprintf("/tmp/brolang-test-process-schema-%d", index)
|
||||
app := fmt.tprintf("%s/app", root)
|
||||
io_dir := fmt.tprintf("%s/std/io", root)
|
||||
process_dir := fmt.tprintf("%s/std/process", root)
|
||||
main_source: string = `process :: import "@std/process"
|
||||
main func(init process.Init) void { _ = init }
|
||||
`
|
||||
_ = os2.remove_all(root)
|
||||
defer _ = os2.remove_all(root)
|
||||
testing.expect(t, os2.make_directory_all(app) == nil)
|
||||
testing.expect(t, os2.make_directory_all(io_dir) == nil)
|
||||
testing.expect(t, os2.make_directory_all(process_dir) == nil)
|
||||
testing.expect(t, os.write_entire_file(
|
||||
fmt.tprintf("%s/main.bro", app), transmute([]byte)main_source,
|
||||
))
|
||||
testing.expect(t, os.write_entire_file(
|
||||
fmt.tprintf("%s/io.bro", io_dir), transmute([]byte)test_case.io_source,
|
||||
))
|
||||
testing.expect(t, os.write_entire_file(
|
||||
fmt.tprintf("%s/process.bro", process_dir), transmute([]byte)test_case.process_source,
|
||||
))
|
||||
|
||||
sources := source.init_store()
|
||||
diagnostics := source.init_store_diagnostics(&sources)
|
||||
symbols := symbol.init_table()
|
||||
ast_module, loaded := loader.load(app, &sources, &diagnostics, &symbols, project_root_path=root)
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||
found := false
|
||||
for diagnostic in diagnostics.items {
|
||||
found = found || strings.contains(diagnostic.message, test_case.message)
|
||||
}
|
||||
testing.expect(t, loaded)
|
||||
testing.expect(t, found)
|
||||
hir.destroy_module(&hir_module)
|
||||
ast.destroy_module(&ast_module)
|
||||
symbol.destroy_table(&symbols)
|
||||
source.destroy_diagnostics(&diagnostics)
|
||||
source.destroy_store(&sources)
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
milestone_37_tuples_reflection_inline_for_and_debug_print_compile_and_run :: 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/programs/tuples", &sources, &diagnostics, &symbols, project_root_path=".",
|
||||
)
|
||||
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(t, loaded)
|
||||
testing.expect_value(t, len(diagnostics.items), 0)
|
||||
testing.expect(t, !strings.contains(llvm_text, "FormatToken"))
|
||||
testing.expect(t, !strings.contains(llvm_text, "parse_format"))
|
||||
testing.expect(t, !strings.contains(llvm_text, "FieldInfo"))
|
||||
testing.expect(t, !strings.contains(llvm_text, "RecordInfo"))
|
||||
|
||||
output := "/tmp/brolang-test-tuples"
|
||||
defer _ = os.remove(output)
|
||||
status := compiler_core.compile_package(
|
||||
"examples/programs/tuples", output, nil, target.DEFAULT, cimport.Options{}, ".",
|
||||
)
|
||||
testing.expect_value(t, status, 0)
|
||||
state, stdout, stderr, _ := os2.process_exec(
|
||||
os2.Process_Desc{command=[]string{output}}, context.allocator,
|
||||
)
|
||||
defer delete(stdout)
|
||||
defer delete(stderr)
|
||||
testing.expect_value(t, state.exit_code, 0)
|
||||
testing.expect_value(t, string(stdout), "")
|
||||
testing.expect_value(t, string(stderr), "tuple=40/bro, limits=-9223372036854775808/18446744073709551615")
|
||||
}
|
||||
|
||||
@(test)
|
||||
milestone_37_format_errors_are_reported_at_comptime :: proc(t: ^testing.T) {
|
||||
directory := "/tmp/brolang-test-format-errors"
|
||||
main_path := "/tmp/brolang-test-format-errors/main.bro"
|
||||
text := `io :: import "@std/io"
|
||||
process :: import "@std/process"
|
||||
|
||||
main func(init process.Init) void {
|
||||
writer io.Writer :: io.Writer {impl = init.io, stream = .stdout}
|
||||
stored :: typeinfo!(i32)
|
||||
_ = stored
|
||||
io.print(writer, "", 1) catch |_| {}
|
||||
io.print(writer, "{s}", {1,}) catch |_| {}
|
||||
io.print(writer, "{d}", {"bro",}) catch |_| {}
|
||||
io.print(writer, "{d}{d}", {1,}) catch |_| {}
|
||||
io.print(writer, "{d}", {1, 2}) catch |_| {}
|
||||
io.print(writer, "{x}", {}) catch |_| {}
|
||||
io.print(writer, "}", {}) catch |_| {}
|
||||
}
|
||||
`
|
||||
_ = os2.remove_all(directory)
|
||||
defer _ = os2.remove_all(directory)
|
||||
testing.expect(t, os.make_directory(directory) == nil)
|
||||
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text))
|
||||
|
||||
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(directory, &sources, &diagnostics, &symbols, project_root_path=".")
|
||||
defer ast.destroy_module(&ast_module)
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
|
||||
found := [7]bool{}
|
||||
for diagnostic in diagnostics.items {
|
||||
message := diagnostic.message
|
||||
found[0] = found[0] || strings.contains(message, "arguments must be a tuple")
|
||||
found[1] = found[1] || strings.contains(message, "cannot implicitly convert i8 to []u8")
|
||||
found[2] = found[2] || strings.contains(message, "'{d}' requires an integer")
|
||||
found[3] = found[3] || strings.contains(message, "argument count does not match")
|
||||
found[4] = found[4] || strings.contains(message, "unknown specifier")
|
||||
found[5] = found[5] || strings.contains(message, "unmatched '}'")
|
||||
found[6] = found[6] || strings.contains(message, "compile-time-only metadata")
|
||||
}
|
||||
testing.expect(t, loaded)
|
||||
for present in found {
|
||||
testing.expect(t, present)
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
milestone_37_inline_loop_control_must_be_statically_resolvable :: proc(t: ^testing.T) {
|
||||
text := `main func() void {
|
||||
total i32 = 0
|
||||
inline for {1, 2} |value| {
|
||||
if total == 0 {
|
||||
break
|
||||
}
|
||||
total += 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)
|
||||
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&ast_module)
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
|
||||
found := false
|
||||
for diagnostic in diagnostics.items {
|
||||
found = found || strings.contains(
|
||||
diagnostic.message,
|
||||
"break or continue targeting an inline loop must be compile-time-resolvable",
|
||||
)
|
||||
}
|
||||
testing.expect(t, found)
|
||||
}
|
||||
|
||||
@(test)
|
||||
milestone_33_rejects_an_incompatible_runtime_write_declaration :: proc(t: ^testing.T) {
|
||||
text := `write c_func(_ c_int, _ c_int, _ c_ulong) c_long
|
||||
@@ -2931,10 +3170,10 @@ main func() void {
|
||||
message := diagnostic.message
|
||||
found_runtime_arg = found_runtime_arg || strings.contains(message, "must be a compile-time integer expression")
|
||||
found_missing = found_missing || strings.contains(message, "cannot infer comptime parameter 'N'")
|
||||
found_extra = found_extra || strings.contains(message, "expects 1 arguments with explicit comptime parameters or 0 with inferred comptime parameters, got 2")
|
||||
found_extra = found_extra || strings.contains(message, "has no unique complete argument mapping")
|
||||
found_negative = found_negative || strings.contains(message, "integer constant -1 does not fit in usize")
|
||||
found_range = found_range || strings.contains(message, "integer constant 300 does not fit in u8")
|
||||
found_bad_type = found_bad_type || strings.contains(message, "requires a concrete integer type")
|
||||
found_bad_type = found_bad_type || strings.contains(message, "requires type, a concrete integer type, or immutable []u8")
|
||||
found_assignment = found_assignment || strings.contains(message, "cannot assign comptime parameter 'N'")
|
||||
found_address = found_address || strings.contains(message, "'&' requires an addressable location")
|
||||
}
|
||||
@@ -2996,7 +3235,7 @@ main func() void {
|
||||
message := diagnostic.message
|
||||
found_conflict = found_conflict || strings.contains(message, "conflicting inference for comptime parameter 'T': i32 and u32")
|
||||
found_unknown = found_unknown || strings.contains(message, "cannot infer comptime parameter 'T'")
|
||||
found_partial = found_partial || strings.contains(message, "expects 3 arguments with explicit comptime parameters or 1 with inferred comptime parameters, got 2")
|
||||
found_partial = found_partial || strings.contains(message, "cannot infer comptime parameter 'N'")
|
||||
if strings.contains(message, "cannot infer comptime parameter 'T'") {
|
||||
unrecoverable += 1
|
||||
}
|
||||
@@ -3008,19 +3247,38 @@ main func() void {
|
||||
}
|
||||
|
||||
@(test)
|
||||
comptime_params_must_form_leading_prefix :: proc(t: ^testing.T) {
|
||||
comptime_params_may_be_interleaved_and_are_erased_from_the_abi :: proc(t: ^testing.T) {
|
||||
text := `valid func($T type, $N usize, value T) [N]T {
|
||||
result [N]T = undefined
|
||||
_ = value
|
||||
return result
|
||||
}
|
||||
runtime_first func(value i32, $T type, $N usize) T { return value }
|
||||
runtime_first func(value T, $T type, $N usize) T { return value }
|
||||
split func($T type, value T, $N usize) [N]T {
|
||||
result [N]T = undefined
|
||||
_ = value
|
||||
return result
|
||||
}
|
||||
main func() void {}
|
||||
from_result func($T type) T {
|
||||
value T = undefined
|
||||
return value
|
||||
}
|
||||
choose_mapping func($A usize, value i32, $B usize) [A]u8 {
|
||||
result [A]u8 = undefined
|
||||
_ = value
|
||||
_ = B
|
||||
return result
|
||||
}
|
||||
main func() void {
|
||||
_ = runtime_first(42, i32, 4)
|
||||
_ = runtime_first(42, _, 4)
|
||||
_ = split(i32, 42, 4)
|
||||
_ = split(42, 4)
|
||||
value i32 = from_result()
|
||||
chosen [1]u8 :: choose_mapping(7, 2)
|
||||
_ = value
|
||||
_ = chosen
|
||||
}
|
||||
`
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
@@ -3034,13 +3292,17 @@ main func() void {}
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
|
||||
count := 0
|
||||
for diagnostic in diagnostics.items {
|
||||
if strings.contains(diagnostic.message, "comptime parameters must form a leading parameter prefix") {
|
||||
count += 1
|
||||
for function in hir_module.functions {
|
||||
name := symbol.resolve(&symbols, function.name)
|
||||
if name == "runtime_first" || name == "split" {
|
||||
testing.expect_value(t, len(function.params), 1)
|
||||
} else if name == "from_result" {
|
||||
testing.expect_value(t, len(function.params), 0)
|
||||
} else if name == "choose_mapping" {
|
||||
testing.expect_value(t, len(function.params), 1)
|
||||
}
|
||||
}
|
||||
testing.expect_value(t, count, 2)
|
||||
testing.expect_value(t, len(diagnostics.items), 0)
|
||||
}
|
||||
|
||||
@(test)
|
||||
|
||||
Reference in New Issue
Block a user