diff --git a/py2v_transpiler/tests/input/transpile/test_global_nonlocal.v b/py2v_transpiler/tests/input/transpile/test_global_nonlocal.v new file mode 100644 index 00000000..6ba9c85d --- /dev/null +++ b/py2v_transpiler/tests/input/transpile/test_global_nonlocal.v @@ -0,0 +1,179 @@ +module main + +// @line: test_global_nonlocal.py:1:0 +pub fn test_global_variable() { + mut counter := 0 +// @line: test_global_nonlocal.py:4:4 + mut increment := fn () int { + //##LLM@@ Python 'global' or 'nonlocal' scope modification detected. V heavily discourages global state and has strict mutability rules for closures. Please refactor state management, possibly by passing mutable parameters (mut) explicitly. + // nonlocal counter + counter += 1 + return counter + } + println('${increment()}') + println('${increment()}') + println('${increment()}') +} +// @line: test_global_nonlocal.py:13:0 +pub fn test_global_in_multiple_functions() { + mut total := 100 +// @line: test_global_nonlocal.py:16:4 + mut add := fn (x int) []Any { + //##LLM@@ Python 'global' or 'nonlocal' scope modification detected. V heavily discourages global state and has strict mutability rules for closures. Please refactor state management, possibly by passing mutable parameters (mut) explicitly. + // nonlocal total + total += x + } +// @line: test_global_nonlocal.py:20:4 + mut subtract := fn (x int) { + //##LLM@@ Python 'global' or 'nonlocal' scope modification detected. V heavily discourages global state and has strict mutability rules for closures. Please refactor state management, possibly by passing mutable parameters (mut) explicitly. + // nonlocal total + total -= x + } +// @line: test_global_nonlocal.py:24:4 + mut get_total := fn [total] () int { + return total + } + add(50) + println('${get_total()}') + subtract(30) + println('${get_total()}') +} +// @line: test_global_nonlocal.py:32:0 +pub fn test_nested_function() { +// @line: test_global_nonlocal.py:33:4 + mut outer := fn (x int) Any { +// @line: test_global_nonlocal.py:34:8 + mut inner := fn [x] (y int) int { + return x + y + } + return inner + } + add_5 := outer(5) + println('${add_5(10)}') + add_10 := outer(10) + println('${add_10(20)}') +} +// @line: test_global_nonlocal.py:44:0 +pub fn test_closure_with_state() { +// @line: test_global_nonlocal.py:45:4 + mut make_accumulator := fn () Any { + mut total := 0 +// @line: test_global_nonlocal.py:48:8 + mut accumulate := fn (value int) int { + //##LLM@@ Python 'global' or 'nonlocal' scope modification detected. V heavily discourages global state and has strict mutability rules for closures. Please refactor state management, possibly by passing mutable parameters (mut) explicitly. + // nonlocal total + total += value + return total + } + return accumulate + } + acc := make_accumulator() + println('${acc(5)}') + println('${acc(10)}') + println('${acc(15)}') +} +// @line: test_global_nonlocal.py:60:0 +pub fn test_closure_in_loop() { + mut funcs := []Any{} + for i in 0..5 { +// @line: test_global_nonlocal.py:63:8 + mut func := fn [i] (x Any) Any { + return x + } + (funcs as string).append(func) + } + for f in funcs { + println('${f()}') + } +} +// @line: test_global_nonlocal.py:70:0 +pub fn test_closure_proper_capture() { + mut funcs := []fn (...Any) Any{} + for i in 0..5 { +// @line: test_global_nonlocal.py:73:8 + mut func := fn [i] () Any { + nonlocal_i := i + return nonlocal_i + } + funcs << func + } + println('Closure in loop (all return last value):') +} +// @line: test_global_nonlocal.py:84:0 +pub fn test_multiple_closures() { +// @line: test_global_nonlocal.py:85:4 + mut make_counters := fn () Any { + mut count_a := 0 + mut count_b := 0 +// @line: test_global_nonlocal.py:89:8 + mut increment_a := fn () int { + //##LLM@@ Python 'global' or 'nonlocal' scope modification detected. V heavily discourages global state and has strict mutability rules for closures. Please refactor state management, possibly by passing mutable parameters (mut) explicitly. + // nonlocal count_a + count_a += 1 + return count_a + } +// @line: test_global_nonlocal.py:94:8 + mut increment_b := fn () int { + //##LLM@@ Python 'global' or 'nonlocal' scope modification detected. V heavily discourages global state and has strict mutability rules for closures. Please refactor state management, possibly by passing mutable parameters (mut) explicitly. + // nonlocal count_b + count_b += 1 + return count_b + } +// @line: test_global_nonlocal.py:99:8 + mut get_counts := fn [count_a, count_b] () Any { + return [count_a, count_b] + } + return [increment_a, increment_b, get_counts] + } + py_destruct_0 := make_counters() + inc_a := py_destruct_0[0] + inc_b := py_destruct_0[1] + get := py_destruct_0[2] + println('${inc_a()}') + println('${inc_a()}') + println('${inc_b()}') + println('${inc_a()}') + println('${get()}') +} +// @line: test_global_nonlocal.py:111:0 +pub fn test_closure_with_list() { +// @line: test_global_nonlocal.py:112:4 + mut make_history := fn () Any { + mut history := []Any{} +// @line: test_global_nonlocal.py:115:8 + mut add := fn [history] (item Any) []Any { + //##LLM@@ Python 'global' or 'nonlocal' scope modification detected. V heavily discourages global state and has strict mutability rules for closures. Please refactor state management, possibly by passing mutable parameters (mut) explicitly. + // nonlocal history + history << item + return history + } +// @line: test_global_nonlocal.py:120:8 + mut get_history := fn [history] () Any { + return history.copy() + } + return [add, get_history] + } + py_destruct_1 := make_history() + add := py_destruct_1[0] + get := py_destruct_1[1] + add(1) + add(2) + add(3) + println('${get()}') +} +// @line: test_global_nonlocal.py:131:0 +pub fn test() { + test_global_variable() + test_global_in_multiple_functions() + test_nested_function() + test_closure_with_state() + test_closure_in_loop() + test_multiple_closures() + test_closure_with_list() +} + +fn main() { + // @line: test_global_nonlocal.py:140:0 + // if __name__ == '__main__': + test() +} \ No newline at end of file diff --git a/py2v_transpiler/tests/input/transpile/test_global_nonlocal_helpers.v b/py2v_transpiler/tests/input/transpile/test_global_nonlocal_helpers.v new file mode 100644 index 00000000..a6196b1b --- /dev/null +++ b/py2v_transpiler/tests/input/transpile/test_global_nonlocal_helpers.v @@ -0,0 +1,125 @@ +module main + +pub struct NoneType {} + +pub fn (n NoneType) str() string { + return 'None' +} + +pub struct Interpolation { +pub: + value Any + expression string + conversion string + format_spec string +} + +pub struct Template { +pub: + strings []string + interpolations []Interpolation +} + +pub fn (t Template) values() []Any { + mut res := []Any{cap: t.interpolations.len} + for i in t.interpolations { + res << i.value + } + return res +} + +pub fn (t1 Template) + (t2 Template) Template { + if t1.strings.len == 0 { return t2 } + if t2.strings.len == 0 { return t1 } + mut new_strings := t1.strings[..t1.strings.len - 1].clone() + new_strings << t1.strings.last() + t2.strings[0] + if t2.strings.len > 1 { + new_strings << t2.strings[1..] + } + mut new_interpolations := t1.interpolations.clone() + new_interpolations << t2.interpolations + return Template{ + strings: new_strings + interpolations: new_interpolations + } +} + +pub type Any = Interpolation | NoneType | Template | []Any | []u8 | bool | f64 | i64 | int | map[string]Any | string + +pub enum PyAnnotationFormat { value forwardref string } + +pub fn py_get_type_hints[T]() map[string]string { + mut hints := map[string]string{} + $for field in T.fields { + hints[field.name] = field.typ + } + return hints +} + +pub fn py_get_type_hints_generic(obj Any) map[string]string { + return map[string]string{} +} + +struct PyGeneratorInput { + val Any + is_exc bool + exc_msg string +} +struct PyGenerator[T] { +mut: + out chan T + in_ chan PyGeneratorInput + open bool = true +} + +fn (mut g PyGenerator[T]) next() ?T { + if !g.open { return none } + g.in_ <- PyGeneratorInput{val: 0} // Send dummy value + res := <-g.out + if res == none { g.open = false } + return res +} +fn (mut g PyGenerator[T]) send(val Any) ?T { + if !g.open { panic('StopIteration') } + g.in_ <- PyGeneratorInput{val: val} + res := <-g.out + if res == none { g.open = false } + return res +} +fn (mut g PyGenerator[T]) throw(msg string) ?T { + if !g.open { panic('StopIteration') } + g.in_ <- PyGeneratorInput{is_exc: true, exc_msg: msg} + res := <-g.out + if res == none { g.open = false } + return res +} +fn (mut g PyGenerator[T]) close() { + g.open = false + g.in_.close() + // g.out will be closed by the generator function loop when it detects in_ closed or panic +} +fn py_yield[T](ch_out chan T, ch_in chan PyGeneratorInput, val T) Any { + ch_out <- val + inp := <-ch_in + if inp.is_exc { + panic(inp.exc_msg) + } + return inp.val +} +//##LLM@@ String formatting for bytes is stubbed and might be incorrect. Please implement proper bytes formatting or use V string interpolation. +fn py_bytes_format(fmt []u8, args Any) []u8 { + // Simplistic implementation for b'%s' % b'val' + // Converts bytes to string, formats, and converts back. + // This is not efficient or correct for non-ASCII bytes but works for simple cases. + fmt_str := fmt.bytestr() + // TODO: handle args properly. V's string interpolation/formatting expects distinct args. + // If args is []u8, treat as string. + arg_str := if args is []u8 { args.bytestr() } else { '${args}' } + + // Manual substitution of %s + // V does not have sprintf for runtime strings easily available in core without C interop. + // Simple replace for %s + res := fmt_str.replace('%s', arg_str) + return res.bytes() +} +fn py_range(args ...int) []int { mut res := []int{}; if args.len == 1 { for i in 0..args[0] { res << i } } else if args.len == 2 { for i in args[0]..args[1] { res << i } } else if args.len == 3 { start := args[0]; stop := args[1]; step := args[2]; if step > 0 { for i := start; i < stop; i += step { res << i } } else if step < 0 { for i := start; i > stop; i += step { res << i } } }; return res } diff --git a/py2v_transpiler/tests/input/transpile/test_iterators.v b/py2v_transpiler/tests/input/transpile/test_iterators.v new file mode 100644 index 00000000..6c33a1a1 --- /dev/null +++ b/py2v_transpiler/tests/input/transpile/test_iterators.v @@ -0,0 +1,148 @@ +module main + +import div72.vexc + +// @line: test_iterators.py:14:4 +pub struct Counter { + current int + end int +} + +// @line: test_iterators.py:1:0 +pub fn test_iter_next() { + lst := [10, 20, 30, 40] + mut it := iter(lst) + println('${it.next()}') + println('${it.next()}') + println('${it.next()}') + println('${it.next()}') + println('${it.next()}') +} +// @line: test_iterators.py:13:0 +pub fn test_custom_iterator() { +// @line: test_iterators.py:15:8 + mut new_ := fn (start int, end int) { + mut self := {} + self.current = start + self.end = end + return self + } +// @line: test_iterators.py:19:8 + mut iter := fn () { + return self + } +// @line: test_iterators.py:22:8 + mut next := fn () int { + if (self as builtins.list[string]).current >= self.end { + vexc.raise('StopIteration', '') + } + result := self.current + self.current += 1 + return result + } + counter := new_counter(0, 5) + for num in counter { + println('${num}') + } +} +// @line: test_iterators.py:33:0 +pub fn test_iterator_consumption() { + mut data := [1, 2, 3, 4, 5] + mut it := iter(data) + remaining := []Any(it) + println('Remaining: ${remaining}') +} +// @line: test_iterators.py:41:0 +pub fn test_zip_iterator() { + names := ['Alice', 'Bob', 'Charlie'] + ages := [25, 30, 35] + py_zip_it1_1 := names + py_zip_it2_1 := ages + for py_i_1, py_v1_1 in py_zip_it1_1 { + if py_i_1 >= py_zip_it2_1.len { break } + py_v2_1 := py_zip_it2_1[py_i_1] + name := py_v1_1 + age := py_v2_1 + println('${name} is ${age} years old') + } + scores := [100, 90] + for py_val_140105991246096 in py_zip(names, ages, scores) { + name := py_val_140105991246096[0] + age := py_val_140105991246096[1] + score := py_val_140105991246096[2] + println('${name}: ${age} years, ${score} points') + } +} +// @line: test_iterators.py:53:0 +pub fn test_enumerate_iterator() { + items := ['apple', 'banana', 'cherry'] + for index, item in items { + println('${index}: ${item}') + } + for index, item in items { + println('${index}: ${item}') + } +} +// @line: test_iterators.py:63:0 +pub fn test_reversed_iterator() { + mut data := [1, 2, 3, 4, 5] + for item in py_reversed(data) { + println('${item}') + } + text := 'hello' + for char in py_reversed(text) { + print('${char}') + } + println('') +} +// @line: test_iterators.py:75:0 +pub fn test_sorted_iterator() { + mut data := [3, 1, 4, 1, 5, 9, 2, 6] + for item in py_sorted(data) { + print('${item} ') + } + println('') + for item in py_sorted(data) { + print('${item} ') + } + println('') + words := ['banana', 'pie', 'Washington', 'book'] + for word in py_sorted(words) { + print('${word} ') + } + println('') +} +// @line: test_iterators.py:93:0 +pub fn test_filter_iterator() { + mut nums := [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + for n in nums.filter(fn (x int) bool { return x % 2 == 0 }) { + print('${n} ') + } + println('') +} +// @line: test_iterators.py:100:0 +pub fn test_map_iterator() { + mut nums := [1, 2, 3, 4, 5] + for n in nums.map(fn (x int) int { return x * x }) { + print('${n} ') + } + println('') +} +// @line: test_iterators.py:107:0 +pub fn test() { + test_iter_next() + test_custom_iterator() + test_iterator_consumption() + test_zip_iterator() + test_enumerate_iterator() + test_reversed_iterator() + test_sorted_iterator() + test_filter_iterator() + test_map_iterator() +} + +fn main() { + // @line: test_iterators.py:118:0 + // if __name__ == '__main__': + test() +} \ No newline at end of file diff --git a/py2v_transpiler/tests/input/transpile/test_iterators_helpers.v b/py2v_transpiler/tests/input/transpile/test_iterators_helpers.v new file mode 100644 index 00000000..e1e064a5 --- /dev/null +++ b/py2v_transpiler/tests/input/transpile/test_iterators_helpers.v @@ -0,0 +1,138 @@ +module main + +pub struct NoneType {} + +pub fn (n NoneType) str() string { + return 'None' +} + +pub struct Interpolation { +pub: + value Any + expression string + conversion string + format_spec string +} + +pub struct Template { +pub: + strings []string + interpolations []Interpolation +} + +pub fn (t Template) values() []Any { + mut res := []Any{cap: t.interpolations.len} + for i in t.interpolations { + res << i.value + } + return res +} + +pub fn (t1 Template) + (t2 Template) Template { + if t1.strings.len == 0 { return t2 } + if t2.strings.len == 0 { return t1 } + mut new_strings := t1.strings[..t1.strings.len - 1].clone() + new_strings << t1.strings.last() + t2.strings[0] + if t2.strings.len > 1 { + new_strings << t2.strings[1..] + } + mut new_interpolations := t1.interpolations.clone() + new_interpolations << t2.interpolations + return Template{ + strings: new_strings + interpolations: new_interpolations + } +} + +pub type Any = Interpolation | NoneType | Template | []Any | []u8 | bool | f64 | i64 | int | map[string]Any | string + +pub enum PyAnnotationFormat { value forwardref string } + +pub fn py_get_type_hints[T]() map[string]string { + mut hints := map[string]string{} + $for field in T.fields { + hints[field.name] = field.typ + } + return hints +} + +pub fn py_get_type_hints_generic(obj Any) map[string]string { + return map[string]string{} +} + +struct PyGeneratorInput { + val Any + is_exc bool + exc_msg string +} +struct PyGenerator[T] { +mut: + out chan T + in_ chan PyGeneratorInput + open bool = true +} +struct PyZipItem[T, U] { a T; b U } +struct PyEnumerateItem[T] { index int; value T } + +fn py_sorted[T](a []T) []T { + mut b := a.clone() + b.sort() + return b +} +fn py_reversed[T](a []T) []T { + mut b := a.clone() + b.reverse() + return b +} +fn (mut g PyGenerator[T]) next() ?T { + if !g.open { return none } + g.in_ <- PyGeneratorInput{val: 0} // Send dummy value + res := <-g.out + if res == none { g.open = false } + return res +} +fn (mut g PyGenerator[T]) send(val Any) ?T { + if !g.open { panic('StopIteration') } + g.in_ <- PyGeneratorInput{val: val} + res := <-g.out + if res == none { g.open = false } + return res +} +fn (mut g PyGenerator[T]) throw(msg string) ?T { + if !g.open { panic('StopIteration') } + g.in_ <- PyGeneratorInput{is_exc: true, exc_msg: msg} + res := <-g.out + if res == none { g.open = false } + return res +} +fn (mut g PyGenerator[T]) close() { + g.open = false + g.in_.close() + // g.out will be closed by the generator function loop when it detects in_ closed or panic +} +fn py_yield[T](ch_out chan T, ch_in chan PyGeneratorInput, val T) Any { + ch_out <- val + inp := <-ch_in + if inp.is_exc { + panic(inp.exc_msg) + } + return inp.val +} +//##LLM@@ String formatting for bytes is stubbed and might be incorrect. Please implement proper bytes formatting or use V string interpolation. +fn py_bytes_format(fmt []u8, args Any) []u8 { + // Simplistic implementation for b'%s' % b'val' + // Converts bytes to string, formats, and converts back. + // This is not efficient or correct for non-ASCII bytes but works for simple cases. + fmt_str := fmt.bytestr() + // TODO: handle args properly. V's string interpolation/formatting expects distinct args. + // If args is []u8, treat as string. + arg_str := if args is []u8 { args.bytestr() } else { '${args}' } + + // Manual substitution of %s + // V does not have sprintf for runtime strings easily available in core without C interop. + // Simple replace for %s + res := fmt_str.replace('%s', arg_str) + return res.bytes() +} +fn py_zip[T, U](a []T, b []U) []PyZipItem[T, U] { mut res := []PyZipItem[T, U]{}; limit := if a.len < b.len { a.len } else { b.len }; for i in 0..limit { res << PyZipItem[T, U]{a: a[i], b: b[i]} }; return res } +fn py_enumerate[T](a []T) []PyEnumerateItem[T] { mut res := []PyEnumerateItem[T]{}; for i, x in a { res << PyEnumerateItem[T]{index: i, value: x} }; return res } diff --git a/py2v_transpiler/tests/input/transpile/test_range_type.v b/py2v_transpiler/tests/input/transpile/test_range_type.v new file mode 100644 index 00000000..753980f0 --- /dev/null +++ b/py2v_transpiler/tests/input/transpile/test_range_type.v @@ -0,0 +1,106 @@ +module main + +// @line: test_range_type.py:1:0 +pub fn test_range_basic() { + mut r := py_range(5) + println('range(5): ${[]Any(r)}') + r = py_range(2, 7) + println('range(2, 7): ${[]Any(r)}') + r = py_range(0, 10, 2) + println('range(0, 10, 2): ${[]Any(r)}') +} +// @line: test_range_type.py:11:0 +pub fn test_range_negative() { + mut r := py_range(-5, 0) + println('range(-5, 0): ${[]Any(r)}') + r = py_range(0, -5, -1) + println('range(0, -5, -1): ${[]Any(r)}') + r = py_range(5, -5, -2) + println('range(5, -5, -2): ${[]Any(r)}') +} +// @line: test_range_type.py:21:0 +pub fn test_range_iteration() { + for i in 0..3 { + println('i=${i}') + } +} +// @line: test_range_type.py:25:0 +pub fn test_range_indexing() { + mut r := py_range(10, 20, 2) + println('r[0]: ${r[0]}') + println('r[2]: ${r[2]}') + println('r[-1]: ${r[r.len - 1]}') +} +// @line: test_range_type.py:31:0 +pub fn test_range_slicing() { + mut r := py_range(10) + mut sliced := []Any(r[2..6]) + println('r[2:6]: ${sliced}') + sliced = []Any(py_list_slice(r, none, none, 3)) + println('r[::3]: ${sliced}') +} +// @line: test_range_type.py:39:0 +pub fn test_range_len() { + mut r := py_range(100, 200, 5) + println('len(range(100, 200, 5)): ${r.len}') +} +// @line: test_range_type.py:43:0 +pub fn test_range_membership() { + mut r := py_range(0, 10, 2) + println('4 in r: ${4 in r}') + println('5 in r: ${5 in r}') +} +// @line: test_range_type.py:48:0 +pub fn test_range_conversion() { + mut r := py_range(5) + println('list(r): ${[]Any(r)}') + println('tuple(r): ${[]Any(r)}') + println('set(r): ${map[string]bool(r)}') +} +// @line: test_range_type.py:54:0 +pub fn test_range_with_enumerate() { + for i, val in py_range(10, 20, 2) { + println('i=${i}, val=${val}') + } +} +// @line: test_range_type.py:58:0 +pub fn test_range_nested() { + for i in 0..3 { + for j in 0..3 { + print('(${i}, ${j}) ') + } + println('') + } +} +// @line: test_range_type.py:64:0 +pub fn test_range_large() { + mut r := py_range(1000000) + println('range(1000000) size: ${r.len}') + println('range(1000000)[500000]: ${r[500000]}') +} +// @line: test_range_type.py:70:0 +pub fn test_range_start_stop_step() { + mut r := py_range(10, 2, -2) + println('range(10, 2, -2): ${[]Any(r)}') +} +// @line: test_range_type.py:74:0 +pub fn test() { + test_range_basic() + test_range_negative() + test_range_iteration() + test_range_indexing() + test_range_slicing() + test_range_len() + test_range_membership() + test_range_conversion() + test_range_with_enumerate() + test_range_nested() + test_range_large() + test_range_start_stop_step() +} + +fn main() { + // @line: test_range_type.py:88:0 + // if __name__ == '__main__': + test() +} \ No newline at end of file diff --git a/py2v_transpiler/tests/input/transpile/test_range_type_helpers.v b/py2v_transpiler/tests/input/transpile/test_range_type_helpers.v new file mode 100644 index 00000000..2a2ada0a --- /dev/null +++ b/py2v_transpiler/tests/input/transpile/test_range_type_helpers.v @@ -0,0 +1,154 @@ +module main + +pub struct NoneType {} + +pub fn (n NoneType) str() string { + return 'None' +} + +pub struct Interpolation { +pub: + value Any + expression string + conversion string + format_spec string +} + +pub struct Template { +pub: + strings []string + interpolations []Interpolation +} + +pub fn (t Template) values() []Any { + mut res := []Any{cap: t.interpolations.len} + for i in t.interpolations { + res << i.value + } + return res +} + +pub fn (t1 Template) + (t2 Template) Template { + if t1.strings.len == 0 { return t2 } + if t2.strings.len == 0 { return t1 } + mut new_strings := t1.strings[..t1.strings.len - 1].clone() + new_strings << t1.strings.last() + t2.strings[0] + if t2.strings.len > 1 { + new_strings << t2.strings[1..] + } + mut new_interpolations := t1.interpolations.clone() + new_interpolations << t2.interpolations + return Template{ + strings: new_strings + interpolations: new_interpolations + } +} + +pub type Any = Interpolation | NoneType | Template | []Any | []u8 | bool | f64 | i64 | int | map[string]Any | string + +pub enum PyAnnotationFormat { value forwardref string } + +pub fn py_get_type_hints[T]() map[string]string { + mut hints := map[string]string{} + $for field in T.fields { + hints[field.name] = field.typ + } + return hints +} + +pub fn py_get_type_hints_generic(obj Any) map[string]string { + return map[string]string{} +} + +struct PyGeneratorInput { + val Any + is_exc bool + exc_msg string +} +struct PyGenerator[T] { +mut: + out chan T + in_ chan PyGeneratorInput + open bool = true +} +struct PyEnumerateItem[T] { index int; value T } + +fn (mut g PyGenerator[T]) next() ?T { + if !g.open { return none } + g.in_ <- PyGeneratorInput{val: 0} // Send dummy value + res := <-g.out + if res == none { g.open = false } + return res +} +fn (mut g PyGenerator[T]) send(val Any) ?T { + if !g.open { panic('StopIteration') } + g.in_ <- PyGeneratorInput{val: val} + res := <-g.out + if res == none { g.open = false } + return res +} +fn (mut g PyGenerator[T]) throw(msg string) ?T { + if !g.open { panic('StopIteration') } + g.in_ <- PyGeneratorInput{is_exc: true, exc_msg: msg} + res := <-g.out + if res == none { g.open = false } + return res +} +fn (mut g PyGenerator[T]) close() { + g.open = false + g.in_.close() + // g.out will be closed by the generator function loop when it detects in_ closed or panic +} +fn py_yield[T](ch_out chan T, ch_in chan PyGeneratorInput, val T) Any { + ch_out <- val + inp := <-ch_in + if inp.is_exc { + panic(inp.exc_msg) + } + return inp.val +} +//##LLM@@ String formatting for bytes is stubbed and might be incorrect. Please implement proper bytes formatting or use V string interpolation. +fn py_bytes_format(fmt []u8, args Any) []u8 { + // Simplistic implementation for b'%s' % b'val' + // Converts bytes to string, formats, and converts back. + // This is not efficient or correct for non-ASCII bytes but works for simple cases. + fmt_str := fmt.bytestr() + // TODO: handle args properly. V's string interpolation/formatting expects distinct args. + // If args is []u8, treat as string. + arg_str := if args is []u8 { args.bytestr() } else { '${args}' } + + // Manual substitution of %s + // V does not have sprintf for runtime strings easily available in core without C interop. + // Simple replace for %s + res := fmt_str.replace('%s', arg_str) + return res.bytes() +} +fn py_list_slice[T](l []T, lower ?Any, upper ?Any, step ?Any) []T { + mut lo := 0 + if lower_val := lower { if lower_val is int { lo = lower_val } } + mut up := l.len + if upper_val := upper { if upper_val is int { up = upper_val } } + mut st := 1 + if step_val := step { if step_val is int { st = step_val } } + + if lo < 0 { lo += l.len } + if up < 0 { up += l.len } + if lo < 0 { lo = 0 } + if up > l.len { up = l.len } + + if st == 1 { return l[lo..up].clone() } + + mut res := []T{} + if st > 0 { + for i := lo; i < up; i += st { + if i >= 0 && i < l.len { res << l[i] } + } + } else if st < 0 { + for i := lo; i > up; i += st { + if i >= 0 && i < l.len { res << l[i] } + } + } + return res +} +fn py_enumerate[T](a []T) []PyEnumerateItem[T] { mut res := []PyEnumerateItem[T]{}; for i, x in a { res << PyEnumerateItem[T]{index: i, value: x} }; return res } +fn py_range(args ...int) []int { mut res := []int{}; if args.len == 1 { for i in 0..args[0] { res << i } } else if args.len == 2 { for i in args[0]..args[1] { res << i } } else if args.len == 3 { start := args[0]; stop := args[1]; step := args[2]; if step > 0 { for i := start; i < stop; i += step { res << i } } else if step < 0 { for i := start; i > stop; i += step { res << i } } }; return res } diff --git a/py2v_transpiler/tests/input/transpile/test_slice_ops.v b/py2v_transpiler/tests/input/transpile/test_slice_ops.v new file mode 100644 index 00000000..cccbc6d6 --- /dev/null +++ b/py2v_transpiler/tests/input/transpile/test_slice_ops.v @@ -0,0 +1,133 @@ +module main + +// @line: test_slice_ops.py:1:0 +pub fn test_slice_basic() { + mut lst := []int{cap: 10} + lst << 0 + lst << 1 + lst << 2 + lst << 3 + lst << 4 + lst << 5 + lst << 6 + lst << 7 + lst << 8 + lst << 9 + println('lst[2:5]: ${lst[2..5]}') + println('lst[:4]: ${lst[..4]}') + println('lst[6:]: ${lst[6..]}') + println('lst[:]: ${lst[..]}') +} +// @line: test_slice_ops.py:8:0 +pub fn test_slice_negative() { + mut lst := []int{cap: 10} + lst << 0 + lst << 1 + lst << 2 + lst << 3 + lst << 4 + lst << 5 + lst << 6 + lst << 7 + lst << 8 + lst << 9 + println('lst[-3:]: ${lst[lst.len - 3..]}') + println('lst[:-3]: ${lst[..lst.len - 3]}') + println('lst[-5:-2]: ${lst[lst.len - 5..lst.len - 2]}') +} +// @line: test_slice_ops.py:14:0 +pub fn test_slice_step() { + mut lst := [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + println('lst[::2]: ${py_list_slice(lst, none, none, 2)}') + println('lst[1::2]: ${py_list_slice(lst, 1, none, 2)}') + println('lst[::3]: ${py_list_slice(lst, none, none, 3)}') +} +// @line: test_slice_ops.py:20:0 +pub fn test_slice_reverse() { + mut lst := [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + println('lst[::-1]: ${py_list_slice(lst, none, none, -1)}') + println('lst[::-2]: ${py_list_slice(lst, none, none, -2)}') + println('lst[7:2:-1]: ${py_list_slice(lst, 7, 2, -1)}') +} +// @line: test_slice_ops.py:26:0 +pub fn test_slice_assignment() { + mut lst := [1, 2, 3, 4, 5] + lst.delete_many(1, (3) - (1)) + lst.insert_many(1, [10, 20]) + println('After lst[1:3] = [10, 20]: ${lst}') + lst = [1, 2, 3, 4, 5] + lst.delete_many(1, (3) - (1)) + lst.insert_many(1, [100]) + println('After lst[1:3] = [100]: ${lst}') + lst = [1, 2, 3, 4, 5] + lst.delete_many(1, (3) - (1)) + lst.insert_many(1, [10, 20, 30, 40]) + println('After lst[1:3] = [10, 20, 30, 40]: ${lst}') +} +// @line: test_slice_ops.py:39:0 +pub fn test_slice_delete() { + mut lst := [1, 2, 3, 4, 5, 6, 7] + lst.delete(None) + println('After del lst[2:5]: ${lst}') +} +// @line: test_slice_ops.py:44:0 +pub fn test_slice_step_assignment() { + mut lst := [0, 0, 0, 0, 0] + lst.delete_many(0, (lst.len) - (0)) + lst.insert_many(0, [1, 1, 1]) + println('After lst[::2] = [1, 1, 1]: ${lst}') +} +// @line: test_slice_ops.py:49:0 +pub fn test_slice_string() { + s := 'Hello, World!' + println('s[0:5]: ${s[0..5]}') + println('s[::-1]: ${py_list_slice(s, none, none, -1)}') + println('s[7:-1]: ${s[7..s.len - 1]}') +} +// @line: test_slice_ops.py:55:0 +pub fn test_slice_tuple() { + t := [0, 1, 2, 3, 4, 5] + println('t[1:4]: ${t[1..4]}') + println('t[::-1]: ${py_list_slice(t, none, none, -1)}') +} +// @line: test_slice_ops.py:60:0 +pub fn test_slice_out_of_bounds() { + mut lst := [1, 2, 3, 4, 5] + println('lst[0:100]: ${lst[0..100]}') + println('lst[-100:100]: ${lst[lst.len - 100..100]}') +} +// @line: test_slice_ops.py:65:0 +pub fn test_slice_empty() { + mut lst := [1, 2, 3] + println('lst[2:1]: ${lst[2..1]}') + println('lst[5:10]: ${lst[5..10]}') +} +// @line: test_slice_ops.py:70:0 +pub fn test_slice_copy() { + mut lst := [1, 2, 3, 4, 5] + mut copy := lst[..].clone() + copy[0] = 100 + println('Original: ${lst}') + println('Copy: ${copy}') +} +// @line: test_slice_ops.py:77:0 +pub fn test() { + test_slice_basic() + test_slice_negative() + test_slice_step() + test_slice_reverse() + test_slice_assignment() + test_slice_delete() + test_slice_step_assignment() + test_slice_string() + test_slice_tuple() + test_slice_out_of_bounds() + test_slice_empty() + test_slice_copy() +} + +fn main() { + // @line: test_slice_ops.py:91:0 + // if __name__ == '__main__': + test() +} \ No newline at end of file diff --git a/py2v_transpiler/tests/input/transpile/test_slice_ops_helpers.v b/py2v_transpiler/tests/input/transpile/test_slice_ops_helpers.v new file mode 100644 index 00000000..248f9da5 --- /dev/null +++ b/py2v_transpiler/tests/input/transpile/test_slice_ops_helpers.v @@ -0,0 +1,158 @@ +module main + +pub struct NoneType {} + +pub fn (n NoneType) str() string { + return 'None' +} + +pub struct Interpolation { +pub: + value Any + expression string + conversion string + format_spec string +} + +pub struct Template { +pub: + strings []string + interpolations []Interpolation +} + +pub fn (t Template) values() []Any { + mut res := []Any{cap: t.interpolations.len} + for i in t.interpolations { + res << i.value + } + return res +} + +pub fn (t1 Template) + (t2 Template) Template { + if t1.strings.len == 0 { return t2 } + if t2.strings.len == 0 { return t1 } + mut new_strings := t1.strings[..t1.strings.len - 1].clone() + new_strings << t1.strings.last() + t2.strings[0] + if t2.strings.len > 1 { + new_strings << t2.strings[1..] + } + mut new_interpolations := t1.interpolations.clone() + new_interpolations << t2.interpolations + return Template{ + strings: new_strings + interpolations: new_interpolations + } +} + +pub type Any = Interpolation | NoneType | Template | []Any | []u8 | bool | f64 | i64 | int | map[string]Any | string + +pub enum PyAnnotationFormat { value forwardref string } + +pub fn py_get_type_hints[T]() map[string]string { + mut hints := map[string]string{} + $for field in T.fields { + hints[field.name] = field.typ + } + return hints +} + +pub fn py_get_type_hints_generic(obj Any) map[string]string { + return map[string]string{} +} + +struct PyGeneratorInput { + val Any + is_exc bool + exc_msg string +} +struct PyGenerator[T] { +mut: + out chan T + in_ chan PyGeneratorInput + open bool = true +} + +fn (mut g PyGenerator[T]) next() ?T { + if !g.open { return none } + g.in_ <- PyGeneratorInput{val: 0} // Send dummy value + res := <-g.out + if res == none { g.open = false } + return res +} +fn (mut g PyGenerator[T]) send(val Any) ?T { + if !g.open { panic('StopIteration') } + g.in_ <- PyGeneratorInput{val: val} + res := <-g.out + if res == none { g.open = false } + return res +} +fn (mut g PyGenerator[T]) throw(msg string) ?T { + if !g.open { panic('StopIteration') } + g.in_ <- PyGeneratorInput{is_exc: true, exc_msg: msg} + res := <-g.out + if res == none { g.open = false } + return res +} +fn (mut g PyGenerator[T]) close() { + g.open = false + g.in_.close() + // g.out will be closed by the generator function loop when it detects in_ closed or panic +} +fn py_yield[T](ch_out chan T, ch_in chan PyGeneratorInput, val T) Any { + ch_out <- val + inp := <-ch_in + if inp.is_exc { + panic(inp.exc_msg) + } + return inp.val +} +//##LLM@@ String formatting for bytes is stubbed and might be incorrect. Please implement proper bytes formatting or use V string interpolation. +fn py_bytes_format(fmt []u8, args Any) []u8 { + // Simplistic implementation for b'%s' % b'val' + // Converts bytes to string, formats, and converts back. + // This is not efficient or correct for non-ASCII bytes but works for simple cases. + fmt_str := fmt.bytestr() + // TODO: handle args properly. V's string interpolation/formatting expects distinct args. + // If args is []u8, treat as string. + arg_str := if args is []u8 { args.bytestr() } else { '${args}' } + + // Manual substitution of %s + // V does not have sprintf for runtime strings easily available in core without C interop. + // Simple replace for %s + res := fmt_str.replace('%s', arg_str) + return res.bytes() +} +fn (mut a []T) delete_many[T](start int, count int) { + if count <= 0 { return } + a.delete(start, start + count) +} +fn (mut a []T) insert_many[T](index int, val []T) { + a.insert(index, val) +} +fn py_list_slice[T](l []T, lower ?Any, upper ?Any, step ?Any) []T { + mut lo := 0 + if lower_val := lower { if lower_val is int { lo = lower_val } } + mut up := l.len + if upper_val := upper { if upper_val is int { up = upper_val } } + mut st := 1 + if step_val := step { if step_val is int { st = step_val } } + + if lo < 0 { lo += l.len } + if up < 0 { up += l.len } + if lo < 0 { lo = 0 } + if up > l.len { up = l.len } + + if st == 1 { return l[lo..up].clone() } + + mut res := []T{} + if st > 0 { + for i := lo; i < up; i += st { + if i >= 0 && i < l.len { res << l[i] } + } + } else if st < 0 { + for i := lo; i > up; i += st { + if i >= 0 && i < l.len { res << l[i] } + } + } + return res +} diff --git a/py2v_transpiler/tests/input/transpile/test_tuple_type.v b/py2v_transpiler/tests/input/transpile/test_tuple_type.v new file mode 100644 index 00000000..e60ed05d --- /dev/null +++ b/py2v_transpiler/tests/input/transpile/test_tuple_type.v @@ -0,0 +1,135 @@ +module main + +// @line: test_tuple_type.py:1:0 +pub fn test_tuple_creation() { + mut t1 := [1, 2, 3] + println('Tuple: ${t1}') + mut t2 := [1, 2, 3] + println('Without parens: ${t2}') + mut t3 := []Any([1, 2, 3]) + println('From list: ${t3}') + t4 := [1] + println('Single element: ${t4}') + t5 := [] + println('Empty tuple: ${t5}') +} +// @line: test_tuple_type.py:19:0 +pub fn test_tuple_access() { + mut t := [10, 20, 30, 40, 50] + println('t[0]: ${t[0]}') + println('t[-1]: ${t[t.len - 1]}') + println('t[1:4]: ${t[1..4]}') + println('t[::2]: ${py_list_slice(t, none, none, 2)}') +} +// @line: test_tuple_type.py:26:0 +pub fn test_tuple_unpacking() { + mut t := [1, 2, 3] + py_destruct_0 := t + a := py_destruct_0[0] + b := py_destruct_0[1] + c := py_destruct_0[2] + println('a=${a}, b=${b}, c=${c}') + nested := [1, [2, 3], 4] + py_destruct_1 := nested + x := py_destruct_1[0] + py_destruct_2 := py_destruct_1[1] + y := py_destruct_2[0] + z := py_destruct_2[1] + w := py_destruct_1[2] + println('x=${x}, y=${y}, z=${z}, w=${w}') +} +// @line: test_tuple_type.py:36:0 +pub fn test_tuple_immutable() { + mut t := [1, 2, 3] + println('Tuple is immutable: ${t}') + mut t2 := [[1, 2], [3, 4]] + t2[0].append(3) + println('Mutable inside tuple: ${t2}') +} +// @line: test_tuple_type.py:46:0 +pub fn test_tuple_methods() { + mut t := [1, 2, 3, 2, 4, 2, 5] + println('Count of 2: ${(t as none).count(2)}') + println('Index of 4: ${t.index(4) or { panic('ValueError: substring not found') }}') +} +// @line: test_tuple_type.py:51:0 +pub fn test_tuple_concat() { + mut t1 := [1, 2, 3] + mut t2 := [4, 5, 6] + mut result := t1 + t2 + println('Concat: ${result}') +} +// @line: test_tuple_type.py:57:0 +pub fn test_tuple_repeat() { + mut t := [1, 2] + mut result := t * 3 + println('Repeat: ${result}') +} +// @line: test_tuple_type.py:62:0 +pub fn test_tuple_membership() { + mut t := [1, 2, 3, 4, 5] + println('3 in tuple: ${3 in t}') + println('10 not in tuple: ${10 !in t}') +} +// @line: test_tuple_type.py:67:0 +pub fn test_tuple_comparison() { + mut t1 := [1, 2, 3] + mut t2 := [1, 2, 4] + mut t3 := [1, 2, 3] + println('t1 == t3: ${t1 == t3}') + println('t1 < t2: ${t1 < t2}') + println('t1 > t2: ${t1 > t2}') +} +// @line: test_tuple_type.py:76:0 +pub fn test_named_tuple() { + point := collections.namedtuple('Point', ['x', 'y']) + p := point(3, 4) + println('Point: ${p}') + println('p.x: ${p.x}') + println('p.y: ${p.y}') + println('p[0]: ${p[0]}') + py_destruct_3 := p + x := py_destruct_3[0] + y := py_destruct_3[1] + println('Unpacked: x=${x}, y=${y}') +} +// @line: test_tuple_type.py:91:0 +pub fn test_tuple_as_dict_key() { + d := {[0, 0]: Any('origin'), [1, 1]: Any('diagonal')} + println('Dict with tuple keys: ${d}') + println('d[(0, 0)]: ${d[[0, 0]]}') +} +// @line: test_tuple_type.py:97:0 +pub fn test_tuple_function_return() { +// @line: test_tuple_type.py:98:4 + mut get_min_max := fn (nums Any) Any { + return [py_min(nums), py_max(nums)] + } + mut result := get_min_max([3, 1, 4, 1, 5, 9]) + println('Min and max: ${result}') + py_destruct_4 := get_min_max([3, 1, 4, 1, 5, 9]) + min_val := py_destruct_4[0] + max_val := py_destruct_4[1] + println('Unpacked: min=${min_val}, max=${max_val}') +} +// @line: test_tuple_type.py:107:0 +pub fn test() { + test_tuple_creation() + test_tuple_access() + test_tuple_unpacking() + test_tuple_immutable() + test_tuple_methods() + test_tuple_concat() + test_tuple_repeat() + test_tuple_membership() + test_tuple_comparison() + test_named_tuple() + test_tuple_as_dict_key() + test_tuple_function_return() +} + +fn main() { + // @line: test_tuple_type.py:121:0 + // if __name__ == '__main__': + test() +} \ No newline at end of file diff --git a/py2v_transpiler/tests/input/transpile/test_tuple_type_helpers.v b/py2v_transpiler/tests/input/transpile/test_tuple_type_helpers.v new file mode 100644 index 00000000..aeea394c --- /dev/null +++ b/py2v_transpiler/tests/input/transpile/test_tuple_type_helpers.v @@ -0,0 +1,160 @@ +module main + +pub struct NoneType {} + +pub fn (n NoneType) str() string { + return 'None' +} + +pub struct Interpolation { +pub: + value Any + expression string + conversion string + format_spec string +} + +pub struct Template { +pub: + strings []string + interpolations []Interpolation +} + +pub fn (t Template) values() []Any { + mut res := []Any{cap: t.interpolations.len} + for i in t.interpolations { + res << i.value + } + return res +} + +pub fn (t1 Template) + (t2 Template) Template { + if t1.strings.len == 0 { return t2 } + if t2.strings.len == 0 { return t1 } + mut new_strings := t1.strings[..t1.strings.len - 1].clone() + new_strings << t1.strings.last() + t2.strings[0] + if t2.strings.len > 1 { + new_strings << t2.strings[1..] + } + mut new_interpolations := t1.interpolations.clone() + new_interpolations << t2.interpolations + return Template{ + strings: new_strings + interpolations: new_interpolations + } +} + +pub type Any = Interpolation | NoneType | Template | []Any | []u8 | bool | f64 | i64 | int | map[string]Any | string + +pub enum PyAnnotationFormat { value forwardref string } + +pub fn py_get_type_hints[T]() map[string]string { + mut hints := map[string]string{} + $for field in T.fields { + hints[field.name] = field.typ + } + return hints +} + +pub fn py_get_type_hints_generic(obj Any) map[string]string { + return map[string]string{} +} + +struct PyGeneratorInput { + val Any + is_exc bool + exc_msg string +} +struct PyGenerator[T] { +mut: + out chan T + in_ chan PyGeneratorInput + open bool = true +} + +fn py_counter[T](a []T) map[T]int { + mut m := map[T]int{} + for x in a { + m[x]++ + } + return m +} +fn (mut g PyGenerator[T]) next() ?T { + if !g.open { return none } + g.in_ <- PyGeneratorInput{val: 0} // Send dummy value + res := <-g.out + if res == none { g.open = false } + return res +} +fn (mut g PyGenerator[T]) send(val Any) ?T { + if !g.open { panic('StopIteration') } + g.in_ <- PyGeneratorInput{val: val} + res := <-g.out + if res == none { g.open = false } + return res +} +fn (mut g PyGenerator[T]) throw(msg string) ?T { + if !g.open { panic('StopIteration') } + g.in_ <- PyGeneratorInput{is_exc: true, exc_msg: msg} + res := <-g.out + if res == none { g.open = false } + return res +} +fn (mut g PyGenerator[T]) close() { + g.open = false + g.in_.close() + // g.out will be closed by the generator function loop when it detects in_ closed or panic +} +fn py_yield[T](ch_out chan T, ch_in chan PyGeneratorInput, val T) Any { + ch_out <- val + inp := <-ch_in + if inp.is_exc { + panic(inp.exc_msg) + } + return inp.val +} +//##LLM@@ String formatting for bytes is stubbed and might be incorrect. Please implement proper bytes formatting or use V string interpolation. +fn py_bytes_format(fmt []u8, args Any) []u8 { + // Simplistic implementation for b'%s' % b'val' + // Converts bytes to string, formats, and converts back. + // This is not efficient or correct for non-ASCII bytes but works for simple cases. + fmt_str := fmt.bytestr() + // TODO: handle args properly. V's string interpolation/formatting expects distinct args. + // If args is []u8, treat as string. + arg_str := if args is []u8 { args.bytestr() } else { '${args}' } + + // Manual substitution of %s + // V does not have sprintf for runtime strings easily available in core without C interop. + // Simple replace for %s + res := fmt_str.replace('%s', arg_str) + return res.bytes() +} +fn py_list_slice[T](l []T, lower ?Any, upper ?Any, step ?Any) []T { + mut lo := 0 + if lower_val := lower { if lower_val is int { lo = lower_val } } + mut up := l.len + if upper_val := upper { if upper_val is int { up = upper_val } } + mut st := 1 + if step_val := step { if step_val is int { st = step_val } } + + if lo < 0 { lo += l.len } + if up < 0 { up += l.len } + if lo < 0 { lo = 0 } + if up > l.len { up = l.len } + + if st == 1 { return l[lo..up].clone() } + + mut res := []T{} + if st > 0 { + for i := lo; i < up; i += st { + if i >= 0 && i < l.len { res << l[i] } + } + } else if st < 0 { + for i := lo; i > up; i += st { + if i >= 0 && i < l.len { res << l[i] } + } + } + return res +} +fn py_min[T](a []T) T { if a.len == 0 { panic('min() arg is an empty sequence') }; mut m := a[0]; for x in a { if x < m { m = x } }; return m } +fn py_max[T](a []T) T { if a.len == 0 { panic('max() arg is an empty sequence') }; mut m := a[0]; for x in a { if x > m { m = x } }; return m } diff --git a/transpilation_issues.md b/transpilation_issues.md new file mode 100644 index 00000000..35028e63 --- /dev/null +++ b/transpilation_issues.md @@ -0,0 +1,40 @@ +# Transpilation Issues Report + +This report documents issues found during the transpilation of Python test files to V using `py2v`. + +## 1. test_slice_ops.py + +### Issues Found: +- **Invalid Delete Call**: In `test_slice_delete`, the Python `del lst[2:5]` was transpiled to `lst.delete(None)`. `None` is not a valid V value (should be `none`), and V's `delete` method requires an index or a range, not `None`. +- **Negative Indexing in Slices**: In `test_slice_out_of_bounds`, `lst[-100:100]` became `lst[lst.len - 100..100]`. If `lst.len < 100`, the start index becomes negative, which causes a runtime panic in V. Python handles this by clamping the index to 0. +- **Type Mismatch in Slicing**: `py_list_slice` is used for both lists and strings. While it might be a generic helper, strings in V are not strictly compatible with `[]T` helpers without proper overloading or casting. + +## 2. test_tuple_type.py + +### Issues Found: +- **Incorrect Casting**: In `test_tuple_methods`, the code `(t as none).count(2)` is generated. `none` is a keyword in V, not a type, making this an invalid cast. +- **Invalid Array Initialization**: `mut t3 := []Any([1, 2, 3])` is generated for `tuple([1, 2, 3])`. V does not support this syntax for creating an array from another array. +- **Method Mapping Error**: Python's `.append()` was transpiled to `.append()` in V (e.g., `t2[0].append(3)`), but V uses the `<<` operator for appending to arrays. +- **Tuple to Array Mapping**: Python tuples are transpiled to V arrays (`[]T`), which are mutable in V. This loses the immutability property of Python tuples. + +## 3. test_range_type.py + +### Issues Found: +- **Invalid Casting to Set**: For `set(r)`, the transpiler generated `map[string]bool(r)`. This is invalid because `r` is a `[]int`, and V does not support direct casting from an array to a map. +- **Invalid Array Cast**: `[]Any(r)` is used to convert `[]int` to `[]Any`. V requires an explicit map call or loop to convert element types in an array. + +## 4. test_global_nonlocal.py + +### Issues Found: +- **Missing Closure Captures**: In `test_global_variable`, the anonymous function `increment` accesses and modifies `counter` from the outer scope without capturing it. V requires explicit capture: `fn [mut counter] () { ... }`. +- **Incorrect Type Inference/Cast**: In `test_closure_in_loop`, the generated code includes `(funcs as string).append(func)`. `funcs` was correctly identified as an array (`[]Any`), but then incorrectly cast to `string` to call an `append` method (which doesn't exist on strings in that form either). +- **Mutating Captures**: V's closure capture syntax `fn [x] ()` captures by value. Modifying a captured variable inside a closure (as in `nonlocal` usage) requires capturing by reference or using a mutable pointer, which the transpiler does not currently handle correctly. + +## 5. test_iterators.py + +### Issues Found: +- **Invalid Struct Initialization**: In `test_custom_iterator`, `mut self := {}` is generated. V requires a type name for struct initialization. +- **Broken Constructor Logic**: The Python `__init__` was transpiled to a local function named `new_`, but the call site used `new_counter`, leading to a "function not found" error. +- **Invalid Iterator Consumption**: `remaining := []Any(it)` was generated for `list(it)`. V does not support casting an iterator directly to an array. +- **Type Inference Failure**: In `next`, the code `(self as builtins.list[string]).current` was generated. `builtins.list[string]` is not a valid V type. +- **StopIteration Handling**: The transpiler uses `vexc.raise('StopIteration', '')`, which depends on a non-standard `div72.vexc` module, making the code non-idiomatic and potentially non-functional without external dependencies.