feat: execute local assignments and while loops

This commit is contained in:
2026-10-10 00:17:12 +02:00
parent 021cbecac8
commit b35a68dff8
16 changed files with 1440 additions and 170 deletions
+223
View File
@@ -431,6 +431,12 @@ releases_temporaries_and_partially_owned_modules_on_allocation_failure test {
`}
`main proc() string {
` computed float :: factor * 2
` remaining := 2
` while remaining > 0 {
` pass :: identity(text)
` identity(pass)
` remaining = remaining - 1
` }
` if computed > 0 {
` saved :: identity(text)
` if computed < 10 {
@@ -654,3 +660,220 @@ nested_locals_are_predeclared_before_any_arm_executes test {
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
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)
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)
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)
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)
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 [5][]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",
]
codes [5]diagnostics.Code :: [.read_before_initialization, .write_before_initialization, .read_before_initialization, .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)
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)
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)
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 [3][]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",
]
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)
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 == 1) .missing_return else .integer_overflow
try expect_trap(outcome, &diagnostic_store, code)
}
}
}
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 {\n local :: n\n n = local + 1\n }\n return n\n}\n",
"main proc() int {\n n := 0\n while n < 10000 {\n local :: n\n n = local + 1\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)
try testing.expect(outcome.trap == null)
try testing.expect_equal(expected[index], outcome.value.data.integer)
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)
}
}