Skip to content

Commit f4a92cd

Browse files
jgarzikclaude
andcommitted
Add 2-arg next(); import test_extcall, test_iter, test_lambda, test_property, test_string
Infrastructure: - builtin next(iterator, default): return default on StopIteration instead of raising (2-arg form) New tests: - test_extcall.py: 12 tests — star args, kwargs, mixed calls, defaults - test_iter.py: 25 tests — iter protocol, for loops, enumerate, zip, map, filter, reversed, sorted, sum, min/max, any/all, custom iterators - test_lambda.py: 12 tests — closures, defaults, varargs, callbacks - test_property.py: 10 tests, 1 skip — property, staticmethod, classmethod - test_string.py: 21 tests — f-strings, % format, methods, slicing Total CPython test suites: 34 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent b3513cf commit f4a92cd

7 files changed

Lines changed: 624 additions & 1 deletion

File tree

Makefile

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,16 @@ gen-cpython-tests:
114114
@$(PYTHON) -m py_compile tests/cpython/test_opcodes.py
115115
@echo "Compiling tests/cpython/test_baseexception.py..."
116116
@$(PYTHON) -m py_compile tests/cpython/test_baseexception.py
117+
@echo "Compiling tests/cpython/test_extcall.py..."
118+
@$(PYTHON) -m py_compile tests/cpython/test_extcall.py
119+
@echo "Compiling tests/cpython/test_iter.py..."
120+
@$(PYTHON) -m py_compile tests/cpython/test_iter.py
121+
@echo "Compiling tests/cpython/test_lambda.py..."
122+
@$(PYTHON) -m py_compile tests/cpython/test_lambda.py
123+
@echo "Compiling tests/cpython/test_property.py..."
124+
@$(PYTHON) -m py_compile tests/cpython/test_property.py
125+
@echo "Compiling tests/cpython/test_string.py..."
126+
@$(PYTHON) -m py_compile tests/cpython/test_string.py
117127
@echo "Done."
118128

119129
check-cpython: $(TARGET) gen-cpython-tests
@@ -175,3 +185,13 @@ check-cpython: $(TARGET) gen-cpython-tests
175185
@./apython tests/cpython/__pycache__/test_opcodes.cpython-312.pyc
176186
@echo "Running CPython test_baseexception.py..."
177187
@./apython tests/cpython/__pycache__/test_baseexception.cpython-312.pyc
188+
@echo "Running CPython test_extcall.py..."
189+
@./apython tests/cpython/__pycache__/test_extcall.cpython-312.pyc
190+
@echo "Running CPython test_iter.py..."
191+
@./apython tests/cpython/__pycache__/test_iter.cpython-312.pyc
192+
@echo "Running CPython test_lambda.py..."
193+
@./apython tests/cpython/__pycache__/test_lambda.cpython-312.pyc
194+
@echo "Running CPython test_property.py..."
195+
@./apython tests/cpython/__pycache__/test_property.cpython-312.pyc
196+
@echo "Running CPython test_string.py..."
197+
@./apython tests/cpython/__pycache__/test_string.cpython-312.pyc

src/builtins_extra.asm

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1692,7 +1692,54 @@ DEF_FUNC builtin_next_fn
16921692
push rbx
16931693

16941694
cmp rsi, 1
1695-
jne .next_error
1695+
je .next_one_arg
1696+
cmp rsi, 2
1697+
je .next_two_args
1698+
jmp .next_error
1699+
1700+
.next_two_args:
1701+
; next(iterator, default) — return default on StopIteration
1702+
push qword [rdi + 24] ; save default tag
1703+
push qword [rdi + 16] ; save default payload
1704+
; Fall through to same iterator logic, but with default on stack
1705+
cmp qword [rdi + 8], TAG_PTR
1706+
jne .next_two_type_error
1707+
mov rdi, [rdi] ; args[0] = iterator
1708+
mov rax, [rdi + PyObject.ob_type]
1709+
mov rax, [rax + PyTypeObject.tp_iternext]
1710+
test rax, rax
1711+
jz .next_two_type_error
1712+
call rax
1713+
test edx, edx
1714+
jz .next_two_default ; exhausted → return default
1715+
; Got value — discard saved default
1716+
add rsp, 16
1717+
pop rbx
1718+
leave
1719+
ret
1720+
.next_two_default:
1721+
; Clear any StopIteration exception
1722+
extern current_exception
1723+
mov rax, [rel current_exception]
1724+
test rax, rax
1725+
jz .next_two_ret_default
1726+
push rdi
1727+
mov rdi, rax
1728+
mov qword [rel current_exception], 0
1729+
call obj_decref
1730+
pop rdi
1731+
.next_two_ret_default:
1732+
pop rax ; default payload
1733+
pop rdx ; default tag
1734+
INCREF_VAL rax, rdx
1735+
pop rbx
1736+
leave
1737+
ret
1738+
.next_two_type_error:
1739+
add rsp, 16 ; discard saved default
1740+
jmp .next_type_error
1741+
1742+
.next_one_arg:
16961743

16971744
cmp qword [rdi + 8], TAG_SMALLINT ; check args[0] tag
16981745
je .next_type_error

tests/cpython/test_extcall.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
"""Tests for extended function call syntax — adapted from CPython test_extcall.py"""
2+
3+
import unittest
4+
5+
6+
class ExtCallTest(unittest.TestCase):
7+
8+
def test_basic_star_args(self):
9+
def f(*args):
10+
return args
11+
self.assertEqual(f(*(1, 2, 3)), (1, 2, 3))
12+
self.assertEqual(f(*[1, 2, 3]), (1, 2, 3))
13+
14+
def test_basic_kwargs(self):
15+
def f(**kwargs):
16+
return kwargs
17+
self.assertEqual(f(**{'a': 1, 'b': 2}), {'a': 1, 'b': 2})
18+
self.assertEqual(f(), {})
19+
20+
def test_mixed_args_kwargs(self):
21+
def f(*args, **kwargs):
22+
return args, kwargs
23+
self.assertEqual(f(1, 2, a=3), ((1, 2), {'a': 3}))
24+
self.assertEqual(f(*(1, 2), **{'a': 3}), ((1, 2), {'a': 3}))
25+
26+
def test_positional_and_star(self):
27+
def f(a, b, c):
28+
return a + b + c
29+
self.assertEqual(f(1, *(2, 3)), 6)
30+
self.assertEqual(f(*(1, 2, 3)), 6)
31+
32+
def test_keyword_args(self):
33+
def f(a, b=10, c=20):
34+
return (a, b, c)
35+
self.assertEqual(f(1), (1, 10, 20))
36+
self.assertEqual(f(1, b=2), (1, 2, 20))
37+
self.assertEqual(f(1, c=3), (1, 10, 3))
38+
self.assertEqual(f(1, b=2, c=3), (1, 2, 3))
39+
40+
def test_keyword_only_args(self):
41+
def f(a, *, b, c=10):
42+
return (a, b, c)
43+
self.assertEqual(f(1, b=2), (1, 2, 10))
44+
self.assertEqual(f(1, b=2, c=3), (1, 2, 3))
45+
46+
def test_star_in_call(self):
47+
def f(a, b, c, d):
48+
return a * 1000 + b * 100 + c * 10 + d
49+
self.assertEqual(f(1, *(2, 3), **{'d': 4}), 1234)
50+
51+
def test_double_star_dict(self):
52+
def f(**kw):
53+
return sorted(kw.items())
54+
d = {'a': 1, 'b': 2}
55+
self.assertEqual(f(**d), [('a', 1), ('b', 2)])
56+
57+
def test_default_args(self):
58+
def f(a, b=2, c=3):
59+
return (a, b, c)
60+
self.assertEqual(f(1), (1, 2, 3))
61+
self.assertEqual(f(1, 5), (1, 5, 3))
62+
self.assertEqual(f(1, 5, 7), (1, 5, 7))
63+
64+
def test_varargs_and_defaults(self):
65+
def f(a, b=10, *args):
66+
return (a, b, args)
67+
self.assertEqual(f(1), (1, 10, ()))
68+
self.assertEqual(f(1, 2), (1, 2, ()))
69+
self.assertEqual(f(1, 2, 3, 4), (1, 2, (3, 4)))
70+
71+
def test_too_few_args(self):
72+
def f(a, b):
73+
pass
74+
self.assertRaises(TypeError, f)
75+
self.assertRaises(TypeError, f, 1)
76+
77+
def test_too_many_args(self):
78+
def f(a):
79+
pass
80+
self.assertRaises(TypeError, f, 1, 2)
81+
82+
83+
if __name__ == "__main__":
84+
unittest.main()

tests/cpython/test_iter.py

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
"""Tests for iterator protocol — adapted from CPython test_iter.py"""
2+
3+
import unittest
4+
5+
6+
class BasicIterTest(unittest.TestCase):
7+
8+
def test_iter_list(self):
9+
self.assertEqual(list(iter([1, 2, 3])), [1, 2, 3])
10+
11+
def test_iter_tuple(self):
12+
self.assertEqual(list(iter((1, 2, 3))), [1, 2, 3])
13+
14+
def test_iter_string(self):
15+
self.assertEqual(list(iter("abc")), ['a', 'b', 'c'])
16+
17+
def test_iter_range(self):
18+
self.assertEqual(list(iter(range(5))), [0, 1, 2, 3, 4])
19+
20+
def test_iter_dict_keys(self):
21+
d = {'a': 1, 'b': 2, 'c': 3}
22+
keys = list(iter(d))
23+
self.assertEqual(sorted(keys), ['a', 'b', 'c'])
24+
25+
def test_next_with_default(self):
26+
it = iter([1])
27+
self.assertEqual(next(it), 1)
28+
self.assertEqual(next(it, 'default'), 'default')
29+
30+
def test_stopiteration(self):
31+
it = iter([])
32+
self.assertRaises(StopIteration, next, it)
33+
34+
def test_for_loop(self):
35+
result = []
36+
for x in [1, 2, 3]:
37+
result.append(x)
38+
self.assertEqual(result, [1, 2, 3])
39+
40+
def test_for_loop_break(self):
41+
result = []
42+
for x in range(10):
43+
if x == 5:
44+
break
45+
result.append(x)
46+
self.assertEqual(result, [0, 1, 2, 3, 4])
47+
48+
def test_for_loop_continue(self):
49+
result = []
50+
for x in range(10):
51+
if x % 2 == 0:
52+
continue
53+
result.append(x)
54+
self.assertEqual(result, [1, 3, 5, 7, 9])
55+
56+
def test_for_else(self):
57+
hit_else = False
58+
for x in range(3):
59+
pass
60+
else:
61+
hit_else = True
62+
self.assertTrue(hit_else)
63+
64+
def test_for_else_break(self):
65+
hit_else = False
66+
for x in range(3):
67+
break
68+
else:
69+
hit_else = True
70+
self.assertFalse(hit_else)
71+
72+
def test_nested_for(self):
73+
result = []
74+
for x in range(3):
75+
for y in range(3):
76+
result.append((x, y))
77+
self.assertEqual(len(result), 9)
78+
self.assertEqual(result[0], (0, 0))
79+
self.assertEqual(result[-1], (2, 2))
80+
81+
def test_enumerate(self):
82+
self.assertEqual(list(enumerate('abc')),
83+
[(0, 'a'), (1, 'b'), (2, 'c')])
84+
self.assertEqual(list(enumerate('abc', 1)),
85+
[(1, 'a'), (2, 'b'), (3, 'c')])
86+
87+
def test_zip(self):
88+
self.assertEqual(list(zip([1, 2], [3, 4])),
89+
[(1, 3), (2, 4)])
90+
self.assertEqual(list(zip([1, 2, 3], [4, 5])),
91+
[(1, 4), (2, 5)])
92+
93+
def test_map(self):
94+
def double(x):
95+
return x * 2
96+
self.assertEqual(list(map(double, [1, 2, 3])), [2, 4, 6])
97+
98+
def test_filter(self):
99+
def is_even(x):
100+
return x % 2 == 0
101+
self.assertEqual(list(filter(is_even, range(10))),
102+
[0, 2, 4, 6, 8])
103+
104+
def test_reversed(self):
105+
self.assertEqual(list(reversed([1, 2, 3])), [3, 2, 1])
106+
self.assertEqual(list(reversed(range(5))), [4, 3, 2, 1, 0])
107+
108+
def test_sorted(self):
109+
self.assertEqual(sorted([3, 1, 2]), [1, 2, 3])
110+
self.assertEqual(sorted([3, 1, 2], reverse=True), [3, 2, 1])
111+
112+
def test_sum(self):
113+
self.assertEqual(sum([1, 2, 3]), 6)
114+
self.assertEqual(sum(range(10)), 45)
115+
self.assertEqual(sum([], 10), 10)
116+
117+
def test_min_max(self):
118+
self.assertEqual(min([3, 1, 2]), 1)
119+
self.assertEqual(max([3, 1, 2]), 3)
120+
self.assertEqual(min(3, 1, 2), 1)
121+
self.assertEqual(max(3, 1, 2), 3)
122+
123+
def test_any_all(self):
124+
self.assertTrue(any([0, 0, 1]))
125+
self.assertFalse(any([0, 0, 0]))
126+
self.assertTrue(all([1, 1, 1]))
127+
self.assertFalse(all([1, 0, 1]))
128+
self.assertTrue(any(x > 3 for x in range(5)))
129+
self.assertTrue(all(x < 5 for x in range(5)))
130+
131+
132+
class CustomIterTest(unittest.TestCase):
133+
134+
def test_iter_protocol(self):
135+
class MyIter:
136+
def __init__(self, data):
137+
self.data = data
138+
self.idx = 0
139+
def __iter__(self):
140+
return self
141+
def __next__(self):
142+
if self.idx >= len(self.data):
143+
raise StopIteration
144+
val = self.data[self.idx]
145+
self.idx += 1
146+
return val
147+
self.assertEqual(list(MyIter([10, 20, 30])), [10, 20, 30])
148+
149+
def test_getitem_protocol(self):
150+
class MySeq:
151+
def __init__(self, data):
152+
self.data = data
153+
def __getitem__(self, idx):
154+
return self.data[idx]
155+
self.assertEqual(list(MySeq([1, 2, 3])), [1, 2, 3])
156+
157+
def test_iter_in_for(self):
158+
class Counter:
159+
def __init__(self, n):
160+
self.n = n
161+
self.i = 0
162+
def __iter__(self):
163+
return self
164+
def __next__(self):
165+
if self.i >= self.n:
166+
raise StopIteration
167+
self.i += 1
168+
return self.i
169+
self.assertEqual(list(Counter(5)), [1, 2, 3, 4, 5])
170+
171+
172+
if __name__ == "__main__":
173+
unittest.main()

0 commit comments

Comments
 (0)