Files
2026-10-20 22:40:26 +09:00

305 lines
8.6 KiB
Python

from operator import iadd, imul, mul
from testutils import assert_raises
assert -3 // 2 == -2
assert -3 % 2 == 1
a = 4
assert a**3 == 64
assert a * 3 == 12
assert a / 2 == 2
assert 2 == a / 2
assert a % 3 == 1
assert a - 3 == 1
assert -a == -4
assert +a == 4
# bitwise
assert 8 >> 3 == 1
assert 8 << 3 == 64
# Left shift raises type error
assert_raises(TypeError, lambda: 1 << 0.1)
assert_raises(TypeError, lambda: 1 << "abc")
# Right shift raises type error
assert_raises(TypeError, lambda: 1 >> 0.1)
assert_raises(TypeError, lambda: 1 >> "abc")
# Left shift raises value error on negative
assert_raises(ValueError, lambda: 1 << -1)
# Right shift raises value error on negative
assert_raises(ValueError, lambda: 1 >> -1)
# Bitwise or, and, xor raises value error on incompatible types
assert_raises(TypeError, lambda: "abc" | True)
assert_raises(TypeError, lambda: "abc" & True)
assert_raises(TypeError, lambda: "abc" ^ True)
assert_raises(TypeError, lambda: True | "abc")
assert_raises(TypeError, lambda: True & "abc")
assert_raises(TypeError, lambda: True ^ "abc")
assert_raises(TypeError, lambda: "abc" | 1.5)
assert_raises(TypeError, lambda: "abc" & 1.5)
assert_raises(TypeError, lambda: "abc" ^ 1.5)
assert_raises(TypeError, lambda: 1.5 | "abc")
assert_raises(TypeError, lambda: 1.5 & "abc")
assert_raises(TypeError, lambda: 1.5 ^ "abc")
assert_raises(TypeError, lambda: True | 1.5)
assert_raises(TypeError, lambda: True & 1.5)
assert_raises(TypeError, lambda: True ^ 1.5)
assert_raises(TypeError, lambda: 1.5 | True)
assert_raises(TypeError, lambda: 1.5 & True)
assert_raises(TypeError, lambda: 1.5 ^ True)
def check_repeat_error(error, message, operation, *args):
with assert_raises(error) as caught:
operation(*args)
assert str(caught.exception) == message, str(caught.exception)
class GetItemOnly:
def __getitem__(self, index):
raise IndexError
# An item slot does not imply support for repetition.
for value in (range(3), GetItemOnly()):
name = type(value).__name__
for operation, symbol in ((mul, "*"), (imul, "*=")):
check_repeat_error(
TypeError,
f"unsupported operand type(s) for {symbol}: '{name}' and 'int'",
operation,
value,
2,
)
check_repeat_error(
TypeError,
f"unsupported operand type(s) for *: 'int' and '{name}'",
mul,
2,
value,
)
# Native sequence slots use the same count conversion in both directions.
for value in ([1], (1,), "a", b"a", bytearray(b"a")):
namespace = {}
exec("def reverse(seq, count):\n return count * seq", namespace)
for operation_index, operation in enumerate((mul, namespace["reverse"], imul)):
for _ in range(64):
operation(value, 1)
for count, error, message in (
(1.5, TypeError, "can't multiply sequence by non-int of type 'float'"),
(2**100, OverflowError, "cannot fit 'int' into an index-sized integer"),
):
if (
type(value) in (str, bytes, tuple)
and operation_index == 1
and error is OverflowError
):
message = "Python int too large to convert to C ssize_t"
check_repeat_error(error, message, operation, value, count)
class RepeatIndex:
def __init__(self, value):
self.value = value
def __index__(self):
return self.value
assert RepeatIndex(2) * [1] == [1, 1]
assert imul(2, [1]) == [1, 1]
check_repeat_error(
TypeError,
"unsupported operand type(s) for *=: 'RepeatIndex' and 'list'",
imul,
RepeatIndex(2),
[1],
)
check_repeat_error(
OverflowError,
"cannot fit 'RepeatIndex' into an index-sized integer",
mul,
"a",
RepeatIndex(-(2**100)),
)
# Each expression has fresh bytecode so cold and warmed paths are both checked.
for value in ("a", b"a", (1,), "", b"", ()):
for expression in (
"return seq * count",
"return count * seq",
"seq *= count; return seq",
"count *= seq; return count",
):
namespace = {}
exec("def repeat(seq, count):\n " + expression, namespace)
repeat = namespace["repeat"]
check_repeat_error(
OverflowError,
"cannot fit 'int' into an index-sized integer",
repeat,
value,
2**100,
)
for _ in range(64):
assert repeat(value, 2) == mul(value, 2)
assert repeat(value, -2) == value[:0]
assert repeat(value, 0) == value[:0]
assert repeat(value, 1) == value
for count in (2**100, -(2**100)):
check_repeat_error(
OverflowError,
"Python int too large to convert to C ssize_t",
repeat,
value,
count,
)
for other in ("a", b"a", (1,)):
if type(other) is not type(value):
check_repeat_error(
OverflowError,
"cannot fit 'int' into an index-sized integer",
repeat,
other,
2**100,
)
if expression == "count *= seq; return count":
check_repeat_error(
TypeError,
f"unsupported operand type(s) for *=: 'RepeatIndex' and '{type(value).__name__}'",
repeat,
value,
RepeatIndex(2),
)
else:
assert repeat(value, RepeatIndex(2)) == mul(value, 2)
check_repeat_error(
OverflowError,
"cannot fit 'RepeatIndex' into an index-sized integer",
repeat,
value,
RepeatIndex(2**100),
)
subclass = type("RepeatSubclass", (type(value),), {})(value)
assert repeat(subclass, 2) == mul(value, 2)
overridden = type(
"OverrideRepeat",
(type(value),),
{
"__mul__": lambda self, count: "forward",
"__rmul__": lambda self, count: "reverse",
},
)(value)
expected = (
"forward" if expression.startswith(("return seq", "seq *=")) else "reverse"
)
assert repeat(overridden, 2) == expected
class DeclineRepeat(GetItemOnly):
calls = []
def __mul__(self, other):
self.calls.append("mul")
return NotImplemented
def __rmul__(self, other):
self.calls.append("rmul")
return NotImplemented
def __imul__(self, other):
self.calls.append("imul")
return NotImplemented
# Numeric fallback must not invoke the same Python method through a sequence slot.
declining = DeclineRepeat()
for operation, args, expected in (
(mul, (declining, 2), ["mul"]),
(mul, (2, declining), ["rmul"]),
(imul, (declining, 2), ["imul", "mul"]),
):
DeclineRepeat.calls.clear()
assert_raises(TypeError, operation, *args)
assert DeclineRepeat.calls == expected
class RepeatList(list):
pass
class RepeatInt(int):
pass
assert "a" * RepeatInt(2) == RepeatInt(2) * "a" == "aa"
for method, operation in (
("__mul__", lambda value: value * 2),
("__rmul__", lambda value: 2 * value),
):
value = RepeatList([1])
assert operation(value) == [1, 1]
setattr(RepeatList, method, lambda self, other: NotImplemented)
assert_raises(TypeError, operation, value)
delattr(RepeatList, method)
assert operation(value) == [1, 1]
# Inherited sequence slots must leave reflected numeric dispatch its priority.
class ReflectedCount(int):
def __rmul__(self, value):
return "reflected"
class InheritedRepeat(list):
pass
for operation in (mul, imul):
value = InheritedRepeat([1])
assert operation(value, ReflectedCount(2)) == "reflected"
assert value == [1]
class RepeatMixin:
def __rmul__(self, value):
return "mixin"
class MixedRepeat(list, RepeatMixin):
pass
# A secondary base can supply the numeric slot even if list hides its method.
assert mul(MixedRepeat([1]), ReflectedCount(2)) == [1, 1]
assert imul(ReflectedCount(2), MixedRepeat([1])) == [1, 1]
# Removing an override preserves an already installed numeric dispatcher.
InheritedRepeat.__imul__ = lambda self, other: NotImplemented
del InheritedRepeat.__imul__
value = InheritedRepeat([1])
assert imul(value, ReflectedCount(2)) is value
assert value == [1, 1]
class ReflectedAdd(list):
def __radd__(self, value):
return "reflected"
InheritedRepeat.__iadd__ = lambda self, other: NotImplemented
del InheritedRepeat.__iadd__
value = InheritedRepeat([1])
assert iadd(value, ReflectedAdd([2])) is value
assert value == [1, 2]