1960 lines
96 KiB
Plaintext
1960 lines
96 KiB
Plaintext
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
|
|
strings := strpool.init(mem.c_allocator)
|
|
defer strpool.deinit(&strings)
|
|
scan_state := lexer.init(mem.c_allocator)
|
|
defer lexer.deinit(&scan_state)
|
|
try lexer.scan(&scan_state, source, &strings, 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, &strings, 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)
|
|
defer interpreter.deinit(&outcome)
|
|
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)
|
|
defer interpreter.deinit(&outcome)
|
|
try testing.expect(outcome.trap == null)
|
|
try testing.expect_equal(ir.Type.string, outcome.value.type)
|
|
try testing.expect(mem.eql(ir.string_bytes(&module, outcome.value.data.string), "a\\\"\n\r\t\x00Az"))
|
|
}
|
|
|
|
resolves_module_local_string_ids_after_frontend_cleanup test {
|
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
|
defer diagnostics.deinit(&diagnostic_store)
|
|
module := try compile_owned(
|
|
"first :: \"not the result\"\nidentity proc(value string) string { return value }\nmain proc() string { return identity(\"second\\n\\x00\") }\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([]u8, "second\n\x00", ir.string_bytes(&module, outcome.value.data.string))
|
|
}
|
|
|
|
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)
|
|
defer interpreter.deinit(&outcome)
|
|
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
|
|
`forwarded :: add
|
|
`later :: 18
|
|
`add proc(left int, right int) int {
|
|
` local_alias :: forwarded
|
|
` return left + right
|
|
`}
|
|
`main proc() int {
|
|
` invoke :: forwarded
|
|
` 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)
|
|
defer interpreter.deinit(&outcome)
|
|
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)
|
|
defer interpreter.deinit(&again)
|
|
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)
|
|
defer interpreter.deinit(&outcome)
|
|
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)
|
|
defer interpreter.deinit(&outcome)
|
|
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)
|
|
defer interpreter.deinit(&outcome)
|
|
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)
|
|
defer interpreter.deinit(&outcome)
|
|
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)
|
|
defer interpreter.deinit(&outcome)
|
|
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)
|
|
defer interpreter.deinit(&outcome)
|
|
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)
|
|
defer interpreter.deinit(&outcome)
|
|
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)
|
|
defer interpreter.deinit(&outcome)
|
|
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)
|
|
defer interpreter.deinit(&outcome)
|
|
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)
|
|
defer interpreter.deinit(&trapped)
|
|
try expect_trap(trapped, &diagnostic_store, .literal_out_of_range)
|
|
}
|
|
|
|
rejects_inexact_integer_literals_in_float_context test {
|
|
sources [4][]u8 :: [
|
|
"main proc() f64 {\n return 9007199254740993\n}\n",
|
|
"main proc() f64 {\n value :: 9007199254740993\n return value\n}\n",
|
|
"main proc() f64 {\n return 9007199254740993 / 1\n}\n",
|
|
"main proc() f64 {\n values [1]f64 :: [9007199254740993]\n return values[0]\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)
|
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
|
defer interpreter.deinit(&outcome)
|
|
try expect_trap(outcome, &diagnostic_store, .inexact_integer_literal)
|
|
}
|
|
}
|
|
|
|
preserves_exact_large_integers_and_explicit_float_rounding test {
|
|
sources [3][]u8 :: [
|
|
"main proc() f64 {\n return 9007199254740994 - 9007199254740992\n}\n",
|
|
"main proc() f64 {\n return 9007199254740993.0 - 9007199254740992\n}\n",
|
|
"main proc() f64 {\n return 0003\n}\n",
|
|
]
|
|
expected [3]f64 :: [2, 0, 3]
|
|
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)
|
|
defer interpreter.deinit(&outcome)
|
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
|
try testing.expect(outcome.trap == null)
|
|
try testing.expect_equal(expected[i], outcome.value.data.real)
|
|
}
|
|
}
|
|
|
|
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)
|
|
defer interpreter.deinit(&outcome)
|
|
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 {
|
|
strings := strpool.init(mem.c_allocator)
|
|
defer strpool.deinit(&strings)
|
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
|
defer diagnostics.deinit(&diagnostic_store)
|
|
source ::
|
|
`text :: "owned\nstring"
|
|
`factor float :: 3.5
|
|
`enabled :: true or false and false
|
|
`compose proc(left bool, right bool) bool {
|
|
` return left and right
|
|
`}
|
|
`broken proc() bool {
|
|
` return compose(true and absent, false or true)
|
|
`}
|
|
`identity proc(value string) string {
|
|
` if false {
|
|
` return value
|
|
` }
|
|
` local :: value
|
|
` return local
|
|
`}
|
|
`rows proc(value [2][2]string) [2][2]string { return value }
|
|
`main proc() string {
|
|
` computed float :: factor * 2
|
|
` aggregate :: rows([[text, "second"], ["third", text]])
|
|
` (aggregate[1])[1]
|
|
` remaining := 2
|
|
` for 0..=2 |item| {
|
|
` saved :: identity(text)
|
|
` for 0..2 |_| {
|
|
` nested :: identity(saved)
|
|
` identity(nested)
|
|
` if (item == 1) continue else break
|
|
` }
|
|
` if (item < 2) continue else break
|
|
` }
|
|
` while compose(remaining > 0 and enabled, compose(!false, compose(true or false, compose(true, compose(true, true))))) {
|
|
` pass :: identity(text)
|
|
` identity(pass)
|
|
` remaining = remaining - 1
|
|
` inner := 2
|
|
` while inner > 0 {
|
|
` inner = inner - 1
|
|
` if (inner > 0) continue else break
|
|
` }
|
|
` if (remaining > 0) continue else break
|
|
` }
|
|
` if computed > 0 and (enabled or false) {
|
|
` 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, &strings, &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, &strings, &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(512)) |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
|
|
}
|
|
errdefer ir.deinit(&module)
|
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
|
defer interpreter.deinit(&outcome)
|
|
try testing.expect(outcome.trap == null)
|
|
try testing.expect(mem.eql(ir.string_bytes(&module, outcome.value.data.string), "owned\nstring"))
|
|
interpreter.deinit(&outcome)
|
|
ir.deinit(&module)
|
|
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)
|
|
defer interpreter.deinit(&outcome)
|
|
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)
|
|
defer interpreter.deinit(&outcome)
|
|
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)
|
|
defer interpreter.deinit(&outcome)
|
|
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)
|
|
defer interpreter.deinit(&outcome)
|
|
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)
|
|
defer interpreter.deinit(&outcome)
|
|
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)
|
|
defer interpreter.deinit(&outcome)
|
|
try expect_trap(outcome, &diagnostic_store, .read_before_initialization)
|
|
}
|
|
|
|
iterative_factorial_executes_from_owned_ir_after_frontend_cleanup test {
|
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
|
defer diagnostics.deinit(&diagnostic_store)
|
|
source ::
|
|
`factorial proc(n int) int {
|
|
` remaining := n
|
|
` result := 1
|
|
` while remaining > 1 {
|
|
` result = result * remaining
|
|
` remaining = remaining - 1
|
|
` }
|
|
` return result
|
|
`}
|
|
`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)
|
|
defer interpreter.deinit(&outcome)
|
|
try testing.expect(outcome.trap == null)
|
|
try testing.expect_equal(ir.Type.int, outcome.value.type)
|
|
try testing.expect_equal(i64(120), outcome.value.data.integer)
|
|
}
|
|
|
|
zero_nested_early_return_and_predicate_call_loops_execute test {
|
|
sources [6][]u8 :: [
|
|
"main proc() int {\n n := 0\n while n > 0 {\n n = n - 1\n }\n return 42 + n\n}\n",
|
|
"main proc() int {\n outer := 3\n total := 0\n while outer > 0 {\n inner := 2\n while inner > 0 {\n total = total + 7\n inner = inner - 1\n }\n outer = outer - 1\n }\n return total\n}\n",
|
|
"main proc() int {\n n := 2\n while n > 0 {\n if n == 1 { return 42 }\n n = n - 1\n }\n return 0\n}\n",
|
|
"enabled :: positive(1)\npositive proc(n int) bool { return n > 0 }\nmain proc() int {\n n := 3\n total := 0\n while positive(n) {\n if enabled { total = total + 14 }\n n = n - 1\n }\n return total\n}\n",
|
|
"main proc() int {\n n := 0\n while (n < 42) n = n + 1\n return n\n}\n",
|
|
"main proc() int {\n while true { return 42 }\n return 0\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)
|
|
defer interpreter.deinit(&outcome)
|
|
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)
|
|
}
|
|
}
|
|
|
|
float_bool_string_and_compatible_function_updates_execute test {
|
|
sources [4][]u8 :: [
|
|
"main proc() float {\n value float := 0\n value = 3 / 2\n return value\n}\n",
|
|
"main proc() bool {\n flag := false\n flag = true\n return flag\n}\n",
|
|
"main proc() string {\n text := \"before\"\n text = \"owned\\nupdated\"\n return text\n}\n",
|
|
"first proc(value int) int { return value + 1 }\nsecond proc(renamed int) int { return renamed + 2 }\nmain proc() int {\n invoke := first\n invoke = second\n return invoke(40)\n}\n",
|
|
]
|
|
expected_types [4]ir.Type :: [.float, .bool, .string, .int]
|
|
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(0, diagnostics.count(&diagnostic_store))
|
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
|
defer interpreter.deinit(&outcome)
|
|
try testing.expect(outcome.trap == null)
|
|
try testing.expect_equal(expected_types[index], outcome.value.type)
|
|
match index {
|
|
0: try testing.expect_equal(f64(1.5), outcome.value.data.real)
|
|
1: try testing.expect(outcome.value.data.boolean)
|
|
2: try testing.expect(mem.eql(ir.string_bytes(&module, outcome.value.data.string), "owned\nupdated"))
|
|
3: try testing.expect_equal(i64(42), outcome.value.data.integer)
|
|
else: unreachable
|
|
}
|
|
}
|
|
}
|
|
|
|
forward_assignments_check_initialization_after_evaluating_the_rhs test {
|
|
sources [2][]u8 :: [
|
|
"main proc() int {\n later = 42\n later := 0\n return later\n}\n",
|
|
"main proc() int {\n later = later + 1\n later := 0\n return later\n}\n",
|
|
]
|
|
codes [2]diagnostics.Code :: [.write_before_initialization, .read_before_initialization]
|
|
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(0, diagnostics.count(&diagnostic_store))
|
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
|
defer interpreter.deinit(&outcome)
|
|
try expect_trap(outcome, &diagnostic_store, codes[index])
|
|
diagnostic :: diagnostics.get(&diagnostic_store, outcome.trap?)?
|
|
try testing.expect(mem.eql(source[usize(diagnostic.span.start)..usize(diagnostic.span.end)], "later"))
|
|
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
|
}
|
|
}
|
|
|
|
loop_and_reentered_arm_locals_are_uninitialized_before_later_declarations test {
|
|
sources [11][]u8 :: [
|
|
"main proc() int {\n n := 2\n while n > 0 {\n if n == 1 { return later }\n later :: 42\n n = n - 1\n }\n return 0\n}\n",
|
|
"main proc() int {\n n := 2\n while n > 0 {\n if n == 1 {\n later = 42\n return later\n }\n later := 0\n n = n - 1\n }\n return 0\n}\n",
|
|
"main proc() int {\n n := 2\n while n > 0 {\n if true {\n if n == 1 { return later }\n later :: 42\n }\n n = n - 1\n }\n return 0\n}\n",
|
|
"main proc() int {\n n := 2\n while n > 0 {\n if true {\n if n == 1 {\n later = 42\n return later\n }\n later := 0\n }\n n = n - 1\n }\n return 0\n}\n",
|
|
"main proc() int {\n n := 2\n while n > 0 {\n if false {} else {\n if n == 1 { return later }\n later :: 42\n }\n n = n - 1\n }\n return 0\n}\n",
|
|
"main proc() int {\n n := 3\n while n > 0 {\n n = n - 1\n if n == 0 { return later }\n if (n == 1) continue\n later :: 42\n continue\n }\n return 0\n}\n",
|
|
"main proc() int {\n n := 3\n while n > 0 {\n n = n - 1\n if n == 0 { later = 42 }\n if (n == 1) continue\n later := 0\n continue\n }\n return 0\n}\n",
|
|
"main proc() int {\n n := 3\n while n > 0 {\n n = n - 1\n if true {\n if n == 0 { return later }\n if (n == 1) continue\n later :: 42\n continue\n }\n }\n return 0\n}\n",
|
|
"main proc() int {\n n := 3\n while n > 0 {\n n = n - 1\n if true {\n if n == 0 { later = 42 }\n if (n == 1) continue\n later := 0\n continue\n }\n }\n return 0\n}\n",
|
|
"main proc() int {\n n := 3\n while n > 0 {\n n = n - 1\n if false {} else {\n if n == 0 { return later }\n if (n == 1) continue\n later :: 42\n continue\n }\n }\n return 0\n}\n",
|
|
"main proc() int {\n n := 3\n while n > 0 {\n n = n - 1\n if false {} else {\n if n == 0 { later = 42 }\n if (n == 1) continue\n later := 0\n continue\n }\n }\n return 0\n}\n",
|
|
]
|
|
codes [11]diagnostics.Code :: [
|
|
.read_before_initialization, .write_before_initialization,
|
|
.read_before_initialization, .write_before_initialization, .read_before_initialization,
|
|
.read_before_initialization, .write_before_initialization,
|
|
.read_before_initialization, .write_before_initialization,
|
|
.read_before_initialization, .write_before_initialization,
|
|
]
|
|
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(0, diagnostics.count(&diagnostic_store))
|
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
|
defer interpreter.deinit(&outcome)
|
|
try expect_trap(outcome, &diagnostic_store, codes[index])
|
|
diagnostic :: diagnostics.get(&diagnostic_store, outcome.trap?)?
|
|
try testing.expect(mem.eql(source[usize(diagnostic.span.start)..usize(diagnostic.span.end)], "later"))
|
|
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
|
}
|
|
}
|
|
|
|
invalid_assignments_and_loop_conditions_preserve_original_diagnostics test {
|
|
sources [9][]u8 :: [
|
|
"main proc() int {\n value :: 0\n value = 1\n return 42\n}\n",
|
|
"update proc(value int) int {\n value = 1\n return value\n}\nmain proc() int { return update(0) }\n",
|
|
"value := 0\nmain proc() int {\n value = 1\n return value\n}\n",
|
|
"main proc() int {\n unknown = 1\n return 42\n}\n",
|
|
"main proc() int {\n value := 0\n value = unknown\n return value\n}\n",
|
|
"main proc() int {\n value := 0\n value = 1.5\n return value\n}\n",
|
|
"first proc(value int) int { return value }\nsecond proc(value bool) int { return 42 }\nmain proc() int {\n invoke := first\n invoke = second\n return 42\n}\n",
|
|
"main proc() int {\n while 1 { return 42 }\n return 0\n}\n",
|
|
"main proc() int {\n while unknown { return 42 }\n return 0\n}\n",
|
|
]
|
|
codes [9]diagnostics.Code :: [.invalid_assignment_target, .invalid_assignment_target, .invalid_assignment_target, .unknown_name, .unknown_name, .type_mismatch, .type_mismatch, .condition_not_bool, .unknown_name]
|
|
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))?
|
|
try testing.expect_equal(codes[index], original.code)
|
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
|
defer interpreter.deinit(&outcome)
|
|
try testing.expect_equal(diagnostics.DiagnosticId(0), outcome.trap?)
|
|
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
|
}
|
|
}
|
|
|
|
zero_iteration_body_errors_are_checked_but_not_executed test {
|
|
sources [5][]u8 :: [
|
|
"main proc() int {\n while false {\n unknown = 1\n }\n return 42\n}\n",
|
|
"main proc() int {\n while false {\n value :: 0\n value = 1\n }\n return 42\n}\n",
|
|
"main proc() int {\n while false {\n value := 0\n value = true\n }\n return 42\n}\n",
|
|
"main proc() int {\n while false {\n return unknown\n }\n return 42\n}\n",
|
|
"main proc() int {\n while false {\n while unknown {}\n }\n return 42\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(diagnostics.count(&diagnostic_store) > 0)
|
|
count :: diagnostics.count(&diagnostic_store)
|
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
|
defer interpreter.deinit(&outcome)
|
|
try testing.expect(outcome.trap == null)
|
|
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
|
try testing.expect_equal(count, diagnostics.count(&diagnostic_store))
|
|
}
|
|
}
|
|
|
|
loop_returns_do_not_guarantee_return_and_assignment_arithmetic_still_checks_overflow test {
|
|
sources [5][]u8 :: [
|
|
"main proc() int {\n while true { return 42 }\n}\n",
|
|
"main proc() int {\n while false { return 42 }\n}\n",
|
|
"main proc() int {\n value := 9223372036854775807\n while true {\n value = value + 1\n return value\n }\n return 0\n}\n",
|
|
"main proc() int {\n while true { break }\n}\n",
|
|
"main proc() int {\n while false { continue }\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)
|
|
if (index != 2) {
|
|
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
|
diagnostic :: diagnostics.get(&diagnostic_store, diagnostics.DiagnosticId(0))?
|
|
try testing.expect_equal(diagnostics.Code.missing_return, diagnostic.code)
|
|
} else try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
|
defer interpreter.deinit(&outcome)
|
|
if (index == 0) {
|
|
try testing.expect(outcome.trap == null)
|
|
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
|
} else {
|
|
code diagnostics.Code :: if (index == 2) .integer_overflow else .missing_return
|
|
try expect_trap(outcome, &diagnostic_store, code)
|
|
if (index != 2) try testing.expect_equal(diagnostics.DiagnosticId(0), outcome.trap?)
|
|
}
|
|
}
|
|
}
|
|
|
|
bounded_pure_loops_do_not_allocate_per_iteration_and_release_runtime_storage test {
|
|
sources [2][]u8 :: [
|
|
"main proc() int {\n n := 0\n while (n < 1 and !(n == 1)) or false {\n local :: n\n n = local + 1\n if (n == 1) break\n continue\n later :: 0\n }\n return n\n}\n",
|
|
"main proc() int {\n n := 0\n while (n < 10000 and !(n == 10000)) or false {\n local :: n\n n = local + 1\n if (n == 10000) break\n continue\n later :: 0\n }\n return n\n}\n",
|
|
]
|
|
expected [2]i64 :: [1, 10000]
|
|
allocations := usize(0)
|
|
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(0, diagnostics.count(&diagnostic_store))
|
|
state := AllocationState{ remaining = 64 }
|
|
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
|
|
outcome := try interpreter.run(allocator, &module, &diagnostic_store)
|
|
defer interpreter.deinit(&outcome)
|
|
try testing.expect(outcome.trap == null)
|
|
try testing.expect_equal(expected[index], outcome.value.data.integer)
|
|
interpreter.deinit(&outcome)
|
|
try testing.expect_equal(usize(0), state.live)
|
|
if (index == 0) allocations = usize(64) - state.remaining
|
|
else try testing.expect_equal(allocations, usize(64) - state.remaining)
|
|
}
|
|
}
|
|
|
|
boolean_truth_tables_nesting_negation_and_precedence_execute test {
|
|
sources [18][]u8 :: [
|
|
"main proc() bool { return false and false }\n",
|
|
"main proc() bool { return false and true }\n",
|
|
"main proc() bool { return true and false }\n",
|
|
"main proc() bool { return true and true }\n",
|
|
"main proc() bool { return false or false }\n",
|
|
"main proc() bool { return false or true }\n",
|
|
"main proc() bool { return true or false }\n",
|
|
"main proc() bool { return true or true }\n",
|
|
"main proc() bool { return !false }\n",
|
|
"main proc() bool { return !true }\n",
|
|
"main proc() bool { return !!true }\n",
|
|
"main proc() bool { return !!!true }\n",
|
|
"main proc() bool { return true or false and false }\n",
|
|
"main proc() bool { return (true or false) and false }\n",
|
|
"main proc() bool { return !(1 + 2 < 3) and 4 == 4 or false }\n",
|
|
"main proc() bool { return false or (true and (false or !false)) }\n",
|
|
"main proc() bool { return true and false or true and true }\n",
|
|
"main proc() bool { return !(-9223372036854775808 < 0) }\n",
|
|
]
|
|
expected [18]bool :: [
|
|
false, false, false, true, false, true, true, true,
|
|
true, false, true, false, true, false, true, true, true, false,
|
|
]
|
|
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(0, diagnostics.count(&diagnostic_store))
|
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
|
defer interpreter.deinit(&outcome)
|
|
try testing.expect(outcome.trap == null)
|
|
try testing.expect_equal(ir.Type.bool, outcome.value.type)
|
|
try testing.expect_equal(expected[index], outcome.value.data.boolean)
|
|
}
|
|
}
|
|
|
|
branchful_calls_preserve_earlier_temps_nested_arguments_and_function_callees test {
|
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
|
defer diagnostics.deinit(&diagnostic_store)
|
|
source ::
|
|
`integer proc(value int) int { return value }
|
|
`boolean proc(value bool) bool { return value }
|
|
`score proc(first int, left bool, right bool, last int) int {
|
|
` if left and right { return first + last }
|
|
` return 0
|
|
`}
|
|
`invoke :: score
|
|
`main proc() int {
|
|
` local :: invoke
|
|
` return local(integer(20), boolean(false or true), boolean(true and (false or true)), local(integer(0), false or boolean(true), true and !false, local(integer(5), true or false, true and true, integer(17))))
|
|
`}
|
|
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)
|
|
defer interpreter.deinit(&outcome)
|
|
try testing.expect(outcome.trap == null)
|
|
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
|
}
|
|
|
|
short_circuit_source_errors_remain_checked_but_skipped_in_every_consumer test {
|
|
sources [10][]u8 :: [
|
|
"main proc() bool { return false and absent or true }\n",
|
|
"main proc() bool { return true or 1 }\n",
|
|
"main proc() bool { return !(false and absent) }\n",
|
|
"main proc() bool { return (false and absent) == false }\n",
|
|
"flag :: true or absent\nmain proc() bool { return flag }\n",
|
|
"main proc() bool {\n flag :: true or absent\n return flag\n}\n",
|
|
"main proc() bool {\n flag := false\n flag = true or absent\n return flag\n}\n",
|
|
"identity proc(value bool) bool { return value }\nmain proc() bool { return identity(true or absent) }\n",
|
|
"main proc() bool {\n if true or absent { return true }\n return false\n}\n",
|
|
"main proc() bool {\n true or absent\n while false and absent {}\n return true\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)
|
|
count :: diagnostics.count(&diagnostic_store)
|
|
try testing.expect(count > 0)
|
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
|
defer interpreter.deinit(&outcome)
|
|
try testing.expect(outcome.trap == null)
|
|
try testing.expect_equal(ir.Type.bool, outcome.value.type)
|
|
try testing.expect(outcome.value.data.boolean)
|
|
try testing.expect_equal(count, diagnostics.count(&diagnostic_store))
|
|
}
|
|
}
|
|
|
|
demanded_boolean_source_errors_keep_the_original_diagnostic_and_operand_span test {
|
|
sources [8][]u8 :: [
|
|
"main proc() bool { return true and absent }\n",
|
|
"main proc() bool { return false or absent }\n",
|
|
"main proc() bool { return true and 1 }\n",
|
|
"main proc() bool { return false or 1 }\n",
|
|
"main proc() bool { return !1 }\n",
|
|
"main proc() bool { return !absent }\n",
|
|
"main proc() bool { return absent and false }\n",
|
|
"main proc() bool { return absent or true }\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))?
|
|
code diagnostics.Code :: if (index >= 2 and index <= 4) .invalid_operand else .unknown_name
|
|
operand []u8 :: if (index >= 2 and index <= 4) "1" else "absent"
|
|
try testing.expect_equal(code, original.code)
|
|
try testing.expect(mem.eql(source[usize(original.span.start)..usize(original.span.end)], operand))
|
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
|
defer interpreter.deinit(&outcome)
|
|
try testing.expect_equal(diagnostics.DiagnosticId(0), outcome.trap?)
|
|
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
|
}
|
|
}
|
|
|
|
branchful_call_arguments_evaluate_left_to_right_before_later_errors test {
|
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
|
defer diagnostics.deinit(&diagnostic_store)
|
|
source ::
|
|
`both proc(left bool, right bool) bool { return left and right }
|
|
`main proc() bool {
|
|
` return both(false or (true and first_absent), true and second_absent)
|
|
`}
|
|
module := try compile_owned(source, &diagnostic_store)
|
|
defer ir.deinit(&module)
|
|
try testing.expect_equal(2, diagnostics.count(&diagnostic_store))
|
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
|
defer interpreter.deinit(&outcome)
|
|
original :: diagnostics.get(&diagnostic_store, outcome.trap?)?
|
|
try testing.expect_equal(diagnostics.Code.unknown_name, original.code)
|
|
try testing.expect(mem.eql(source[usize(original.span.start)..usize(original.span.end)], "first_absent"))
|
|
try testing.expect_equal(2, diagnostics.count(&diagnostic_store))
|
|
}
|
|
|
|
short_circuit_protects_uninitialized_reads_global_cycles_and_overflow test {
|
|
sources [6][]u8 :: [
|
|
"main proc() bool {\n before :: false and later\n later :: true\n return !before\n}\n",
|
|
"main proc() bool {\n before :: true or later\n later :: false\n return before\n}\n",
|
|
"flag bool :: true or flag\nmain proc() bool { return flag }\n",
|
|
"left bool :: false and right\nright bool :: left\nmain proc() bool { return !left }\n",
|
|
"main proc() bool { return !(false and 9223372036854775807 + 1 == 0) }\n",
|
|
"main proc() bool { return true or -(-9223372036854775808) == 0 }\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)
|
|
defer interpreter.deinit(&outcome)
|
|
try testing.expect(outcome.trap == null)
|
|
try testing.expect(outcome.value.data.boolean)
|
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
|
}
|
|
}
|
|
|
|
demanded_boolean_runtime_errors_keep_the_instruction_span test {
|
|
sources [4][]u8 :: [
|
|
"main proc() bool {\n before :: true and later\n later :: true\n return before\n}\n",
|
|
"flag bool :: false or flag\nmain proc() bool { return flag }\n",
|
|
"main proc() bool { return true and 9223372036854775807 + 1 == 0 }\n",
|
|
"main proc() bool {\n minimum :: -9223372036854775808\n return false or -minimum == 0\n}\n",
|
|
]
|
|
codes [4]diagnostics.Code :: [.read_before_initialization, .initialization_cycle, .integer_overflow, .integer_overflow]
|
|
spans [4][]u8 :: ["later", "flag", "9223372036854775807 + 1", "-minimum"]
|
|
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(0, diagnostics.count(&diagnostic_store))
|
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
|
defer interpreter.deinit(&outcome)
|
|
try expect_trap(outcome, &diagnostic_store, codes[index])
|
|
diagnostic :: diagnostics.get(&diagnostic_store, outcome.trap?)?
|
|
try testing.expect(mem.eql(source[usize(diagnostic.span.start)..usize(diagnostic.span.end)], spans[index]))
|
|
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
|
}
|
|
}
|
|
|
|
composed_loop_guards_recompute_and_overwrite_synthetic_results test {
|
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
|
defer diagnostics.deinit(&diagnostic_store)
|
|
source ::
|
|
`enabled :: false or true and !false
|
|
`positive proc(n int) bool { return n > 0 }
|
|
`main proc() int {
|
|
` n := 4
|
|
` total := 0
|
|
` while (enabled and positive(n)) or n > 4 {
|
|
` if (n == 4 or n == 2) and !(n == 3) {
|
|
` total = total + 21
|
|
` }
|
|
` n = n - 1
|
|
` }
|
|
` return total + n
|
|
`}
|
|
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)
|
|
defer interpreter.deinit(&outcome)
|
|
try testing.expect(outcome.trap == null)
|
|
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
|
}
|
|
|
|
lazy_branchful_global_initializers_and_calls_release_every_runtime_allocation test {
|
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
|
defer diagnostics.deinit(&diagnostic_store)
|
|
source ::
|
|
`first :: true and later
|
|
`later :: false or !false
|
|
`both proc(left bool, right bool) bool { return left and right }
|
|
`bound proc(flag bool, value int) int {
|
|
` if flag { return value }
|
|
` return 0
|
|
`}
|
|
`main proc() bool {
|
|
` n := 3
|
|
` for bound(first or false, 0)..bound(both(first, true and later), 3) |i| {
|
|
` local :: both(first and first, later or false)
|
|
` if (local and i < 2) continue else break
|
|
` }
|
|
` while both(first and first, both(false or later, !false and later)) and n > 0 {
|
|
` n = n - 1
|
|
` if (n > 0) continue else break
|
|
` }
|
|
` return both(first and first, both(false or later, !false and later))
|
|
`}
|
|
module := try compile_owned(source, &diagnostic_store)
|
|
defer ir.deinit(&module)
|
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
|
failures := usize(0)
|
|
succeeded := false
|
|
for (usize(0)..usize(64)) |limit| {
|
|
state := AllocationState{ remaining = limit }
|
|
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
|
|
outcome := interpreter.run(allocator, &module, &diagnostic_store) catch |error| {
|
|
try testing.expect(error == .out_of_memory)
|
|
failures += 1
|
|
try testing.expect_equal(usize(0), state.live)
|
|
continue
|
|
}
|
|
defer interpreter.deinit(&outcome)
|
|
try testing.expect(outcome.trap == null)
|
|
try testing.expect(outcome.value.data.boolean)
|
|
interpreter.deinit(&outcome)
|
|
try testing.expect_equal(usize(0), state.live)
|
|
succeeded = true
|
|
break
|
|
}
|
|
try testing.expect(succeeded)
|
|
try testing.expect(failures > 0)
|
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
|
}
|
|
|
|
skipped_rhs_does_not_hide_a_demanded_outer_type_mismatch test {
|
|
sources [3][]u8 :: [
|
|
"main proc() int { return true or absent }\n",
|
|
"main proc() void {\n number := 0\n number = true or absent\n}\n",
|
|
"integer proc(value int) int { return value }\nmain proc() int { return integer(true or absent) }\n",
|
|
]
|
|
codes [3]diagnostics.Code :: [.return_type_mismatch, .type_mismatch, .type_mismatch]
|
|
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)
|
|
count :: diagnostics.count(&diagnostic_store)
|
|
try testing.expect_equal(2, count)
|
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
|
defer interpreter.deinit(&outcome)
|
|
try expect_trap(outcome, &diagnostic_store, codes[index])
|
|
try testing.expect_equal(count, diagnostics.count(&diagnostic_store))
|
|
}
|
|
}
|
|
|
|
break_and_continue_skip_iterations_exit_early_and_restore_outer_targets test {
|
|
sources [3][]u8 :: [
|
|
"main proc() int {\n n := 0\n total := 0\n while n < 10 {\n n = n + 1\n if (n == 2 or n == 4) continue\n if (n == 6) break\n total = total + n\n }\n return total + 33\n}\n",
|
|
"main proc() int {\n outer := 3\n total := 0\n while outer > 0 {\n outer = outer - 1\n inner := 3\n while inner > 0 {\n inner = inner - 1\n if (inner == 1) continue\n if (inner == 0) break\n total = total + 7\n }\n if (outer > 0) continue else break\n }\n return total + 21\n}\n",
|
|
"main proc() int {\n n := 0\n while n < 5 {\n n = n + 1\n if (n < 3) continue else break\n n = n + 100\n }\n return n + 39\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)
|
|
defer interpreter.deinit(&outcome)
|
|
try testing.expect(outcome.trap == null)
|
|
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
|
}
|
|
}
|
|
|
|
continue_restarts_composed_calling_guards_after_branchful_body_calls test {
|
|
guards [3][]u8 :: [
|
|
"(positive(3 - n) and !(n == 2)) or n == 2",
|
|
"!(n >= 3) and (positive(3 - n) or false)",
|
|
"positive(3 - n) and ((n < 3 or false) and !false)",
|
|
]
|
|
for (guards) |guard| {
|
|
source := arraylist.init(u8, mem.c_allocator)
|
|
defer arraylist.deinit(&source)
|
|
for ("positive proc(n int) bool { return n > 0 }\nadvance proc(flag bool, n int) int {\n if flag { return n + 1 }\n return n + 1\n}\nmain proc() int {\n n := 0\n while ") |byte| try arraylist.append(&source, byte)
|
|
for (guard) |byte| try arraylist.append(&source, byte)
|
|
# bound a stale guard too, so incorrect continue lowering fails instead of hanging.
|
|
for (" {\n n = advance(n == 0 or n == 1, n)\n if (n > 5) break\n continue\n n = n + 100\n }\n return n + 39\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)
|
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
|
defer interpreter.deinit(&outcome)
|
|
try testing.expect(outcome.trap == null)
|
|
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
|
}
|
|
}
|
|
|
|
reached_outside_controls_preserve_diagnostic_identity_and_keyword_span test {
|
|
sources [4][]u8 :: [
|
|
"main proc() int {\n break\n return 42\n}\n",
|
|
"main proc() int {\n continue\n return 42\n}\n",
|
|
"bad proc() void {\n break\n}\nmain proc() int {\n while true { bad()\n break }\n return 42\n}\n",
|
|
"main proc() int {\n while true { break }\n continue\n return 42\n}\n",
|
|
]
|
|
codes [4]diagnostics.Code :: [.break_outside_loop, .continue_outside_loop, .break_outside_loop, .continue_outside_loop]
|
|
keywords [4][]u8 :: ["break", "continue", "break", "continue"]
|
|
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))?
|
|
try testing.expect_equal(codes[index], original.code)
|
|
try testing.expect(mem.eql(source[usize(original.span.start)..usize(original.span.end)], keywords[index]))
|
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
|
defer interpreter.deinit(&outcome)
|
|
try testing.expect_equal(diagnostics.DiagnosticId(0), outcome.trap?)
|
|
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
|
}
|
|
}
|
|
|
|
untaken_outside_controls_remain_checked_without_trapping test {
|
|
sources [5][]u8 :: [
|
|
"main proc() int {\n if (false) break\n return 42\n}\n",
|
|
"main proc() int {\n if true { return 42 } else continue\n}\n",
|
|
"main proc() int {\n return 42\n break\n}\n",
|
|
"main proc() int {\n return 42\n continue\n}\n",
|
|
"bad proc() void {\n continue\n}\nmain proc() int {\n while false { bad() }\n return 42\n}\n",
|
|
]
|
|
codes [5]diagnostics.Code :: [.break_outside_loop, .continue_outside_loop, .break_outside_loop, .continue_outside_loop, .continue_outside_loop]
|
|
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))?
|
|
try testing.expect_equal(codes[index], original.code)
|
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
|
defer interpreter.deinit(&outcome)
|
|
try testing.expect(outcome.trap == null)
|
|
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
|
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
|
}
|
|
}
|
|
|
|
source_errors_after_loop_transfers_stay_checked_but_unreached test {
|
|
sources [4][]u8 :: [
|
|
"main proc() int {\n n := 2\n while n > 0 {\n n = n - 1\n continue\n missing\n }\n return 42\n}\n",
|
|
"main proc() int {\n while true {\n break\n missing\n }\n return 42\n}\n",
|
|
"main proc() int {\n n := 2\n while n > 0 {\n n = n - 1\n if (n > 0) continue else break\n missing\n }\n return 42\n}\n",
|
|
"main proc() int {\n while true {\n return 42\n break\n continue\n missing\n }\n return 0\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(1, diagnostics.count(&diagnostic_store))
|
|
original :: diagnostics.get(&diagnostic_store, diagnostics.DiagnosticId(0))?
|
|
try testing.expect_equal(diagnostics.Code.unknown_name, original.code)
|
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
|
defer interpreter.deinit(&outcome)
|
|
try testing.expect(outcome.trap == null)
|
|
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
|
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
|
}
|
|
}
|
|
|
|
traps_before_loop_controls_keep_the_original_error test {
|
|
sources [2][]u8 :: [
|
|
"main proc() int {\n while true {\n missing\n break\n }\n return 42\n}\n",
|
|
"main proc() int {\n while true {\n missing\n continue\n }\n return 42\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(1, diagnostics.count(&diagnostic_store))
|
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
|
defer interpreter.deinit(&outcome)
|
|
try expect_trap(outcome, &diagnostic_store, .unknown_name)
|
|
try testing.expect_equal(diagnostics.DiagnosticId(0), outcome.trap?)
|
|
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
|
}
|
|
}
|
|
|
|
owned_integer_ranges_execute_exclusive_inclusive_empty_signed_and_discard_forms test {
|
|
sources [13][]u8 :: [
|
|
"main proc() int {\n sum := 0\n for 0..7 |i| { sum = sum + i }\n return sum\n}\n",
|
|
"main proc() int {\n sum := 0\n for (0..=6) |i| { sum = sum + i }\n return sum\n}\n",
|
|
"main proc() int {\n sum := 0\n for (-3)..3 |i| { sum = sum + i }\n return sum\n}\n",
|
|
"main proc() int {\n sum := 0\n for (-3)..=3 |i| { sum = sum + i }\n return sum\n}\n",
|
|
"main proc() int {\n sum := 0\n for 7..7 |i| { sum = sum + i }\n return sum\n}\n",
|
|
"main proc() int {\n sum := 0\n for 7..=7 |i| { sum = sum + i }\n return sum\n}\n",
|
|
"main proc() int {\n count := 0\n for 7..2 |_| { count = count + 1 }\n for 7..=2 |_| { count = count + 1 }\n return count\n}\n",
|
|
"main proc() int {\n count := 0\n for (0..4) |_| count = count + 1\n return count\n}\n",
|
|
"main proc() int {\n count := 0\n for 9223372036854775807..=9223372036854775807 |i| {\n count = count + 1\n if (count > 2) break\n continue\n }\n return count\n}\n",
|
|
"main proc() int {\n count := 0\n for 9223372036854775806..=9223372036854775807 |_| {\n count = count + 1\n if (count > 3) break\n continue\n }\n return count\n}\n",
|
|
"main proc() int {\n count := 0\n for 9223372036854775806..9223372036854775807 |i| {\n count = count + 1\n if (count > 2) break\n continue\n }\n return count\n}\n",
|
|
"main proc() int {\n for (-9223372036854775808)..(-9223372036854775807) |i| { return i }\n return 0\n}\n",
|
|
"main proc() int {\n sum := 0\n for 0..(3 + 1) |i| { sum = sum + i }\n return sum\n}\n",
|
|
]
|
|
expected [13]i64 :: [21, 21, -3, 0, 0, 7, 0, 4, 1, 2, 1, minval!(i64), 6]
|
|
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(0, diagnostics.count(&diagnostic_store))
|
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
|
defer interpreter.deinit(&outcome)
|
|
try testing.expect(outcome.trap == null)
|
|
try testing.expect_equal(ir.Type.int, outcome.value.type)
|
|
try testing.expect_equal(expected[index], outcome.value.data.integer)
|
|
}
|
|
}
|
|
|
|
range_bounds_calls_and_globals_are_frozen_before_branchful_body_calls test {
|
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
|
defer diagnostics.deinit(&diagnostic_store)
|
|
source ::
|
|
`seed :: choose(true or false, identity(0))
|
|
`limit :: choose(false or true, identity(4))
|
|
`identity proc(value int) int { return value }
|
|
`choose proc(flag bool, value int) int {
|
|
` if flag { return value }
|
|
` return 99
|
|
`}
|
|
`main proc() int {
|
|
` first := seed
|
|
` last := limit
|
|
` sum := 0
|
|
` for choose(true and !false, identity(first))..choose(false or true, choose(true or false, last)) |i| {
|
|
` sum = choose(i == 0 or i > 0, identity(sum + i))
|
|
` first = 100
|
|
` last = 0
|
|
` }
|
|
` return sum + 36
|
|
`}
|
|
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)
|
|
defer interpreter.deinit(&outcome)
|
|
try testing.expect(outcome.trap == null)
|
|
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
|
}
|
|
|
|
range_break_continue_and_nested_for_while_restore_lexical_targets test {
|
|
sources [3][]u8 :: [
|
|
"main proc() int {\n sum := 0\n count := 0\n for 0..10 |i| {\n count = count + 1\n if (count > 12) break\n if (i == 2 or i == 4) continue\n if (i == 6) break\n sum = sum + i\n }\n return sum + 33\n}\n",
|
|
"main proc() int {\n sum := 0\n count := 0\n for 0..3 |outer| {\n count = count + 1\n if (count > 4) break\n inner := 3\n while inner > 0 {\n inner = inner - 1\n if (inner == 1) continue\n if (inner == 0) break\n sum = sum + 7\n }\n if (outer < 2) continue else break\n }\n return sum + 21\n}\n",
|
|
"main proc() int {\n outer := 3\n sum := 0\n while outer > 0 {\n outer = outer - 1\n count := 0\n for 0..3 |i| {\n count = count + 1\n if (count > 4) break\n for 0..3 |j| {\n if (j == 0) continue\n sum = sum + 7\n break\n }\n if (i == 0) continue else break\n }\n if (outer > 0) continue else break\n }\n return sum\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)
|
|
defer interpreter.deinit(&outcome)
|
|
try testing.expect(outcome.trap == null)
|
|
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
|
}
|
|
}
|
|
|
|
range_body_and_arm_locals_reset_before_continued_later_declarations test {
|
|
sources [6][]u8 :: [
|
|
"main proc() int {\n for 0..3 |i| {\n if i == 2 { return later }\n if (i == 1) continue\n later :: 42\n continue\n }\n return 0\n}\n",
|
|
"main proc() int {\n for 0..3 |i| {\n if i == 2 { later = 42 }\n if (i == 1) continue\n later := 0\n continue\n }\n return 0\n}\n",
|
|
"main proc() int {\n for 0..3 |i| {\n if true {\n if i == 2 { return later }\n if (i == 1) continue\n later :: 42\n continue\n }\n }\n return 0\n}\n",
|
|
"main proc() int {\n for 0..3 |i| {\n if true {\n if i == 2 { later = 42 }\n if (i == 1) continue\n later := 0\n continue\n }\n }\n return 0\n}\n",
|
|
"main proc() int {\n for 0..3 |i| {\n if false {} else {\n if i == 2 { return later }\n if (i == 1) continue\n later :: 42\n continue\n }\n }\n return 0\n}\n",
|
|
"main proc() int {\n for 0..3 |i| {\n if false {} else {\n if i == 2 { later = 42 }\n if (i == 1) continue\n later := 0\n continue\n }\n }\n return 0\n}\n",
|
|
]
|
|
codes [6]diagnostics.Code :: [
|
|
.read_before_initialization, .write_before_initialization,
|
|
.read_before_initialization, .write_before_initialization,
|
|
.read_before_initialization, .write_before_initialization,
|
|
]
|
|
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(0, diagnostics.count(&diagnostic_store))
|
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
|
defer interpreter.deinit(&outcome)
|
|
try expect_trap(outcome, &diagnostic_store, codes[index])
|
|
diagnostic :: diagnostics.get(&diagnostic_store, outcome.trap?)?
|
|
try testing.expect(mem.eql(source[usize(diagnostic.span.start)..usize(diagnostic.span.end)], "later"))
|
|
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
|
}
|
|
}
|
|
|
|
untaken_range_errors_and_errors_after_controls_remain_diagnostic_only test {
|
|
sources [10][]u8 :: [
|
|
"main proc() int {\n for 0..0 |i| { return unknown }\n return 42\n}\n",
|
|
"main proc() int {\n for 3..2 |i| { i = 1 }\n return 42\n}\n",
|
|
"main proc() int {\n i :: 0\n for 0..0 |i| {}\n return 42\n}\n",
|
|
"main proc() int {\n for 0..3 |i| {\n continue\n missing\n }\n return 42\n}\n",
|
|
"main proc() int {\n for 0..3 |i| {\n break\n missing\n }\n return 42\n}\n",
|
|
"main proc() int {\n for 0..3 |i| {\n return 42\n break\n continue\n missing\n }\n return 0\n}\n",
|
|
"unused proc() int {\n for 0..1 |i| { return unknown }\n return 0\n}\nmain proc() int { return 42 }\n",
|
|
"main proc() int {\n if false { for true..1 |i| {} }\n return 42\n}\n",
|
|
"main proc() int {\n for 0..0 |int| {}\n return 42\n}\n",
|
|
"unsupported :: 0..1\nmain proc() int {\n for 0..0 |_| { local :: 0..1 }\n return 42\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)
|
|
count :: diagnostics.count(&diagnostic_store)
|
|
try testing.expect(count > 0)
|
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
|
defer interpreter.deinit(&outcome)
|
|
try testing.expect(outcome.trap == null)
|
|
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
|
try testing.expect_equal(count, diagnostics.count(&diagnostic_store))
|
|
}
|
|
}
|
|
|
|
demanded_range_header_capture_and_body_errors_keep_original_diagnostic_identity test {
|
|
sources [10][]u8 :: [
|
|
"main proc() int {\n for true..1 |i| {}\n return 42\n}\n",
|
|
"main proc() int {\n for 0..1 |i| { i = 1 }\n return 42\n}\n",
|
|
"main proc() int {\n i :: 0\n for 0..1 |i| {}\n return 42\n}\n",
|
|
"main proc() int {\n for 0..1 |i| {\n missing\n continue\n }\n return 42\n}\n",
|
|
"main proc() int {\n for 0..1 |i| {\n missing\n break\n }\n return 42\n}\n",
|
|
"main proc() int {\n for 1 |i| {}\n return 42\n}\n",
|
|
"main proc() int {\n 0..1\n return 42\n}\n",
|
|
"main proc() int {\n for 0..1 |@i| {}\n return 42\n}\n",
|
|
"main proc() int {\n for 0..1 |i, _| {}\n return 42\n}\n",
|
|
"main proc() int {\n local :: 0..1\n return 42\n}\n",
|
|
]
|
|
codes [10]diagnostics.Code :: [
|
|
.invalid_range_bound, .invalid_assignment_target, .duplicate_name,
|
|
.unknown_name, .unknown_name, .invalid_for_iterable, .range_value_not_supported,
|
|
.range_pointer_capture, .range_index_capture, .range_value_not_supported,
|
|
]
|
|
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))?
|
|
try testing.expect_equal(codes[index], original.code)
|
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
|
defer interpreter.deinit(&outcome)
|
|
try testing.expect_equal(diagnostics.DiagnosticId(0), outcome.trap?)
|
|
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
|
}
|
|
}
|
|
|
|
range_bounds_evaluate_left_to_right_before_header_errors_and_body_overflow_keeps_span test {
|
|
sources [4][]u8 :: [
|
|
"first :: fail()\nsecond :: absent\nfail proc() int { return 9223372036854775807 + 1 }\nmain proc() int {\n for first..second |i| {}\n return 42\n}\n",
|
|
"fail proc() int { return 9223372036854775807 + 1 }\nmain proc() int {\n for fail()..1 |@i| {}\n return 42\n}\n",
|
|
"main proc() int {\n for 0..1 |i| { return 9223372036854775807 + i + 1 }\n return 0\n}\n",
|
|
"main proc() int {\n for first_absent..second_absent |i| {}\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)
|
|
before :: diagnostics.count(&diagnostic_store)
|
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
|
defer interpreter.deinit(&outcome)
|
|
code diagnostics.Code :: if (index == 3) .unknown_name else .integer_overflow
|
|
try expect_trap(outcome, &diagnostic_store, code)
|
|
diagnostic :: diagnostics.get(&diagnostic_store, outcome.trap?)?
|
|
span []u8 :: match index {
|
|
0, 1: "9223372036854775807 + 1"
|
|
2: "9223372036854775807 + i + 1"
|
|
3: "first_absent"
|
|
else: unreachable
|
|
}
|
|
try testing.expect(mem.eql(source[usize(diagnostic.span.start)..usize(diagnostic.span.end)], span))
|
|
if (index == 3) try testing.expect_equal(diagnostics.DiagnosticId(0), outcome.trap?)
|
|
else try testing.expect_equal(diagnostics.DiagnosticId(before), outcome.trap?)
|
|
}
|
|
}
|
|
|
|
range_runtime_allocations_do_not_grow_with_iterations test {
|
|
sources [2][]u8 :: [
|
|
"main proc() int {\n count := 0\n for 0..1 |i| {\n local :: i\n count = count + 1\n if (count > 1) break\n if (local >= 0) continue\n later :: 0\n }\n return count\n}\n",
|
|
"main proc() int {\n count := 0\n for 0..10000 |i| {\n local :: i\n count = count + 1\n if (count > 10000) break\n if (local >= 0) continue\n later :: 0\n }\n return count\n}\n",
|
|
]
|
|
expected [2]i64 :: [1, 10000]
|
|
allocations := usize(0)
|
|
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(0, diagnostics.count(&diagnostic_store))
|
|
state := AllocationState{ remaining = 64 }
|
|
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
|
|
outcome := try interpreter.run(allocator, &module, &diagnostic_store)
|
|
defer interpreter.deinit(&outcome)
|
|
try testing.expect(outcome.trap == null)
|
|
try testing.expect_equal(expected[index], outcome.value.data.integer)
|
|
interpreter.deinit(&outcome)
|
|
try testing.expect_equal(usize(0), state.live)
|
|
if (index == 0) allocations = usize(64) - state.remaining
|
|
else try testing.expect_equal(allocations, usize(64) - state.remaining)
|
|
}
|
|
}
|
|
|
|
array_mutation_copies_rows_wholes_and_by_value_calls test {
|
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
|
defer diagnostics.deinit(&diagnostic_store)
|
|
source ::
|
|
`seed :: later
|
|
`later [2]mut [2]mut i64 :: [[1, 2], [3, 4]]
|
|
`identity proc(value [2]mut [2]mut i64) [2]mut [2]mut i64 { return value }
|
|
`changed proc(value [2]mut [2]mut i64) [2]mut [2]mut i64 {
|
|
` copy := value
|
|
` (copy[0])[0] = 20
|
|
` return copy
|
|
`}
|
|
`main proc() bool {
|
|
` a := identity(identity(seed))
|
|
` row := a[0]
|
|
` row[0] = 9
|
|
` b := changed(identity(a))
|
|
` (b[1])[1] = 30
|
|
` c := a
|
|
` c = b
|
|
` (b[0])[1] = 40
|
|
` a[1] = row
|
|
` row[1] = 50
|
|
` count usize :: 2
|
|
` sum := 0
|
|
` for 0..count |i| {
|
|
` for 0..count |j| { sum = sum + a[i][j] }
|
|
` }
|
|
` return sum == 14 and seed[0][0] == 1 and seed[1][1] == 4 and c[0][0] == 20 and c[0][1] == 2 and c[1][1] == 30
|
|
`}
|
|
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)
|
|
defer interpreter.deinit(&outcome)
|
|
try testing.expect(outcome.trap == null)
|
|
try testing.expect(outcome.value.data.boolean)
|
|
}
|
|
|
|
array_context_inference_empty_shapes_and_scalar_leaf_families test {
|
|
sources [8][]u8 :: [
|
|
"main proc() bool {\n a [_]mut i64 := [1, 2, 3]\n a[1] = 9\n return a[1] == 9\n}\n",
|
|
"main proc() bool {\n a [0]bool :: []\n b :: []\n return true\n}\n",
|
|
"main proc() bool {\n a [2]mut f64 := [1, 2.5]\n a[0] = 3.5\n return a[0] + a[1] == 6.0\n}\n",
|
|
"main proc() bool {\n a [2]mut bool := [false or true, true and false]\n a[1] = true\n return a[0] and a[1]\n}\n",
|
|
"main proc() string {\n a [2]mut string := [\"one\", \"two\"]\n b := a\n a[1] = \"three\"\n return b[1]\n}\n",
|
|
"plus proc(n int) int { return n + 1 }\nminus proc(n int) int { return n - 1 }\nmain proc() bool {\n a :: [plus, minus]\n return a[0](41) == 42 and a[1](43) == 42\n}\n",
|
|
"make proc() [2]i64 { return [20, 22] }\nmain proc() bool {\n return make()[0] + ([20, 22])[1] == 42\n}\n",
|
|
"count :: forwarded + 1\nforwarded :: 1\nmain proc() bool {\n a [count][0]bool :: [[], []]\n return true\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)
|
|
defer interpreter.deinit(&outcome)
|
|
try testing.expect(outcome.trap == null)
|
|
if (outcome.value.type == .string) {
|
|
try testing.expect(mem.eql(ir.string_bytes(&module, outcome.value.data.string), "two"))
|
|
} else {
|
|
try testing.expect(outcome.value.data.boolean)
|
|
}
|
|
}
|
|
}
|
|
|
|
array_literal_scratch_handles_nested_calls_and_branchful_elements test {
|
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
|
defer diagnostics.deinit(&diagnostic_store)
|
|
source ::
|
|
`identity proc(value [2]bool) [2]bool { return value }
|
|
`pair proc(left [2]bool, right [2]bool) [2][2]bool { return [left, right] }
|
|
`main proc() bool {
|
|
` value :: pair(identity([true or false, false and true]), identity(identity([false or true, true and !false])))
|
|
` return value[0][0] and !value[0][1] and value[1][0] and value[1][1]
|
|
`}
|
|
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)
|
|
defer interpreter.deinit(&outcome)
|
|
try testing.expect(outcome.trap == null)
|
|
try testing.expect(outcome.value.data.boolean)
|
|
}
|
|
|
|
array_bounds_roots_and_mutability_trap_at_demanded_positions test {
|
|
sources [12][]u8 :: [
|
|
"main proc() int {\n a :: [1]\n return a[1]\n}\n",
|
|
"main proc() int {\n a [0]i64 :: []\n return a[0]\n}\n",
|
|
"main proc() int {\n a [1]mut i64 := [1]\n a[18446744073709551615] = 2\n return 0\n}\n",
|
|
"main proc() int {\n a [1]mut i64 :: [1]\n a[0] = 2\n return 0\n}\n",
|
|
"main proc() int {\n a := [1]\n a[0] = 2\n return 0\n}\n",
|
|
"a [1]mut i64 :: [1]\nmain proc() int {\n a[0] = 2\n return 0\n}\n",
|
|
"bad proc(a [1]mut i64) int {\n a[0] = 2\n return 0\n}\nmain proc() int {\n return bad([1])\n}\n",
|
|
"main proc() int {\n a [1]mut [1]i64 := [[1]]\n (a[0])[0] = 2\n return 0\n}\n",
|
|
"main proc() int {\n a [1][1]mut i64 := [[1]]\n (a[0])[0] = 2\n return 0\n}\n",
|
|
"a [1]i64 :: b\nb [1]i64 :: a\nmain proc() int {\n return a[0]\n}\n",
|
|
"main proc() int {\n before :: after[0]\n after [1]i64 :: [1]\n return before\n}\n",
|
|
"main proc() int {\n [1]i64\n return 0\n}\n",
|
|
]
|
|
codes [12]diagnostics.Code :: [
|
|
.index_out_of_bounds, .index_out_of_bounds, .index_out_of_bounds,
|
|
.invalid_index_assignment, .invalid_index_assignment, .invalid_index_assignment,
|
|
.invalid_index_assignment, .invalid_index_assignment, .invalid_index_assignment,
|
|
.initialization_cycle, .read_before_initialization, .type_not_value,
|
|
]
|
|
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)
|
|
defer interpreter.deinit(&outcome)
|
|
try expect_trap(outcome, &diagnostic_store, codes[i])
|
|
}
|
|
}
|
|
|
|
array_assignment_rhs_and_nested_bounds_preserve_error_order_and_identity test {
|
|
sources [5][]u8 :: [
|
|
"rhs proc() i64 { return absent_rhs }\nmain proc() int {\n a [0]mut i64 := []\n a[absent_index] = rhs()\n return 0\n}\n",
|
|
"index proc() usize { return absent_inner }\nmain proc() int {\n a [1][1]i64 :: [[1]]\n return a[2][index()]\n}\n",
|
|
"rhs proc() i64 { return absent_rhs }\nmain proc() int {\n a :: [1]\n a[0] = rhs()\n return 0\n}\n",
|
|
"main proc() int {\n a [0]mut i64 := []\n a[0] = 9223372036854775807 + 1\n return 0\n}\n",
|
|
"main proc() int {\n a [1]mut i64 := [1]\n a[absent_index] = absent_rhs\n return 0\n}\n",
|
|
]
|
|
codes [5]diagnostics.Code :: [.unknown_name, .index_out_of_bounds, .unknown_name, .integer_overflow, .unknown_name]
|
|
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)
|
|
before :: diagnostics.count(&diagnostic_store)
|
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
|
defer interpreter.deinit(&outcome)
|
|
try expect_trap(outcome, &diagnostic_store, codes[i])
|
|
if (i == 0 or i == 2 or i == 4) {
|
|
diagnostic :: diagnostics.get(&diagnostic_store, outcome.trap?)?
|
|
try testing.expect(mem.eql(source[usize(diagnostic.span.start)..usize(diagnostic.span.end)], "absent_rhs"))
|
|
try testing.expect_equal(before, diagnostics.count(&diagnostic_store))
|
|
}
|
|
}
|
|
}
|
|
|
|
usize_arithmetic_and_guarded_ranges_use_unsigned_full_width test {
|
|
sources [5][]u8 :: [
|
|
"main proc() bool {\n max usize :: 18446744073709551615\n n := 0\n for max..=max |i| {\n if (i != max) return false\n n = n + 1\n continue\n }\n return n == 1\n}\n",
|
|
"main proc() bool {\n max usize :: 18446744073709551615\n zero usize :: 0\n n := 0\n for max..max |_| { n = n + 1 }\n for zero..=zero |_| { n = n + 1 }\n return n == 1\n}\n",
|
|
"main proc() bool {\n start usize :: 18446744073709551614\n end usize :: 18446744073709551615\n n := 0\n for start..=end |i| {\n n = n + 1\n continue\n }\n return n == 2\n}\n",
|
|
"main proc() bool {\n a usize :: 9223372036854775808\n return a > 9223372036854775807 and a * 1 + 1 - 1 == a\n}\n",
|
|
"main proc() bool {\n end usize :: 3\n sum usize := 0\n for 0..end |i| { sum = sum + i }\n return sum == 3\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)
|
|
defer interpreter.deinit(&outcome)
|
|
try testing.expect(outcome.trap == null)
|
|
try testing.expect(outcome.value.data.boolean)
|
|
}
|
|
}
|
|
|
|
aggregate_loop_frames_allocate_once_and_reset_local_freshness test {
|
|
sources [2][]u8 :: [
|
|
"main proc() int {\n end usize :: 1\n count := 0\n a [1]mut i64 := [0]\n for 0..end |i| {\n row [1]mut i64 := [a[0]]\n row[0] = row[0] + 1\n a = row\n count = count + 1\n }\n return count + a[0]\n}\n",
|
|
"main proc() int {\n end usize :: 10000\n count := 0\n a [1]mut i64 := [0]\n for 0..end |i| {\n row [1]mut i64 := [a[0]]\n row[0] = row[0] + 1\n a = row\n count = count + 1\n }\n return count + a[0]\n}\n",
|
|
]
|
|
expected [2]i64 :: [2, 20000]
|
|
allocations := usize(0)
|
|
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))
|
|
state := AllocationState{ remaining = 64 }
|
|
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
|
|
outcome := try interpreter.run(allocator, &module, &diagnostic_store)
|
|
defer interpreter.deinit(&outcome)
|
|
try testing.expect(outcome.trap == null)
|
|
try testing.expect_equal(expected[i], outcome.value.data.integer)
|
|
interpreter.deinit(&outcome)
|
|
try testing.expect_equal(usize(0), state.live)
|
|
if (i == 0) allocations = usize(64) - state.remaining
|
|
else try testing.expect_equal(allocations, usize(64) - state.remaining)
|
|
}
|
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
|
defer diagnostics.deinit(&diagnostic_store)
|
|
module := try compile_owned(
|
|
"main proc() int {\n end usize :: 2\n for 0..end |i| {\n if (i == 1) return later[0]\n later [1]i64 :: [42]\n }\n return 0\n}\n",
|
|
&diagnostic_store,
|
|
)
|
|
defer ir.deinit(&module)
|
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
|
defer interpreter.deinit(&outcome)
|
|
try expect_trap(outcome, &diagnostic_store, .read_before_initialization)
|
|
}
|
|
|
|
escaped_nested_array_returns_survive_frontend_and_frames_and_release_on_oom test {
|
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
|
defer diagnostics.deinit(&diagnostic_store)
|
|
source ::
|
|
`later [2][2]string :: [["owned\none", "two"], ["three", "four"]]
|
|
`first :: identity(later)
|
|
`identity proc(value [2][2]string) [2][2]string { return value }
|
|
`main proc() [2][2]string { return identity(identity(first)) }
|
|
module := try compile_owned(source, &diagnostic_store)
|
|
defer ir.deinit(&module)
|
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
|
succeeded := false
|
|
failures := usize(0)
|
|
for (usize(0)..usize(512)) |limit| {
|
|
state := AllocationState{ remaining = limit }
|
|
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
|
|
outcome := interpreter.run(allocator, &module, &diagnostic_store) catch |error| {
|
|
try testing.expect(error == .out_of_memory)
|
|
failures += 1
|
|
try testing.expect_equal(usize(0), state.live)
|
|
continue
|
|
}
|
|
defer interpreter.deinit(&outcome)
|
|
try testing.expect(outcome.trap == null)
|
|
try testing.expect_equal(ir.Type.array, outcome.value.type)
|
|
try testing.expect_equal(usize(4), outcome.value.data.elements.len)
|
|
try testing.expect(mem.eql(ir.string_bytes(&module, outcome.value.data.elements[0].data.string), "owned\none"))
|
|
try testing.expect(mem.eql(ir.string_bytes(&module, outcome.value.data.elements[3].data.string), "four"))
|
|
try testing.expect(state.live > 0)
|
|
interpreter.deinit(&outcome)
|
|
try testing.expect_equal(usize(0), state.live)
|
|
succeeded = true
|
|
break
|
|
}
|
|
try testing.expect(succeeded)
|
|
try testing.expect(failures > 0)
|
|
}
|
|
|
|
unsigned_arithmetic_reports_overflow_without_signed_narrowing test {
|
|
sources [3][]u8 :: [
|
|
"main proc() usize {\n return 18446744073709551615 + 1\n}\n",
|
|
"main proc() usize {\n return 0 - 1\n}\n",
|
|
"main proc() usize {\n return 9223372036854775808 * 2\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)
|
|
defer interpreter.deinit(&outcome)
|
|
try expect_trap(outcome, &diagnostic_store, .integer_overflow)
|
|
}
|
|
}
|
|
|
|
array_index_write_checks_initialization_after_rhs test {
|
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
|
defer diagnostics.deinit(&diagnostic_store)
|
|
module := try compile_owned(
|
|
"main proc() int {\n a[0] = 42\n a [1]mut i64 := [0]\n return 0\n}\n",
|
|
&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)
|
|
defer interpreter.deinit(&outcome)
|
|
try expect_trap(outcome, &diagnostic_store, .write_before_initialization)
|
|
}
|
|
|
|
empty_array_entry_results_need_no_escaped_allocation test {
|
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
|
defer diagnostics.deinit(&diagnostic_store)
|
|
module := try compile_owned("main proc() [0]bool { return [] }\n", &diagnostic_store)
|
|
defer ir.deinit(&module)
|
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
|
state := AllocationState{ remaining = 64 }
|
|
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
|
|
outcome := try interpreter.run(allocator, &module, &diagnostic_store)
|
|
defer interpreter.deinit(&outcome)
|
|
try testing.expect(outcome.trap == null)
|
|
try testing.expect_equal(ir.Type.array, outcome.value.type)
|
|
try testing.expect_equal(usize(0), outcome.value.data.elements.len)
|
|
try testing.expect_equal(usize(0), state.live)
|
|
interpreter.deinit(&outcome)
|
|
try testing.expect_equal(usize(0), state.live)
|
|
}
|
|
|
|
fallible_source_handlers_do_not_catch_runtime_allocation_failures test {
|
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
|
defer diagnostics.deinit(&diagnostic_store)
|
|
source ::
|
|
`Error :: enum { out_of_memory }
|
|
`fail proc() int ! Error { return .out_of_memory }
|
|
`main proc() int { return fail() catch 42 }
|
|
module := try compile_owned(source, &diagnostic_store)
|
|
defer ir.deinit(&module)
|
|
try testing.expect_equal(usize(0), diagnostics.count(&diagnostic_store))
|
|
succeeded := false
|
|
failures := usize(0)
|
|
for (usize(0)..usize(128)) |limit| {
|
|
state := AllocationState{ remaining = limit }
|
|
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
|
|
outcome := interpreter.run(allocator, &module, &diagnostic_store) catch |error| {
|
|
try testing.expect(error == .out_of_memory)
|
|
failures += 1
|
|
try testing.expect_equal(usize(0), state.live)
|
|
try testing.expect_equal(usize(0), diagnostics.count(&diagnostic_store))
|
|
continue
|
|
}
|
|
defer interpreter.deinit(&outcome)
|
|
try testing.expect(outcome.trap == null)
|
|
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
|
interpreter.deinit(&outcome)
|
|
try testing.expect_equal(usize(0), state.live)
|
|
succeeded = true
|
|
break
|
|
}
|
|
try testing.expect(succeeded and failures > 0)
|
|
}
|