Skip to content

Commit ec6f7c6

Browse files
committed
[mypyc] Optimize list.__add__, list.__iadd__, tuple.__add__
1 parent f629589 commit ec6f7c6

File tree

9 files changed

+103
-2
lines changed

9 files changed

+103
-2
lines changed

mypyc/doc/list_operations.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ Operators
3232

3333
* ``lst[n]`` (get item by integer index)
3434
* ``lst[n:m]``, ``lst[n:]``, ``lst[:m]``, ``lst[:]`` (slicing)
35+
* ``lst1 + lst2``, ``lst += iter``
3536
* ``lst * n``, ``n * lst``
3637
* ``obj in lst``
3738

mypyc/doc/tuple_operations.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Operators
2121

2222
* ``tup[n]`` (integer index)
2323
* ``tup[n:m]``, ``tup[n:]``, ``tup[:m]`` (slicing)
24+
* ``tup1 + tup2``
2425

2526
Statements
2627
----------

mypyc/primitives/list_ops.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,24 @@
271271
error_kind=ERR_MAGIC,
272272
)
273273

274+
# list + list
275+
binary_op(
276+
name="+",
277+
arg_types=[list_rprimitive, list_rprimitive],
278+
return_type=list_rprimitive,
279+
c_function_name="PySequence_Concat",
280+
error_kind=ERR_MAGIC,
281+
)
282+
283+
# list += list
284+
binary_op(
285+
name="+=",
286+
arg_types=[list_rprimitive, object_rprimitive],
287+
return_type=list_rprimitive,
288+
c_function_name="PySequence_InPlaceConcat",
289+
error_kind=ERR_MAGIC,
290+
)
291+
274292
# list * int
275293
binary_op(
276294
name="*",

mypyc/primitives/tuple_ops.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
object_rprimitive,
1616
tuple_rprimitive,
1717
)
18-
from mypyc.primitives.registry import custom_op, function_op, load_address_op, method_op
18+
from mypyc.primitives.registry import binary_op, custom_op, function_op, load_address_op, method_op
1919

2020
# Get the 'builtins.tuple' type object.
2121
load_address_op(name="builtins.tuple", type=object_rprimitive, src="PyTuple_Type")
@@ -74,6 +74,15 @@
7474
error_kind=ERR_MAGIC,
7575
)
7676

77+
# tuple + tuple
78+
binary_op(
79+
name="+",
80+
arg_types=[tuple_rprimitive, tuple_rprimitive],
81+
return_type=tuple_rprimitive,
82+
c_function_name="PySequence_Concat",
83+
error_kind=ERR_MAGIC,
84+
)
85+
7786
# tuple[begin:end]
7887
tuple_slice_op = custom_op(
7988
arg_types=[tuple_rprimitive, int_rprimitive, int_rprimitive],

mypyc/test-data/fixtures/ir.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,10 @@ def __getitem__(self, i: slice) -> Tuple[T_co, ...]: pass
207207
def __len__(self) -> int: pass
208208
def __iter__(self) -> Iterator[T_co]: ...
209209
def __contains__(self, item: object) -> int: ...
210+
@overload
211+
def __add__(self, value: Tuple[T_co, ...], /) -> Tuple[T_co, ...]: ...
212+
@overload
213+
def __add__(self, value: Tuple[_T, ...], /) -> Tuple[T_co | _T, ...]: ...
210214

211215
class function: pass
212216

@@ -223,7 +227,11 @@ def __rmul__(self, i: int) -> List[_T]: pass
223227
def __iter__(self) -> Iterator[_T]: pass
224228
def __len__(self) -> int: pass
225229
def __contains__(self, item: object) -> int: ...
226-
def __add__(self, x: List[_T]) -> List[_T]: ...
230+
@overload
231+
def __add__(self, value: List[_T], /) -> List[_T]: ...
232+
@overload
233+
def __add__(self, value: List[_S], /) -> List[_S | _T]: ...
234+
def __iadd__(self, value: Iterable[_T], /) -> List[_T]: ... # type: ignore[misc]
227235
def append(self, x: _T) -> None: pass
228236
def pop(self, i: int = -1) -> _T: pass
229237
def count(self, _T) -> int: pass

mypyc/test-data/irbuild-lists.test

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,32 @@ L0:
145145
x = r10
146146
return 1
147147

148+
[case testListAdd]
149+
from typing import List
150+
def f(a: List[int], b: List[int]) -> None:
151+
c = a + b
152+
[out]
153+
def f(a, b):
154+
a, b, r0, c :: list
155+
L0:
156+
r0 = PySequence_Concat(a, b)
157+
c = r0
158+
return 1
159+
160+
[case testListIAdd]
161+
from typing import List, Any
162+
def f(a: List[int], b: Any) -> None:
163+
a += b
164+
[out]
165+
def f(a, b):
166+
a :: list
167+
b :: object
168+
r0 :: list
169+
L0:
170+
r0 = PySequence_InPlaceConcat(a, b)
171+
a = r0
172+
return 1
173+
148174
[case testListMultiply]
149175
from typing import List
150176
def f(a: List[int]) -> None:

mypyc/test-data/irbuild-tuple.test

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,3 +384,15 @@ L3:
384384
L4:
385385
a = r1
386386
return 1
387+
388+
[case testTupleAdd]
389+
from typing import Tuple
390+
def f(a: Tuple[int, ...], b: Tuple[int, ...]) -> None:
391+
c = a + b
392+
[out]
393+
def f(a, b):
394+
a, b, r0, c :: tuple
395+
L0:
396+
r0 = PySequence_Concat(a, b)
397+
c = r0
398+
return 1

mypyc/test-data/run-lists.test

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,9 @@ print(g())
267267
7
268268

269269
[case testListOps]
270+
from typing import Any
271+
from testutil import assertRaises
272+
270273
def test_slicing() -> None:
271274
# Use dummy adds to avoid constant folding
272275
zero = int()
@@ -289,6 +292,22 @@ def test_slicing() -> None:
289292
assert s[long_int:] == []
290293
assert s[-long_int:-1] == ["f", "o", "o", "b", "a"]
291294

295+
def in_place_add(l2: Any) -> list[Any]:
296+
l1 = [1, 2]
297+
l1 += l2
298+
return l1
299+
300+
def test_add() -> None:
301+
res = [1, 2, 3, 4]
302+
assert [1, 2] + [3, 4] == res
303+
with assertRaises(TypeError, 'can only concatenate list (not "tuple") to list'):
304+
assert [1, 2] + (3, 4) == res # type: ignore[operator]
305+
assert in_place_add([3, 4]) == res
306+
assert in_place_add((3, 4)) == res
307+
assert in_place_add({3, 4}) == res
308+
assert in_place_add({3: "", 4: ""}) == res
309+
assert in_place_add(range(3, 5)) == res
310+
292311
[case testOperatorInExpression]
293312

294313
def tuple_in_int0(i: int) -> bool:

mypyc/test-data/run-tuples.test

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ assert Record.__annotations__ == {
147147

148148
[case testTupleOps]
149149
from typing import Tuple, Final, List, Any, Optional
150+
from testutil import assertRaises
150151

151152
def f() -> Tuple[()]:
152153
return ()
@@ -254,3 +255,9 @@ TUPLE: Final[Tuple[str, ...]] = ('x', 'y')
254255
def test_final_boxed_tuple() -> None:
255256
t = TUPLE
256257
assert t == ('x', 'y')
258+
259+
def test_add() -> None:
260+
res = (1, 2, 3, 4)
261+
assert (1, 2) + (3, 4) == res
262+
with assertRaises(TypeError, 'can only concatenate tuple (not "list") to tuple'):
263+
assert (1, 2) + [3, 4] == res # type: ignore[operator]

0 commit comments

Comments
 (0)