diff --git a/tests/move/test_move_01.zc b/tests/move/test_move_01.zc new file mode 100644 index 00000000..4a6de01e --- /dev/null +++ b/tests/move/test_move_01.zc @@ -0,0 +1,14 @@ +// EXPECT: FAIL + +struct M { val: int } + +fn consume(m: M) { + println "{m.val}" +} + +fn main() { + let m = M { val: 10 } + consume(m) + consume(m) // Use-after-move +} + diff --git a/tests/move/test_move_02.zc b/tests/move/test_move_02.zc new file mode 100644 index 00000000..4b8ac243 --- /dev/null +++ b/tests/move/test_move_02.zc @@ -0,0 +1,10 @@ +// EXPECT: FAIL + +struct M { val: int } + +fn main() { + let m = M { val: 1 } + let a = m + let b = m // Use-after-move + println "{a.val} {b.val}" +} diff --git a/tests/move/test_move_03.zc b/tests/move/test_move_03.zc new file mode 100644 index 00000000..47538360 --- /dev/null +++ b/tests/move/test_move_03.zc @@ -0,0 +1,16 @@ +// EXPECT: PASS + +struct M { val: int } + +fn consume(m: M) { + println "{m.val}" +} + +fn main() { + let m = M { val: 10 } + if 1 == 1 { + consume(m) + } else { + consume(m) + } +} diff --git a/tests/move/test_move_04.zc b/tests/move/test_move_04.zc new file mode 100644 index 00000000..4cc6378c --- /dev/null +++ b/tests/move/test_move_04.zc @@ -0,0 +1,16 @@ +// EXPECT: PASS + +struct M { val: int } + +fn consume(m: M) { + println "{m.val}" +} + +fn main() { + let m = M { val: 10 } + if false { + consume(m) + } + consume(m) +} + diff --git a/tests/move/test_move_05.zc b/tests/move/test_move_05.zc new file mode 100644 index 00000000..44b19eb5 --- /dev/null +++ b/tests/move/test_move_05.zc @@ -0,0 +1,16 @@ +// EXPECT: PASS + +struct M { val: int } + +fn consume(m: M) { + println "{m.val}" +} + +fn main() { + let m = M { val: 10 } + while false { + consume(m) + } + consume(m) +} + diff --git a/tests/move/test_move_06.zc b/tests/move/test_move_06.zc new file mode 100644 index 00000000..0e780b8e --- /dev/null +++ b/tests/move/test_move_06.zc @@ -0,0 +1,16 @@ +// EXPECT: PASS + +struct M { val: int } + +fn takes_and_true(m: M) -> bool { + return true +} + +fn main() { + let m = M { val: 10 } + if false && takes_and_true(m) { + println "never" + } + takes_and_true(m) +} + diff --git a/tests/move/test_move_07.zc b/tests/move/test_move_07.zc new file mode 100644 index 00000000..71fefd60 --- /dev/null +++ b/tests/move/test_move_07.zc @@ -0,0 +1,16 @@ +// EXPECT: PASS + +struct M { val: int } + +fn consume(m: M) { + println "{m.val}" +} + +fn main() { + let m = M { val: 10 } + match 1 { + 0 => { consume(m) } + _ => { consume(m) } + } +} + diff --git a/tests/move/test_move_08.zc b/tests/move/test_move_08.zc new file mode 100644 index 00000000..7df965e1 --- /dev/null +++ b/tests/move/test_move_08.zc @@ -0,0 +1,15 @@ +// EXPECT: PASS + +struct M { val: int } + +impl Copy for M {} + +fn consume(m: M) { + println "{m.val}" +} + +fn main() { + let m = M { val: 10 } + consume(m) + consume(m) +} diff --git a/tests/move/test_move_09.zc b/tests/move/test_move_09.zc new file mode 100644 index 00000000..6059f362 --- /dev/null +++ b/tests/move/test_move_09.zc @@ -0,0 +1,13 @@ +// EXPECT: PASS + +struct M { val: int } + +fn borrow(m: M*) { + println "borrowed" +} + +fn main() { + let m = M { val: 10 } + borrow(&m) + borrow(&m) +} diff --git a/tests/move/test_move_10.zc b/tests/move/test_move_10.zc new file mode 100644 index 00000000..d83f7bd1 --- /dev/null +++ b/tests/move/test_move_10.zc @@ -0,0 +1,15 @@ +// EXPECT: FAIL + +struct M { val: int } + +fn consume(m: M) { + println "consume" +} + +fn main() { + let m = M { val: 10 } + consume(m) + let x = m.val + println "{x}" +} + diff --git a/tests/move/test_move_11.zc b/tests/move/test_move_11.zc new file mode 100644 index 00000000..55e14536 --- /dev/null +++ b/tests/move/test_move_11.zc @@ -0,0 +1,18 @@ +// EXPECT: FAIL + +struct M { val: int } + +fn consume(m: M) { + println "consume" +} + +fn borrow(m: M*) { + println "borrow" +} + +fn main() { + let m = M { val: 10 } + consume(m) + borrow(&m) +} + diff --git a/tests/move/test_move_12.zc b/tests/move/test_move_12.zc new file mode 100644 index 00000000..04adb560 --- /dev/null +++ b/tests/move/test_move_12.zc @@ -0,0 +1,14 @@ +// EXPECT: FAIL + +struct M { val: int } + +fn consume(m: M) { + println "consume" +} + +fn main() { + let m = M { val: 10 } + consume(m) + println "{m.val}" +} + diff --git a/tests/move/test_move_13.zc b/tests/move/test_move_13.zc new file mode 100644 index 00000000..ee515a6f --- /dev/null +++ b/tests/move/test_move_13.zc @@ -0,0 +1,19 @@ +// EXPECT: FAIL + +struct M { val: int } + +fn consume(m: M) { + println "consume" +} + +fn main(argc: int, argv: char**) { + let m = M { val: 10 } + consume(m) + + if argc == 1 { + m = M { val: 20 } + } + + consume(m) +} + diff --git a/tests/move/test_move_14.zc b/tests/move/test_move_14.zc new file mode 100644 index 00000000..0978063c --- /dev/null +++ b/tests/move/test_move_14.zc @@ -0,0 +1,17 @@ +// EXPECT: PASS + +struct M { val: int } + +fn consume(m: M) { + println "consume" +} + +fn main(argc: int, argv: char**) { + let m = M { val: 10 } + + if argc == 1 { + consume(m) + } else { + consume(m) + } +} diff --git a/tests/move/test_move_15.zc b/tests/move/test_move_15.zc new file mode 100644 index 00000000..951c43bc --- /dev/null +++ b/tests/move/test_move_15.zc @@ -0,0 +1,17 @@ +// EXPECT: PASS + +struct M { val: int } + +fn consume(m: M) { + println "consume" +} + +fn main() { + let m = M { val: 10 } + + match 1 { + 0 => { consume(m) } + _ => { consume(m) } + } +} + diff --git a/tests/move/test_move_16.zc b/tests/move/test_move_16.zc new file mode 100644 index 00000000..251a3eb2 --- /dev/null +++ b/tests/move/test_move_16.zc @@ -0,0 +1,17 @@ +// EXPECT: PASS + +struct M { val: int } + +fn consume_and_true(m: M) -> bool { + println "consume" + return true +} + +fn main() { + let m = M { val: 10 } + if false && consume_and_true(m) { + println "never" + } + consume_and_true(m) +} + diff --git a/tests/move/test_move_17.zc b/tests/move/test_move_17.zc new file mode 100644 index 00000000..9be89922 --- /dev/null +++ b/tests/move/test_move_17.zc @@ -0,0 +1,18 @@ +// EXPECT: PASS + +struct M { val: int } + +fn consume(m: M) { + println "consume" +} + +fn main() { + let m = M { val: 10 } + + while false { + consume(m) + } + + consume(m) +} + diff --git a/tests/move/test_move_18.zc b/tests/move/test_move_18.zc new file mode 100644 index 00000000..efdd00b3 --- /dev/null +++ b/tests/move/test_move_18.zc @@ -0,0 +1,18 @@ +// EXPECT: PASS + +struct M { val: int } + +fn consume(m: M) { + println "consume" +} + +fn main() { + let m = M { val: 10 } + + if false { + consume(m) + } + + consume(m) +} + diff --git a/tests/move/test_move_19.zc b/tests/move/test_move_19.zc new file mode 100644 index 00000000..bd4b3c1e --- /dev/null +++ b/tests/move/test_move_19.zc @@ -0,0 +1,17 @@ +// EXPECT: FAIL + +struct M { val: int } + +fn consume(m: M) { + println "consume" +} + +test "move in test block" { + let m = M { val: 10 } + consume(m) + let x = m.val + assert(x == 10, "must fail") +} + +fn main() {} + diff --git a/tests/move/test_move_20.zc b/tests/move/test_move_20.zc new file mode 100644 index 00000000..c37494c1 --- /dev/null +++ b/tests/move/test_move_20.zc @@ -0,0 +1,15 @@ +// EXPECT: FAIL + +struct M { val: int } + +fn bad() -> M { + let m = M { val: 10 } + let n = m + return m +} + +fn main() { + let x = bad() + println "{x.val}" +} + diff --git a/tests/move/test_move_21.zc b/tests/move/test_move_21.zc new file mode 100644 index 00000000..8657f8dc --- /dev/null +++ b/tests/move/test_move_21.zc @@ -0,0 +1,22 @@ +// EXPECT: PASS + +// Complex control-flow: exactly one consume per path, should be valid. +struct M { val: int } + +fn consume(m: M) { + println "consume {m.val}" +} + +fn main(argc: int, argv: char**) { + let m = M { val: 10 } + + if argc == 1 { + if argc == 2 { + consume(m) + } else { + consume(m) + } + } else { + consume(m) + } +} diff --git a/tests/move/test_move_22.zc b/tests/move/test_move_22.zc new file mode 100644 index 00000000..26a0587c --- /dev/null +++ b/tests/move/test_move_22.zc @@ -0,0 +1,22 @@ +// EXPECT: PASS + +// if + match nesting, still one move on every concrete path. +struct M { val: int } + +fn consume(m: M) { + println "consume {m.val}" +} + +fn main(argc: int, argv: char**) { + let m = M { val: 20 } + + if argc > 0 { + match argc { + 1 => { consume(m) } + _ => { consume(m) } + } + } else { + consume(m) + } +} + diff --git a/tests/move/test_move_23.zc b/tests/move/test_move_23.zc new file mode 100644 index 00000000..b1de654e --- /dev/null +++ b/tests/move/test_move_23.zc @@ -0,0 +1,22 @@ +// EXPECT: FAIL + +// True semantic should FAIL: if loop doesn't run, m remains moved. +struct M { val: int } + +fn consume(m: M) { + println "consume {m.val}" +} + +fn main(argc: int, argv: char**) { + let m = M { val: 1 } + consume(m) + + let i = 0 + while i < argc { + m = M { val: 2 } + break + } + + consume(m) +} + diff --git a/tests/move/test_move_24.zc b/tests/move/test_move_24.zc new file mode 100644 index 00000000..f6acd252 --- /dev/null +++ b/tests/move/test_move_24.zc @@ -0,0 +1,16 @@ +// EXPECT: FAIL + +// Access through nested member chain after move. +struct Inner { val: int } +struct Outer { inner: Inner } + +fn consume(o: Outer) { + println "consume" +} + +fn main() { + let o = Outer { inner: Inner { val: 33 } } + consume(o) + println "{o.inner.val}" +} + diff --git a/tests/move/test_move_25.zc b/tests/move/test_move_25.zc new file mode 100644 index 00000000..45e63821 --- /dev/null +++ b/tests/move/test_move_25.zc @@ -0,0 +1,21 @@ +// EXPECT: FAIL + +// Borrowing a moved object through address-of should fail. +struct M { val: int } + +fn consume(m: M) { + println "consume" +} + +fn borrow(m: M*) { + println "borrow" +} + +fn main() { + let m = M { val: 40 } + consume(m) + if true { + borrow(&m) + } +} + diff --git a/tests/move/test_move_26.zc b/tests/move/test_move_26.zc new file mode 100644 index 00000000..a188dbc0 --- /dev/null +++ b/tests/move/test_move_26.zc @@ -0,0 +1,20 @@ +// EXPECT: PASS + +// Generic wrapper + mutually exclusive branches. +struct Box { + value: T +} + +fn consume_box(b: Box) { + println "consume {b.value}" +} + +fn main(argc: int, argv: char**) { + let b = Box { value: 55 } + + if argc == 1 { + consume_box(b) + } else { + consume_box(b) + } +} diff --git a/tests/move/test_move_27.zc b/tests/move/test_move_27.zc new file mode 100644 index 00000000..edc4e069 --- /dev/null +++ b/tests/move/test_move_27.zc @@ -0,0 +1,20 @@ +// EXPECT: PASS + +// RHS may be skipped by || short-circuit. +struct M { val: int } + +fn consume_and_true(m: M) -> bool { + println "consume" + return true +} + +fn main(argc: int, argv: char**) { + let m = M { val: 60 } + + if (argc > 0) || consume_and_true(m) { + println "path" + } + + consume_and_true(m) +} + diff --git a/tests/move/test_move_28.zc b/tests/move/test_move_28.zc new file mode 100644 index 00000000..3f07ef3c --- /dev/null +++ b/tests/move/test_move_28.zc @@ -0,0 +1,24 @@ +// EXPECT: PASS + +// match arm contains nested if/else, all paths move exactly once. +struct M { val: int } + +fn consume(m: M) { + println "consume {m.val}" +} + +fn main(argc: int, argv: char**) { + let m = M { val: 70 } + + match argc { + 0 => { consume(m) } + _ => { + if argc > 10 { + consume(m) + } else { + consume(m) + } + } + } +} + diff --git a/tests/move/test_move_29.zc b/tests/move/test_move_29.zc new file mode 100644 index 00000000..4e159296 --- /dev/null +++ b/tests/move/test_move_29.zc @@ -0,0 +1,21 @@ +// EXPECT: FAIL + +// Should fail inside test block too. +struct M { val: int } + +fn consume(m: M) { + println "consume" +} + +test "nested move in test" { + let m = M { val: 80 } + consume(m) + + if true { + let x = m.val + assert(x == 80, "must fail") + } +} + +fn main() {} + diff --git a/tests/move/test_move_30.zc b/tests/move/test_move_30.zc new file mode 100644 index 00000000..42931ae5 --- /dev/null +++ b/tests/move/test_move_30.zc @@ -0,0 +1,20 @@ +// EXPECT: PASS + +// Methods use pointer-style self receiver semantics; repeated calls are valid. +struct Holder { + val: int +} + +impl Holder { + fn take(self) -> int { + return self.val + } +} + +fn main() { + let h = Holder { val: 90 } + let x = h.take() + println "x={x}" + let y = h.take() + println "y={y}" +} diff --git a/tests/move/test_move_31.zc b/tests/move/test_move_31.zc new file mode 100644 index 00000000..c4c8ead7 --- /dev/null +++ b/tests/move/test_move_31.zc @@ -0,0 +1,25 @@ +// EXPECT: PASS + +struct Mover { + val: int +} + +impl Drop for Mover { + fn drop(self) { + println "dropping mover"; + } +} + +fn consume(m: Mover) { + println "m.val = {m.val}"; +} + +fn main(argc: int, argv: char**) { + let m = Mover { val: 10 }; + if argc == 1 { + consume(m); + } + else { + consume(m); + } +} diff --git a/tests/move/test_move_32.zc b/tests/move/test_move_32.zc new file mode 100644 index 00000000..d4676c60 --- /dev/null +++ b/tests/move/test_move_32.zc @@ -0,0 +1,9 @@ +// EXPECT: FAIL + +fn main() { + let original = 100 + let fail = x -> original += x + println "Original before : { original }" + fail(10) + println "Original after : { original }" +} diff --git a/tests/move/test_move_33.zc b/tests/move/test_move_33.zc new file mode 100644 index 00000000..a19b4687 --- /dev/null +++ b/tests/move/test_move_33.zc @@ -0,0 +1,10 @@ +// EXPECT: FAIL + +// Capture-by-value is immutable; mutating captured value must fail. +fn main() { + let original = 100 + let fail_mutate = x -> original += x + + fail_mutate(10) + println "{original}" +} diff --git a/tests/move/test_move_34.zc b/tests/move/test_move_34.zc new file mode 100644 index 00000000..cece4fdf --- /dev/null +++ b/tests/move/test_move_34.zc @@ -0,0 +1,16 @@ +// EXPECT: FAIL + +// Use-after-move inside lambda body must fail. +struct M { val: int } + +fn consume(m: M) { println "{m.val}" } + +fn main() { + let f = fn() { + let m = M { val: 1 } + consume(m) + consume(m) + } + f() +} + diff --git a/tests/move/test_move_35.zc b/tests/move/test_move_35.zc new file mode 100644 index 00000000..c57528bf --- /dev/null +++ b/tests/move/test_move_35.zc @@ -0,0 +1,15 @@ +// EXPECT: FAIL + +// Capturing an already moved outer value must fail. +struct M { val: int } + +fn consume(m: M) { println "{m.val}" } + +fn main() { + let m = M { val: 10 } + consume(m) + + let f = x -> x + m.val + println "{f(1)}" +} + diff --git a/tests/move/test_move_36.zc b/tests/move/test_move_36.zc new file mode 100644 index 00000000..06c65b43 --- /dev/null +++ b/tests/move/test_move_36.zc @@ -0,0 +1,14 @@ +// EXPECT: FAIL + +struct M { val: int } + +fn consume(m: M) { println "{m.val}" } + +fn main() { + let take = fn(x: M) { consume(x) } + let m = M { val: 5 } + + take(m) + consume(m) +} + diff --git a/tests/move/test_move_37.zc b/tests/move/test_move_37.zc new file mode 100644 index 00000000..1c184322 --- /dev/null +++ b/tests/move/test_move_37.zc @@ -0,0 +1,18 @@ +// EXPECT: PASS + +// Borrowing via pointer parameter in lambda must not move the value. +struct M { val: int } + +fn consume(m: M) { println "{m.val}" } + +fn main() { + let borrow = fn(ptr: M*) { + let ok = ptr != 0 + assert(ok, "ptr must be valid") + } + + let m = M { val: 7 } + borrow(&m) + borrow(&m) + consume(m) +} diff --git a/tests/move/test_move_38.zc b/tests/move/test_move_38.zc new file mode 100644 index 00000000..bedecb8d --- /dev/null +++ b/tests/move/test_move_38.zc @@ -0,0 +1,17 @@ +// EXPECT: FAIL + +// Implicit value-capture of non-Copy should move 'm'. +struct M { val: int } + +fn consume(m: M) { println "{m.val}" } + +fn main() { + let m = M { val: 12 } + let get_plus = x -> m.val + x + + // This should fail: 'm' was moved by capture. + consume(m) + + let y = get_plus(5) + assert(y == 17, "unreachable on correct move semantics") +} diff --git a/tests/move/test_move_39.zc b/tests/move/test_move_39.zc new file mode 100644 index 00000000..ab8dbab2 --- /dev/null +++ b/tests/move/test_move_39.zc @@ -0,0 +1,13 @@ +// EXPECT: PASS + +// Copy types passed into lambda by value can be reused. +struct MC { val: int } +impl Copy for MC {} + +fn main() { + let take = fn(x: MC) { println "{x.val}" } + let m = MC { val: 42 } + + take(m) + take(m) +} diff --git a/tests/move/test_move_40.zc b/tests/move/test_move_40.zc new file mode 100644 index 00000000..339ff011 --- /dev/null +++ b/tests/move/test_move_40.zc @@ -0,0 +1,17 @@ +// EXPECT: PASS + +import "std/mem.zc" + +struct MyBox { val: int; } + +impl Clone for MyBox { + fn clone(self) -> MyBox { + return MyBox{val: self.val}; + } +} + +fn main() { + let b1 = MyBox{val: 42}; + let b2 = b1.clone(); // Explicit copy + println "{b2.val}" +} diff --git a/tests/scripts/run_move_tests.sh b/tests/scripts/run_move_tests.sh new file mode 100755 index 00000000..ca1f6ad9 --- /dev/null +++ b/tests/scripts/run_move_tests.sh @@ -0,0 +1,70 @@ +#!/bin/bash + +# Move semantics test runner +ZC="./zc" +if [ ! -f "$ZC" ]; then + ZC="./build/zc" +fi + +TEST_DIR="tests/move" +PASSED=0 +FAILED=0 +FAILED_TESTS="" + +if [ ! -f "$ZC" ]; then + echo "Error: zc binary not found. Please build it first." + exit 1 +fi + +if [ ! -d "$TEST_DIR" ]; then + echo "Error: $TEST_DIR does not exist." + exit 1 +fi + +echo "** Running Move Semantics Tests **" + +while IFS= read -r test_file; do + [ -e "$test_file" ] || continue + + echo -n "Testing $test_file... " + + output=$($ZC run "$test_file" 2>&1) + exit_code=$? + + if grep -q "^// EXPECT: FAIL" "$test_file"; then + if [ $exit_code -ne 0 ]; then + echo "PASS (Expected Failure)" + PASSED=$((PASSED + 1)) + else + echo "FAIL (Unexpected Success)" + FAILED=$((FAILED + 1)) + FAILED_TESTS="$FAILED_TESTS\n- $test_file (Unexpected Success)" + fi + else + if [ $exit_code -eq 0 ]; then + echo "PASS" + PASSED=$((PASSED + 1)) + else + echo "FAIL" + FAILED=$((FAILED + 1)) + err_line=$(echo "$output" | grep -m1 -E "error:|C compilation failed|panic" || true) + FAILED_TESTS="$FAILED_TESTS\n- $test_file ${err_line:+($err_line)}" + fi + fi +done < <(find "$TEST_DIR" -name "*.zc" -not -name "_*.zc" | sort) + +echo "----------------------------------------" +echo "Summary:" +echo "-> Passed: $PASSED" +echo "-> Failed: $FAILED" +echo "----------------------------------------" + +rm -f a.out out.c + +if [ $FAILED -ne 0 ]; then + echo -e "Failed tests:$FAILED_TESTS" + exit 1 +fi + +echo "All move tests passed!" +exit 0