Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions mypyc/doc/list_operations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Operators

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

Expand Down
1 change: 1 addition & 0 deletions mypyc/doc/tuple_operations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Operators

* ``tup[n]`` (integer index)
* ``tup[n:m]``, ``tup[n:]``, ``tup[:m]`` (slicing)
* ``tup1 + tup2``
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this only specialized for variable-length tuples? If yes, it's worth adding a note here, since fixed-length tuple concatenation could be quite slow otherwise.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should work for fixed-length tuple as well. Just the whole box / unbox dance which could possibly be optimized further at a later point.


Statements
----------
Expand Down
18 changes: 18 additions & 0 deletions mypyc/primitives/list_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,24 @@
error_kind=ERR_MAGIC,
)

# list + list
binary_op(
name="+",
arg_types=[list_rprimitive, list_rprimitive],
return_type=list_rprimitive,
c_function_name="PySequence_Concat",
error_kind=ERR_MAGIC,
)

# list += list
binary_op(
name="+=",
arg_types=[list_rprimitive, object_rprimitive],
return_type=list_rprimitive,
c_function_name="PySequence_InPlaceConcat",
error_kind=ERR_MAGIC,
)

# list * int
binary_op(
name="*",
Expand Down
11 changes: 10 additions & 1 deletion mypyc/primitives/tuple_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
object_rprimitive,
tuple_rprimitive,
)
from mypyc.primitives.registry import custom_op, function_op, load_address_op, method_op
from mypyc.primitives.registry import binary_op, custom_op, function_op, load_address_op, method_op

# Get the 'builtins.tuple' type object.
load_address_op(name="builtins.tuple", type=object_rprimitive, src="PyTuple_Type")
Expand Down Expand Up @@ -74,6 +74,15 @@
error_kind=ERR_MAGIC,
)

# tuple + tuple
binary_op(
name="+",
arg_types=[tuple_rprimitive, tuple_rprimitive],
return_type=tuple_rprimitive,
c_function_name="PySequence_Concat",
error_kind=ERR_MAGIC,
)

# tuple[begin:end]
tuple_slice_op = custom_op(
arg_types=[tuple_rprimitive, int_rprimitive, int_rprimitive],
Expand Down
10 changes: 9 additions & 1 deletion mypyc/test-data/fixtures/ir.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,10 @@ def __getitem__(self, i: slice) -> Tuple[T_co, ...]: pass
def __len__(self) -> int: pass
def __iter__(self) -> Iterator[T_co]: ...
def __contains__(self, item: object) -> int: ...
@overload
def __add__(self, value: Tuple[T_co, ...], /) -> Tuple[T_co, ...]: ...
@overload
def __add__(self, value: Tuple[_T, ...], /) -> Tuple[T_co | _T, ...]: ...

class function: pass

Expand All @@ -223,7 +227,11 @@ def __rmul__(self, i: int) -> List[_T]: pass
def __iter__(self) -> Iterator[_T]: pass
def __len__(self) -> int: pass
def __contains__(self, item: object) -> int: ...
def __add__(self, x: List[_T]) -> List[_T]: ...
@overload
def __add__(self, value: List[_T], /) -> List[_T]: ...
@overload
def __add__(self, value: List[_S], /) -> List[_S | _T]: ...
def __iadd__(self, value: Iterable[_T], /) -> List[_T]: ... # type: ignore[misc]
def append(self, x: _T) -> None: pass
def pop(self, i: int = -1) -> _T: pass
def count(self, _T) -> int: pass
Expand Down
26 changes: 26 additions & 0 deletions mypyc/test-data/irbuild-lists.test
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,32 @@ L0:
x = r10
return 1

[case testListAdd]
from typing import List
def f(a: List[int], b: List[int]) -> None:
c = a + b
[out]
def f(a, b):
a, b, r0, c :: list
L0:
r0 = PySequence_Concat(a, b)
c = r0
return 1

[case testListIAdd]
from typing import List, Any
def f(a: List[int], b: Any) -> None:
a += b
[out]
def f(a, b):
a :: list
b :: object
r0 :: list
L0:
r0 = PySequence_InPlaceConcat(a, b)
a = r0
return 1

[case testListMultiply]
from typing import List
def f(a: List[int]) -> None:
Expand Down
12 changes: 12 additions & 0 deletions mypyc/test-data/irbuild-tuple.test
Original file line number Diff line number Diff line change
Expand Up @@ -384,3 +384,15 @@ L3:
L4:
a = r1
return 1

[case testTupleAdd]
from typing import Tuple
def f(a: Tuple[int, ...], b: Tuple[int, ...]) -> None:
c = a + b
[out]
def f(a, b):
a, b, r0, c :: tuple
L0:
r0 = PySequence_Concat(a, b)
c = r0
return 1
19 changes: 19 additions & 0 deletions mypyc/test-data/run-lists.test
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,9 @@ print(g())
7

[case testListOps]
from typing import Any
from testutil import assertRaises

def test_slicing() -> None:
# Use dummy adds to avoid constant folding
zero = int()
Expand All @@ -289,6 +292,22 @@ def test_slicing() -> None:
assert s[long_int:] == []
assert s[-long_int:-1] == ["f", "o", "o", "b", "a"]

def in_place_add(l2: Any) -> list[Any]:
l1 = [1, 2]
l1 += l2
return l1

def test_add() -> None:
res = [1, 2, 3, 4]
assert [1, 2] + [3, 4] == res
with assertRaises(TypeError, 'can only concatenate list (not "tuple") to list'):
assert [1, 2] + (3, 4) == res # type: ignore[operator]
assert in_place_add([3, 4]) == res
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test that the identity of the target object is preserved in +=?

assert in_place_add((3, 4)) == res
assert in_place_add({3, 4}) == res
assert in_place_add({3: "", 4: ""}) == res
assert in_place_add(range(3, 5)) == res

[case testOperatorInExpression]

def tuple_in_int0(i: int) -> bool:
Expand Down
7 changes: 7 additions & 0 deletions mypyc/test-data/run-tuples.test
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ assert Record.__annotations__ == {

[case testTupleOps]
from typing import Tuple, Final, List, Any, Optional
from testutil import assertRaises

def f() -> Tuple[()]:
return ()
Expand Down Expand Up @@ -254,3 +255,9 @@ TUPLE: Final[Tuple[str, ...]] = ('x', 'y')
def test_final_boxed_tuple() -> None:
t = TUPLE
assert t == ('x', 'y')

def test_add() -> None:
res = (1, 2, 3, 4)
assert (1, 2) + (3, 4) == res
with assertRaises(TypeError, 'can only concatenate tuple (not "list") to tuple'):
assert (1, 2) + [3, 4] == res # type: ignore[operator]