import "@std/mem" import "@std/arraylist" import "@std/testing" import "@source/diagnostics" import "@source/strpool" import "@source/lexer" import "@source/parser" import "@source/ast" import "@source/ir" import "@source/interpreter" sema :: import "@source/sema" typecheck :: import "@source/sema/check" @hide:file CompileError :: alias mem.AllocError | diagnostics.CapacityError | strpool.InternError | lexer.SourceError # every consumer test executes only after all source and frontend storage is gone. @hide:file compile_owned proc(text []u8, diagnostic_store @mut diagnostics.Store) ir.Module ! CompileError { source :: try mem.alloc(u8, mem.c_allocator, text.len) defer mem.free(mem.c_allocator, source) for (text) |byte, i| source[i] = byte strpool.strings = strpool.init(mem.c_allocator) defer strpool.deinit(&strpool.strings) scan_state := lexer.init(mem.c_allocator) defer lexer.deinit(&scan_state) try lexer.scan(&scan_state, source, diagnostic_store) tree := try parser.parse(mem.c_allocator, scan_state.tokens.items, source, diagnostic_store) defer ast.deinit(&tree) resolution := try sema.resolve(mem.c_allocator, &tree, diagnostic_store) defer sema.deinit(&resolution) checked := try typecheck.check(mem.c_allocator, &tree, &resolution, diagnostic_store) defer typecheck.deinit(&checked) return try lower(mem.c_allocator, &tree, &resolution, &checked, diagnostic_store) } @hide:file expect_trap proc( outcome interpreter.Outcome, diagnostic_store @diagnostics.Store, code diagnostics.Code, ) void ! testing.Error { if (outcome.trap) |id| { if (diagnostics.get(diagnostic_store, id)) |diagnostic| { try testing.expect_equal(code, diagnostic.code) return } } return .expectation_failed } executes_calls_locals_arithmetic_and_nested_argument_slices test { diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: `add proc(left int, right int) int { ` result :: left + right ` return result `} `double proc(value int) int { ` return value * 2 `} `main proc() int { ` a :: add(double(4), add(5, 6)) ` b :: add(10, 13) ` return a + b `} module := try compile_owned(source, &diagnostic_store) defer ir.deinit(&module) try testing.expect_equal(0, diagnostics.count(&diagnostic_store)) outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store) try testing.expect(outcome.trap == null) try testing.expect_equal(ir.Type.int, outcome.value.type) try testing.expect_equal(i64(42), outcome.value.data.integer) } owns_decoded_strings_after_source_and_frontend_are_freed test { diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: `text :: "a\\\"\n\r\t\0\x41\x7a" `identity proc(value string) string { ` return value `} `main proc() string { ` return identity(text) `} module := try compile_owned(source, &diagnostic_store) defer ir.deinit(&module) try testing.expect_equal(0, diagnostics.count(&diagnostic_store)) outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store) try testing.expect(outcome.trap == null) try testing.expect_equal(ir.Type.string, outcome.value.type) try testing.expect(mem.eql(outcome.value.data.string, "a\\\"\n\r\t\x00Az")) } honors_float_context_and_float_division test { diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: `main proc() float { ` left :: 3 ` return -(left / 2) + 2.0 `} module := try compile_owned(source, &diagnostic_store) defer ir.deinit(&module) try testing.expect_equal(0, diagnostics.count(&diagnostic_store)) outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store) try testing.expect(outcome.trap == null) try testing.expect_equal(ir.Type.float, outcome.value.type) try testing.expect_equal(f64(0.5), outcome.value.data.real) } executes_forward_lazy_globals_and_function_aliases test { diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: `forward :: later + 1 `alias :: add `later :: 18 `add proc(left int, right int) int { ` local_alias :: alias ` return left + right `} `main proc() int { ` invoke :: alias ` return invoke(forward, 23) `} module := try compile_owned(source, &diagnostic_store) defer ir.deinit(&module) try testing.expect_equal(0, diagnostics.count(&diagnostic_store)) outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store) try testing.expect(outcome.trap == null) try testing.expect_equal(i64(42), outcome.value.data.integer) again :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store) try testing.expect(again.trap == null) try testing.expect_equal(i64(42), again.value.data.integer) } supports_bare_void_value_returns_and_fallthrough test { sources [3][]u8 :: [ "main proc() void {\n return\n}\n", "noop proc() void {}\nmain proc() void {\n return noop()\n}\n", "noop proc() void {}\nmain proc() void {\n noop()\n}\n", ] for (sources) |source| { diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) module := try compile_owned(source, &diagnostic_store) defer ir.deinit(&module) try testing.expect_equal(0, diagnostics.count(&diagnostic_store)) outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store) try testing.expect(outcome.trap == null) try testing.expect_equal(ir.Type.void, outcome.value.type) } } preserves_demanded_diagnostic_identity_and_source_location test { diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: `bad proc() int { ` return unknown `} `main proc() int { ` before :: 1 + 2 ` return bad() `} module := try compile_owned(source, &diagnostic_store) defer ir.deinit(&module) try testing.expect_equal(1, diagnostics.count(&diagnostic_store)) diagnostic :: diagnostics.get(&diagnostic_store, diagnostics.DiagnosticId(0))? try testing.expect_equal(diagnostics.Code.unknown_name, diagnostic.code) try testing.expect(mem.eql(source[usize(diagnostic.span.start)..usize(diagnostic.span.end)], "unknown")) outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store) try testing.expect_equal(diagnostics.DiagnosticId(0), outcome.trap?) try testing.expect_equal(1, diagnostics.count(&diagnostic_store)) } skips_unreachable_and_unused_invalid_code test { diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: `bad proc() int { ` return unknown `} `unused :: "\q" `main proc() int { ` return 42 ` 9223372036854775807 + 1 ` missing ` huge :: 9223372036854775808 `} module := try compile_owned(source, &diagnostic_store) defer ir.deinit(&module) try testing.expect(diagnostics.count(&diagnostic_store) > 0) outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store) try testing.expect(outcome.trap == null) try testing.expect_equal(i64(42), outcome.value.data.integer) } traps_local_forward_loads_and_global_initialization_cycles test { sources [3][]u8 :: [ "main proc() int {\n before :: after\n after :: 42\n return before\n}\n", "left int :: right\nright int :: left\nmain proc() int {\n return left\n}\n", "self int :: self\nmain proc() int {\n return self\n}\n", ] for (sources) |source, i| { diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) module := try compile_owned(source, &diagnostic_store) defer ir.deinit(&module) try testing.expect_equal(0, diagnostics.count(&diagnostic_store)) outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store) code diagnostics.Code :: if (i == 0) .read_before_initialization else .initialization_cycle try expect_trap(outcome, &diagnostic_store, code) } } handles_integer_boundaries_without_host_overflow test { sources [3][]u8 :: [ "main proc() int {\n return 9223372036854775807\n}\n", "main proc() int {\n return -9223372036854775808\n}\n", "main proc() int {\n return -0009223372036854775808\n}\n", ] expected [3]i64 :: [maxval!(i64), minval!(i64), minval!(i64)] for (sources) |source, i| { diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) module := try compile_owned(source, &diagnostic_store) defer ir.deinit(&module) try testing.expect_equal(0, diagnostics.count(&diagnostic_store)) outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store) try testing.expect(outcome.trap == null) try testing.expect_equal(expected[i], outcome.value.data.integer) } } traps_invalid_literals_escapes_and_checked_integer_arithmetic test { sources [8][]u8 :: [ "main proc() int {\n return 9223372036854775808\n}\n", "main proc() int {\n return -9223372036854775809\n}\n", "main proc() int {\n return 9223372036854775807 + 1\n}\n", "main proc() int {\n return -(-9223372036854775808)\n}\n", "main proc() int {\n return -9223372036854775808 - 1\n}\n", "main proc() int {\n return 9223372036854775807 * 2\n}\n", "main proc() string {\n return \"\\q\"\n}\n", "main proc() string {\n return \"\\x0g\"\n}\n", ] for (sources) |source, i| { diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) module := try compile_owned(source, &diagnostic_store) defer ir.deinit(&module) outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store) code := diagnostics.Code.integer_overflow if (i < 2) code = .literal_out_of_range if (i >= 6) code = .invalid_escape try expect_trap(outcome, &diagnostic_store, code) } } validates_main_and_preserves_first_declaration test { sources [4][]u8 :: [ "other proc() void {}\n", "main :: 42\n", "main proc(value int) int {\n return value\n}\n", "main proc() int {\n return 42\n}\nmain proc() int {\n return 7\n}\n", ] for (sources) |source, i| { diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) module := try compile_owned(source, &diagnostic_store) defer ir.deinit(&module) outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store) if (i == 3) { try testing.expect(outcome.trap == null) try testing.expect_equal(i64(42), outcome.value.data.integer) } else { code diagnostics.Code :: if (i == 0) .missing_entry else .invalid_entry try expect_trap(outcome, &diagnostic_store, code) try testing.expect_equal(module.entry_diagnostic, outcome.trap?) } } } invalid_signatures_trap_without_poisoning_unrelated_main test { sources [2][]u8 :: [ "broken proc(value void) void {}\nmain proc() int {\n return 42\n}\n", "main proc() Unknown {\n return 42\n}\n", ] for (sources) |source, i| { diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) module := try compile_owned(source, &diagnostic_store) defer ir.deinit(&module) try testing.expect(diagnostics.count(&diagnostic_store) > 0) outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store) if (i == 0) { try testing.expect(outcome.trap == null) try testing.expect_equal(i64(42), outcome.value.data.integer) } else { try testing.expect_equal(diagnostics.DiagnosticId(0), outcome.trap?) try expect_trap(outcome, &diagnostic_store, .unknown_name) } } } parses_float_context_without_integer_range_limits_and_rejects_float_overflow test { diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) module := try compile_owned( "main proc() float {\n return 18446744073709551616\n}\n", &diagnostic_store, ) defer ir.deinit(&module) outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store) try testing.expect(outcome.trap == null) try testing.expect_equal(ir.Type.float, outcome.value.type) try testing.expect_equal(f64(18446744073709551616.0), outcome.value.data.real) source := arraylist.init(u8, mem.c_allocator) defer arraylist.deinit(&source) for ("main proc() float {\n return ") |byte| try arraylist.append(&source, byte) for (usize(0)..usize(400)) |_| try arraylist.append(&source, '9') for (".0\n}\n") |byte| try arraylist.append(&source, byte) oversized := try compile_owned(source.items, &diagnostic_store) defer ir.deinit(&oversized) trapped :: try interpreter.run(mem.c_allocator, &oversized, &diagnostic_store) try expect_trap(trapped, &diagnostic_store, .literal_out_of_range) } accepts_representable_subnormals_and_rejects_underflow_to_zero test { zeros [2]usize :: [307, 400] for (zeros) |count| { source := arraylist.init(u8, mem.c_allocator) defer arraylist.deinit(&source) for ("main proc() float {\n return 0.") |byte| try arraylist.append(&source, byte) for (usize(0)..count) |_| try arraylist.append(&source, '0') for ("1\n}\n") |byte| try arraylist.append(&source, byte) diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) module := try compile_owned(source.items, &diagnostic_store) defer ir.deinit(&module) outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store) if (count == 307) { try testing.expect_equal(0, diagnostics.count(&diagnostic_store)) try testing.expect(outcome.trap == null) try testing.expect_equal(ir.Type.float, outcome.value.type) try testing.expect(outcome.value.data.real > 0 and outcome.value.data.real < 1) } else { try expect_trap(outcome, &diagnostic_store, .literal_out_of_range) } } } @hide:file AllocationState :: struct { remaining usize live usize = 0 } @hide:file limited_alloc proc(context ?@mut anyopaque, size usize, alignment usize) ?*mut u8 { if (context) |userdata| { state @mut AllocationState :: ptrcast!(userdata) if (state.remaining == 0) return null state.remaining -= 1 memory :: mem.raw_alloc(mem.c_allocator, size, alignment) if (memory) |_| state.live += 1 return memory } unreachable } @hide:file limited_realloc proc(context ?@mut anyopaque, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8 { if (context) |userdata| { state @mut AllocationState :: ptrcast!(userdata) if (state.remaining == 0) return null state.remaining -= 1 resized :: mem.raw_realloc(mem.c_allocator, memory, old_size, new_size, alignment) if (resized) |_| { if (memory) |_| {} else state.live += 1 } return resized } unreachable } @hide:file limited_free proc(context ?@mut anyopaque, memory ?*mut u8, size usize, alignment usize) void { if (context) |userdata| { state @mut AllocationState :: ptrcast!(userdata) if (memory) |_| state.live -= 1 mem.raw_free(mem.c_allocator, memory, size, alignment) return } unreachable } @hide:file limited_vtable :: mem.AllocatorVTable{ alloc = limited_alloc, realloc = limited_realloc, free = limited_free } releases_temporaries_and_partially_owned_modules_on_allocation_failure test { strpool.strings = strpool.init(mem.c_allocator) defer strpool.deinit(&strpool.strings) diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: `text :: "owned\nstring" `factor float :: 3.5 `identity proc(value string) string { ` if false { ` return value ` } ` local :: value ` return local `} `main proc() string { ` computed float :: factor * 2 ` if computed > 0 { ` saved :: identity(text) ` if computed < 10 { ` return saved ` } ` } else { ` fallback :: "fallback" ` identity(fallback) ` } ` return identity(text) `} scan_state := lexer.init(mem.c_allocator) defer lexer.deinit(&scan_state) try lexer.scan(&scan_state, source, &diagnostic_store) tree := try parser.parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store) defer ast.deinit(&tree) resolution := try sema.resolve(mem.c_allocator, &tree, &diagnostic_store) defer sema.deinit(&resolution) checked := try typecheck.check(mem.c_allocator, &tree, &resolution, &diagnostic_store) defer typecheck.deinit(&checked) succeeded := false failures := usize(0) for (usize(0)..usize(128)) |limit| { state := AllocationState{ remaining = limit } allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable } module := lower(allocator, &tree, &resolution, &checked, &diagnostic_store) catch |error| { try testing.expect(error == .out_of_memory) failures += 1 try testing.expect_equal(usize(0), state.live) continue } outcome :: interpreter.run(mem.c_allocator, &module, &diagnostic_store) catch |error| { ir.deinit(&module) return error } ir.deinit(&module) try testing.expect(outcome.trap == null) try testing.expect_equal(usize(0), state.live) succeeded = true break } try testing.expect(succeeded) try testing.expect(failures > 0) } recursive_factorial_and_conditional_returns_execute_after_frontend_cleanup test { diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: `factorial proc(n int) int { ` if n <= 1 { ` return 1 ` } else { ` return n * factorial(n - 1) ` } `} `main proc() int { ` return factorial(5) `} module := try compile_owned(source, &diagnostic_store) defer ir.deinit(&module) try testing.expect_equal(0, diagnostics.count(&diagnostic_store)) outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store) try testing.expect(outcome.trap == null) try testing.expect_equal(i64(120), outcome.value.data.integer) } nested_branches_joins_calls_and_lazy_globals_preserve_execution_position test { diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: `seed :: classify(2) `enabled bool :: true `identity proc(value int) int { return value } `classify proc(n int) int { ` if n < 0 { ` return 3 ` } else if n == 0 { ` return 5 ` } else { ` if n > 1 { ` local :: identity(7) ` identity(local) ` } else { ` local :: identity(9) ` identity(local) ` } ` return identity(11) ` } `} `choose proc(flag bool) int { ` if flag { ` local :: identity(seed) ` identity(local) ` } else { ` local :: identity(13) ` identity(local) ` } ` return identity(seed) + classify(0) + classify(-1) `} `main proc() int { ` return choose(enabled) + choose(false) + classify(1) - 7 `} module := try compile_owned(source, &diagnostic_store) defer ir.deinit(&module) try testing.expect_equal(0, diagnostics.count(&diagnostic_store)) outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store) try testing.expect(outcome.trap == null) try testing.expect_equal(i64(42), outcome.value.data.integer) } bool_storage_results_and_numeric_comparisons_execute test { sources [16][]u8 :: [ "main proc() bool { return 2 == 2 }\n", "main proc() bool { return 2 != 3 }\n", "main proc() bool { return -2 < 1 }\n", "main proc() bool { return 2 <= 2 }\n", "main proc() bool { return 3 > 2 }\n", "main proc() bool { return 2 >= 2 }\n", "main proc() bool { return 2 == 2.0 }\n", "main proc() bool { return 2 != 3.0 }\n", "main proc() bool { return 2 < 2.5 }\n", "main proc() bool { return 2 <= 2.0 }\n", "main proc() bool { return 3 > 2.5 }\n", "main proc() bool { return 2 >= 2.0 }\n", "main proc() bool { return true == true }\n", "main proc() bool { return false != true }\n", "flag bool :: false\nidentity proc(value bool) bool { return value }\nmain proc() bool {\n local bool :: identity(flag)\n return identity(local == false)\n}\n", "main proc() bool {\n local :: 2\n return local < 2.5\n}\n", ] for (sources) |source| { diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) module := try compile_owned(source, &diagnostic_store) defer ir.deinit(&module) try testing.expect_equal(0, diagnostics.count(&diagnostic_store)) outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store) try testing.expect(outcome.trap == null) try testing.expect_equal(ir.Type.bool, outcome.value.type) try testing.expect(outcome.value.data.boolean) } } conditional_errors_trap_only_when_demanded_and_preserve_original_identity test { sources [7][]u8 :: [ "main proc() int {\n if true {\n return 42\n } else {\n return unknown\n }\n}\n", "main proc() int {\n if false {\n return 42\n } else {\n return unknown\n }\n}\n", "main proc() int {\n if true {\n return 42\n }\n}\n", "main proc() int {\n if false {\n return 42\n }\n}\n", "main proc() void {\n if false {\n unknown\n }\n}\n", "main proc() int {\n if 1 {\n return 42\n } else {\n return 0\n }\n}\n", "main proc() int {\n if unknown {\n return 42\n }\n}\n", ] for (sources) |source, index| { diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) module := try compile_owned(source, &diagnostic_store) defer ir.deinit(&module) try testing.expect_equal(1, diagnostics.count(&diagnostic_store)) original :: diagnostics.get(&diagnostic_store, diagnostics.DiagnosticId(0))? outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store) if (index == 0 or index == 2 or index == 4) { try testing.expect(outcome.trap == null) if (index == 4) try testing.expect_equal(ir.Type.void, outcome.value.type) else try testing.expect_equal(i64(42), outcome.value.data.integer) } else { try testing.expect_equal(diagnostics.DiagnosticId(0), outcome.trap?) code := diagnostics.Code.unknown_name if (index == 3) code = .missing_return if (index == 5) code = .condition_not_bool try testing.expect_equal(code, original.code) if (index == 5) try testing.expect(mem.eql(source[usize(original.span.start)..usize(original.span.end)], "1")) } try testing.expect_equal(1, diagnostics.count(&diagnostic_store)) } } missing_return_does_not_replace_reached_arm_error test { sources [3][]u8 :: [ "main proc() int {\n if true {\n return unknown\n }\n}\n", "main proc() int {\n if false {\n return unknown\n }\n}\n", "main proc() int {\n if false {\n return unknown\n }\n return 42\n}\n", ] for (sources) |source, index| { diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) module := try compile_owned(source, &diagnostic_store) defer ir.deinit(&module) outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store) if (index == 2) { try testing.expect(outcome.trap == null) try testing.expect_equal(i64(42), outcome.value.data.integer) } else { code diagnostics.Code :: if (index == 0) .unknown_name else .missing_return try expect_trap(outcome, &diagnostic_store, code) for (0..diagnostics.count(&diagnostic_store)) |i| { diagnostic :: diagnostics.get(&diagnostic_store, diagnostics.DiagnosticId(i))? if (diagnostic.code == code) try testing.expect_equal(diagnostics.DiagnosticId(i), outcome.trap?) } } } } nested_locals_are_predeclared_before_any_arm_executes test { diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: `main proc() int { ` if true { ` if true { ` before :: later ` later :: 42 ` return before ` } ` } ` return 0 `} module := try compile_owned(source, &diagnostic_store) defer ir.deinit(&module) try testing.expect_equal(0, diagnostics.count(&diagnostic_store)) outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store) try expect_trap(outcome, &diagnostic_store, .read_before_initialization) }