From 0ced76232e313f03e2b2e8bc753fca66d393ac7b Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Thu, 27 Aug 2026 21:51:58 -0700 Subject: [PATCH 1/2] [mypyc] specialise bytearray(bytes()[i:j]) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes https://github.com/mypyc/mypyc/issues/1216 Local compiled benchmarks showed approximately 1.8–2× speedup for 1 KiB and 1 MiB slices. Authored-by: Codex --- mypyc/irbuild/specialize.py | 32 +++++++- mypyc/lib-rt/bytearray_extra_ops.c | 19 +++++ mypyc/lib-rt/bytearray_extra_ops.h | 3 + mypyc/primitives/bytearray_ops.py | 18 ++++- mypyc/test-data/irbuild-bytes.test | 66 +++++++++++++++++ mypyc/test-data/run-bytes.test | 114 +++++++++++++++++++++++++++++ 6 files changed, 250 insertions(+), 2 deletions(-) diff --git a/mypyc/irbuild/specialize.py b/mypyc/irbuild/specialize.py index a397143d457af..1af1f85de8cd3 100644 --- a/mypyc/irbuild/specialize.py +++ b/mypyc/irbuild/specialize.py @@ -30,6 +30,7 @@ MemberExpr, NameExpr, RefExpr, + SliceExpr, StrExpr, SuperExpr, TupleExpr, @@ -64,7 +65,9 @@ int32_rprimitive, int64_rprimitive, int_rprimitive, + is_any_int, is_bool_rprimitive, + is_bytes_rprimitive, is_dict_rprimitive, is_fixed_width_rtype, is_float_rprimitive, @@ -108,7 +111,7 @@ vec_to_list, vec_to_tuple, ) -from mypyc.primitives.bytearray_ops import isinstance_bytearray +from mypyc.primitives.bytearray_ops import bytearray_from_bytes_slice_op, isinstance_bytearray from mypyc.primitives.bytes_ops import ( bytes_adjust_index_op, bytes_get_item_unsafe_op, @@ -348,6 +351,33 @@ def translate_vec_to_list(builder: IRBuilder, expr: CallExpr, callee: RefExpr) - return None +@specialize_function("builtins.bytearray") +def translate_bytearray_from_bytes_slice( + builder: IRBuilder, expr: CallExpr, callee: RefExpr +) -> Value | None: + """Construct a bytearray from a bytes slice without an intermediate copy.""" + if len(expr.args) != 1 or expr.arg_kinds != [ARG_POS]: + return None + arg = expr.args[0] + if not isinstance(arg, IndexExpr) or not is_bytes_rprimitive(builder.node_type(arg.base)): + return None + index = arg.index + if ( + not isinstance(index, SliceExpr) + or index.stride is not None + or index.begin_index is None + or index.end_index is None + or not is_any_int(builder.node_type(index.begin_index)) + or not is_any_int(builder.node_type(index.end_index)) + ): + return None + + obj = builder.accept(arg.base) + start = builder.accept(index.begin_index) + end = builder.accept(index.end_index) + return builder.primitive_op(bytearray_from_bytes_slice_op, [obj, start, end], expr.line) + + @specialize_function("builtins.list") def dict_methods_fast_path(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None: """Specialize a common case when list() is called on a dictionary diff --git a/mypyc/lib-rt/bytearray_extra_ops.c b/mypyc/lib-rt/bytearray_extra_ops.c index a956f8c76f1f3..1a9fcddf2947e 100644 --- a/mypyc/lib-rt/bytearray_extra_ops.c +++ b/mypyc/lib-rt/bytearray_extra_ops.c @@ -3,3 +3,22 @@ PyObject *CPyByteArray_New(void) { return PyByteArray_FromStringAndSize(NULL, 0); } + +PyObject *CPyByteArray_FromBytesSlice(PyObject *obj, CPyTagged start, CPyTagged end) { + if (PyBytes_CheckExact(obj) && CPyTagged_CheckShort(start) && CPyTagged_CheckShort(end)) { + Py_ssize_t startn = CPyTagged_ShortAsSsize_t(start); + Py_ssize_t endn = CPyTagged_ShortAsSsize_t(end); + if (0 <= startn && startn <= endn && endn <= PyBytes_GET_SIZE(obj)) { + return PyByteArray_FromStringAndSize(PyBytes_AS_STRING(obj) + startn, endn - startn); + } + } + + // Preserve general slice semantics, including bytes subclass overrides. + PyObject *slice = CPyObject_GetSlice(obj, start, end); + if (slice == NULL) { + return NULL; + } + PyObject *result = PyByteArray_FromObject(slice); + Py_DECREF(slice); + return result; +} diff --git a/mypyc/lib-rt/bytearray_extra_ops.h b/mypyc/lib-rt/bytearray_extra_ops.h index 41f17be3ab963..8489d6027e458 100644 --- a/mypyc/lib-rt/bytearray_extra_ops.h +++ b/mypyc/lib-rt/bytearray_extra_ops.h @@ -7,4 +7,7 @@ // Construct empty bytearray PyObject *CPyByteArray_New(void); +// Construct a bytearray from a bytes slice, avoiding an intermediate bytes object. +PyObject *CPyByteArray_FromBytesSlice(PyObject *obj, CPyTagged start, CPyTagged end); + #endif diff --git a/mypyc/primitives/bytearray_ops.py b/mypyc/primitives/bytearray_ops.py index 2128a6d48d5cc..e601b32e355e8 100644 --- a/mypyc/primitives/bytearray_ops.py +++ b/mypyc/primitives/bytearray_ops.py @@ -9,7 +9,13 @@ from mypyc.ir.deps import BYTEARRAY_EXTRA_OPS from mypyc.ir.ops import ERR_MAGIC, ERR_NEVER -from mypyc.ir.rtypes import bit_rprimitive, bytearray_rprimitive, object_rprimitive +from mypyc.ir.rtypes import ( + bit_rprimitive, + bytearray_rprimitive, + bytes_rprimitive, + int_rprimitive, + object_rprimitive, +) from mypyc.primitives.registry import custom_primitive_op, function_op, load_address_op # Get the 'bytearray' type object. @@ -24,6 +30,16 @@ error_kind=ERR_MAGIC, ) +# bytearray(bytes[start:end]) +bytearray_from_bytes_slice_op = custom_primitive_op( + name="bytearray_from_bytes_slice", + arg_types=[bytes_rprimitive, int_rprimitive, int_rprimitive], + return_type=bytearray_rprimitive, + c_function_name="CPyByteArray_FromBytesSlice", + error_kind=ERR_MAGIC, + dependencies=[BYTEARRAY_EXTRA_OPS], +) + # bytearray() -- construct empty bytearray function_op( name="builtins.bytearray", diff --git a/mypyc/test-data/irbuild-bytes.test b/mypyc/test-data/irbuild-bytes.test index b149e0a96cdf6..54be80425c157 100644 --- a/mypyc/test-data/irbuild-bytes.test +++ b/mypyc/test-data/irbuild-bytes.test @@ -99,6 +99,72 @@ L0: r0 = CPyBytes_GetSlice(a, start, end) return r0 +[case testBytearrayFromBytesSlice] +def f(a: bytes, start: int, end: int) -> bytearray: + return bytearray(a[start:end]) +[out] +def f(a, start, end): + a :: bytes + start, end :: int + r0 :: bytearray +L0: + r0 = CPyByteArray_FromBytesSlice(a, start, end) + return r0 + +[case testBytearrayFromBytesSliceFixedWidth_64bit] +from mypy_extensions import i32, i64 + +def f(a: bytes, start: i32, end: i64) -> bytearray: + return bytearray(a[start:end]) +[out] +def f(a, start, end): + a :: bytes + start :: i32 + end :: i64 + r0 :: native_int + r1 :: int + r2, r3 :: bit + r4, r5, r6 :: int + r7 :: bytearray +L0: + r0 = extend signed start: i32 to native_int + r1 = r0 << 1 + r2 = end <= 4611686018427387903 :: signed + if r2 goto L1 else goto L2 :: bool +L1: + r3 = end >= -4611686018427387904 :: signed + if r3 goto L3 else goto L2 :: bool +L2: + r4 = CPyTagged_FromInt64(end) + r5 = r4 + goto L4 +L3: + r6 = end << 1 + r5 = r6 +L4: + r7 = CPyByteArray_FromBytesSlice(a, r1, r5) + return r7 + +[case testBytearrayFromBytesSliceWithStep] +def f(a: bytes, start: int, end: int, step: int) -> bytearray: + return bytearray(a[start:end:step]) +[out] +def f(a, start, end, step): + a :: bytes + start, end, step :: int + r0, r1, r2, r3, r4 :: object + r5 :: bytes + r6 :: bytearray +L0: + r0 = box(int, start) + r1 = box(int, end) + r2 = box(int, step) + r3 = PySlice_New(r0, r1, r2) + r4 = PyObject_GetItem(a, r3) + r5 = cast(bytes, r4) + r6 = PyByteArray_FromObject(r5) + return r6 + [case testBytesIndex] from mypy_extensions import i64 diff --git a/mypyc/test-data/run-bytes.test b/mypyc/test-data/run-bytes.test index 1d4157033e650..5309ec3ac1681 100644 --- a/mypyc/test-data/run-bytes.test +++ b/mypyc/test-data/run-bytes.test @@ -275,6 +275,120 @@ def test_bytes_slicing() -> None: assert type(b[-ten:]) == bytes assert type(b[:]) == bytes +[case testBytearrayFromBytesSlice] +from typing import Any, Callable +from mypy_extensions import i32, i64 + +def from_slice(b: bytes, start: int, end: int) -> bytearray: + return bytearray(b[start:end]) + +def from_slice_i64(b: bytes, start: i64, end: i64) -> bytearray: + return bytearray(b[start:end]) + +def from_slice_i32(b: bytes, start: i32, end: i32) -> bytearray: + return bytearray(b[start:end]) + +def from_slice_calls( + source: Callable[[], bytes], start: Callable[[], int], end: Callable[[], int] +) -> bytearray: + return bytearray(source()[start():end()]) + +def from_slice_step(b: bytes, start: int, end: int, step: int) -> bytearray: + return bytearray(b[start:end:step]) + +def from_slice_any(b: bytes, start: Any, end: Any) -> bytearray: + return bytearray(b[start:end]) + +[file driver.py] +from native import ( + from_slice, from_slice_i64, from_slice_i32, from_slice_calls, + from_slice_step, from_slice_any, +) +from testutil import assertRaises + +small_indices = [-100, -8, -7, -1, 0, 1, 3, 6, 7, 8, 100] +for f, large_indices in [ + (from_slice, [-2**100, -2**63, 2**63, 2**100]), + (from_slice_i64, [-2**63, 2**63 - 1]), + (from_slice_i32, [-2**31, 2**31 - 1]), +]: + for b in [b'', b'abcdefg', b'\x00\xff\x80\x00abc']: + for start in small_indices + large_indices: + for end in small_indices + large_indices: + result = f(b, start, end) + assert type(result) is bytearray + assert result == bytearray(b[start:end]), (b, start, end, result) + assert result is not f(b, start, end) + +b = b'abcdefg' +result = from_slice(b, 1, 4) +result[0] = 0 +assert b == b'abcdefg' +assert from_slice(b, 1, 4) == b'bcd' + +# A bytes subclass can override slicing, including returning a different type. +class SubBytes(bytes): + def __getitem__(self, key): + assert key == slice(start, end) + return [0, 255, 128] + +for start, end in [(1, 4), (-5, -1), (0, 2**100)]: + assert from_slice(SubBytes(b), start, end) == bytearray([0, 255, 128]) + +class BadSlice(bytes): + def __getitem__(self, key): + raise ValueError('slice failed') + +with assertRaises(ValueError, 'slice failed'): + from_slice(BadSlice(b), 1, 4) + +class BadResult(bytes): + def __getitem__(self, key): + return object() + +with assertRaises(TypeError): + from_slice(BadResult(b), 1, 4) + +# Evaluate the source and both bounds exactly once, in order, on either path. +def argument(name, value, fail=False): + def call(): + calls.append(name) + if fail: + raise ValueError(name) + return value + return call + +for start, end in [(1, 4), (-5, 2**100)]: + calls = [] + assert from_slice_calls( + argument('source', b), argument('start', start), argument('end', end) + ) == bytearray(b[start:end]) + assert calls == ['source', 'start', 'end'] + +for fail_at in range(3): + calls = [] + names = ['source', 'start', 'end'] + args = [argument(name, value, i == fail_at) + for i, (name, value) in enumerate(zip(names, [b, 1, 4]))] + with assertRaises(ValueError, names[fail_at]): + from_slice_calls(*args) + assert calls == names[:fail_at + 1] + +# Unsupported forms must retain the ordinary constructor and slicing behavior. +for step in [-2, -1, 1, 2]: + assert from_slice_step(b, 1, 6, step) == bytearray(b[1:6:step]) +with assertRaises(ValueError): + from_slice_step(b, 1, 6, 0) + +class Index: + def __index__(self): + return 2 + +assert from_slice_any(b, Index(), 5) == b'cde' +assert from_slice_any(b, None, None) == b +with assertRaises(TypeError): + from_slice_any(b, 'bad', 5) + [case testBytearrayBasics] from typing import Any From 47a9932b89916e891f149f61d9779b7c7a14b890 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Fri, 28 Aug 2026 13:30:26 -0700 Subject: [PATCH 2/2] address --- mypyc/irbuild/specialize.py | 18 ++++--- mypyc/lib-rt/CPy.h | 1 + mypyc/lib-rt/bytearray_extra_ops.c | 11 ++-- mypyc/lib-rt/bytearray_extra_ops.h | 1 + mypyc/lib-rt/generic_ops.c | 6 ++- mypyc/primitives/bytearray_ops.py | 1 + mypyc/test-data/irbuild-bytes.test | 34 +++++++++++++ mypyc/test-data/run-bytes.test | 80 ++++++++++++++++++++++++++---- 8 files changed, 131 insertions(+), 21 deletions(-) diff --git a/mypyc/irbuild/specialize.py b/mypyc/irbuild/specialize.py index 1af1f85de8cd3..b2ff57847d6ae 100644 --- a/mypyc/irbuild/specialize.py +++ b/mypyc/irbuild/specialize.py @@ -42,6 +42,7 @@ Call, Extend, Integer, + LoadErrorValue, PrimitiveDescription, RaiseStandardError, Register, @@ -365,16 +366,21 @@ def translate_bytearray_from_bytes_slice( if ( not isinstance(index, SliceExpr) or index.stride is not None - or index.begin_index is None - or index.end_index is None - or not is_any_int(builder.node_type(index.begin_index)) - or not is_any_int(builder.node_type(index.end_index)) + or (index.begin_index is not None and not is_any_int(builder.node_type(index.begin_index))) + or (index.end_index is not None and not is_any_int(builder.node_type(index.end_index))) ): return None obj = builder.accept(arg.base) - start = builder.accept(index.begin_index) - end = builder.accept(index.end_index) + # Use the default-argument sentinel so subclass slicing still receives None. + if index.begin_index is None: + start = builder.add(LoadErrorValue(int_rprimitive, is_borrowed=True)) + else: + start = builder.accept(index.begin_index) + if index.end_index is None: + end = builder.add(LoadErrorValue(int_rprimitive, is_borrowed=True)) + else: + end = builder.accept(index.end_index) return builder.primitive_op(bytearray_from_bytes_slice_op, [obj, start, end], expr.line) diff --git a/mypyc/lib-rt/CPy.h b/mypyc/lib-rt/CPy.h index 7e8ea1d71716b..250c24e5c7505 100644 --- a/mypyc/lib-rt/CPy.h +++ b/mypyc/lib-rt/CPy.h @@ -658,6 +658,7 @@ PyObject *CPyObject_GetAttr3(PyObject *v, PyObject *name, PyObject *defl); PyObject *CPyIter_Next(PyObject *iter); PyObject *CPyNumber_Power(PyObject *base, PyObject *index); PyObject *CPyNumber_InPlacePower(PyObject *base, PyObject *index); +// An omitted slice bound is represented by CPY_INT_TAG. PyObject *CPyObject_GetSlice(PyObject *obj, CPyTagged start, CPyTagged end); diff --git a/mypyc/lib-rt/bytearray_extra_ops.c b/mypyc/lib-rt/bytearray_extra_ops.c index 1a9fcddf2947e..511fd6dcca758 100644 --- a/mypyc/lib-rt/bytearray_extra_ops.c +++ b/mypyc/lib-rt/bytearray_extra_ops.c @@ -5,10 +5,13 @@ PyObject *CPyByteArray_New(void) { } PyObject *CPyByteArray_FromBytesSlice(PyObject *obj, CPyTagged start, CPyTagged end) { - if (PyBytes_CheckExact(obj) && CPyTagged_CheckShort(start) && CPyTagged_CheckShort(end)) { - Py_ssize_t startn = CPyTagged_ShortAsSsize_t(start); - Py_ssize_t endn = CPyTagged_ShortAsSsize_t(end); - if (0 <= startn && startn <= endn && endn <= PyBytes_GET_SIZE(obj)) { + if (PyBytes_CheckExact(obj) + && (start == CPY_INT_TAG || CPyTagged_CheckShort(start)) + && (end == CPY_INT_TAG || CPyTagged_CheckShort(end))) { + Py_ssize_t size = PyBytes_GET_SIZE(obj); + Py_ssize_t startn = start == CPY_INT_TAG ? 0 : CPyTagged_ShortAsSsize_t(start); + Py_ssize_t endn = end == CPY_INT_TAG ? size : CPyTagged_ShortAsSsize_t(end); + if (0 <= startn && startn <= endn && endn <= size) { return PyByteArray_FromStringAndSize(PyBytes_AS_STRING(obj) + startn, endn - startn); } } diff --git a/mypyc/lib-rt/bytearray_extra_ops.h b/mypyc/lib-rt/bytearray_extra_ops.h index 8489d6027e458..3fe973d230118 100644 --- a/mypyc/lib-rt/bytearray_extra_ops.h +++ b/mypyc/lib-rt/bytearray_extra_ops.h @@ -8,6 +8,7 @@ PyObject *CPyByteArray_New(void); // Construct a bytearray from a bytes slice, avoiding an intermediate bytes object. +// An omitted bound is represented by CPY_INT_TAG. PyObject *CPyByteArray_FromBytesSlice(PyObject *obj, CPyTagged start, CPyTagged end); #endif diff --git a/mypyc/lib-rt/generic_ops.c b/mypyc/lib-rt/generic_ops.c index 1e1e184bf290b..2c07d8e7ced80 100644 --- a/mypyc/lib-rt/generic_ops.c +++ b/mypyc/lib-rt/generic_ops.c @@ -47,9 +47,11 @@ PyObject *CPyNumber_InPlacePower(PyObject *base, PyObject *index) } PyObject *CPyObject_GetSlice(PyObject *obj, CPyTagged start, CPyTagged end) { - PyObject *start_obj = CPyTagged_AsObject(start); - PyObject *end_obj = CPyTagged_AsObject(end); + PyObject *start_obj = start == CPY_INT_TAG ? Py_NewRef(Py_None) : CPyTagged_AsObject(start); + PyObject *end_obj = end == CPY_INT_TAG ? Py_NewRef(Py_None) : CPyTagged_AsObject(end); if (unlikely(start_obj == NULL || end_obj == NULL)) { + Py_XDECREF(start_obj); + Py_XDECREF(end_obj); return NULL; } PyObject *slice = PySlice_New(start_obj, end_obj, NULL); diff --git a/mypyc/primitives/bytearray_ops.py b/mypyc/primitives/bytearray_ops.py index e601b32e355e8..c02b3ee1c2a7a 100644 --- a/mypyc/primitives/bytearray_ops.py +++ b/mypyc/primitives/bytearray_ops.py @@ -31,6 +31,7 @@ ) # bytearray(bytes[start:end]) +# Omitted bounds use the tagged integer error value. bytearray_from_bytes_slice_op = custom_primitive_op( name="bytearray_from_bytes_slice", arg_types=[bytes_rprimitive, int_rprimitive, int_rprimitive], diff --git a/mypyc/test-data/irbuild-bytes.test b/mypyc/test-data/irbuild-bytes.test index 54be80425c157..9f9d14c291b15 100644 --- a/mypyc/test-data/irbuild-bytes.test +++ b/mypyc/test-data/irbuild-bytes.test @@ -102,6 +102,15 @@ L0: [case testBytearrayFromBytesSlice] def f(a: bytes, start: int, end: int) -> bytearray: return bytearray(a[start:end]) + +def from_start(a: bytes, start: int) -> bytearray: + return bytearray(a[start:]) + +def to_end(a: bytes, end: int) -> bytearray: + return bytearray(a[:end]) + +def full(a: bytes) -> bytearray: + return bytearray(a[:]) [out] def f(a, start, end): a :: bytes @@ -110,6 +119,31 @@ def f(a, start, end): L0: r0 = CPyByteArray_FromBytesSlice(a, start, end) return r0 +def from_start(a, start): + a :: bytes + start, r0 :: int + r1 :: bytearray +L0: + r0 = :: int + r1 = CPyByteArray_FromBytesSlice(a, start, r0) + return r1 +def to_end(a, end): + a :: bytes + end, r0 :: int + r1 :: bytearray +L0: + r0 = :: int + r1 = CPyByteArray_FromBytesSlice(a, r0, end) + return r1 +def full(a): + a :: bytes + r0, r1 :: int + r2 :: bytearray +L0: + r0 = :: int + r1 = :: int + r2 = CPyByteArray_FromBytesSlice(a, r0, r1) + return r2 [case testBytearrayFromBytesSliceFixedWidth_64bit] from mypy_extensions import i32, i64 diff --git a/mypyc/test-data/run-bytes.test b/mypyc/test-data/run-bytes.test index 5309ec3ac1681..5d754d2562475 100644 --- a/mypyc/test-data/run-bytes.test +++ b/mypyc/test-data/run-bytes.test @@ -299,10 +299,33 @@ def from_slice_step(b: bytes, start: int, end: int, step: int) -> bytearray: def from_slice_any(b: bytes, start: Any, end: Any) -> bytearray: return bytearray(b[start:end]) +def from_start(b: bytes, start: int) -> bytearray: + return bytearray(b[start:]) + +def to_end(b: bytes, end: int) -> bytearray: + return bytearray(b[:end]) + +def from_start_i64(b: bytes, start: i64) -> bytearray: + return bytearray(b[start:]) + +def to_end_i32(b: bytes, end: i32) -> bytearray: + return bytearray(b[:end]) + +def full(b: bytes) -> bytearray: + return bytearray(b[:]) + +def from_start_calls(source: Callable[[], bytes], start: Callable[[], int]) -> bytearray: + return bytearray(source()[start():]) + +def to_end_calls(source: Callable[[], bytes], end: Callable[[], int]) -> bytearray: + return bytearray(source()[:end()]) + [file driver.py] from native import ( from_slice, from_slice_i64, from_slice_i32, from_slice_calls, from_slice_step, from_slice_any, + from_start, to_end, from_start_i64, to_end_i32, full, + from_start_calls, to_end_calls, ) from testutil import assertRaises @@ -320,6 +343,29 @@ for f, large_indices in [ assert result == bytearray(b[start:end]), (b, start, end, result) assert result is not f(b, start, end) +for f, is_start, large_indices in [ + (from_start, True, [-2**100, -2**63, 2**63, 2**100]), + (to_end, False, [-2**100, -2**63, 2**63, 2**100]), + (from_start_i64, True, [-2**63, 2**63 - 1]), + (to_end_i32, False, [-2**31, 2**31 - 1]), +]: + for b in [b'', b'abcdefg', b'\x00\xff\x80\x00abc']: + for n in small_indices + large_indices: + result = f(b, n) + expected = bytearray(b[n:] if is_start else b[:n]) + assert type(result) is bytearray + assert result == expected, (b, n, result) + assert result is not f(b, n) + +for b in [b'', b'abcdefg', b'\x00\xff\x80']: + result = full(b) + assert type(result) is bytearray + assert result == b + assert result is not full(b) + if result: + result[0] = 1 + assert full(b) == b + b = b'abcdefg' result = from_slice(b, 1, 4) result[0] = 0 @@ -327,27 +373,31 @@ assert b == b'abcdefg' assert from_slice(b, 1, 4) == b'bcd' # A bytes subclass can override slicing, including returning a different type. +# Omitted bounds must remain None in a subclass's __getitem__. class SubBytes(bytes): def __getitem__(self, key): - assert key == slice(start, end) + assert key == expected_slice return [0, 255, 128] -for start, end in [(1, 4), (-5, -1), (0, 2**100)]: - assert from_slice(SubBytes(b), start, end) == bytearray([0, 255, 128]) - class BadSlice(bytes): def __getitem__(self, key): raise ValueError('slice failed') -with assertRaises(ValueError, 'slice failed'): - from_slice(BadSlice(b), 1, 4) - class BadResult(bytes): def __getitem__(self, key): return object() -with assertRaises(TypeError): - from_slice(BadResult(b), 1, 4) +cases = [(from_slice, (start, end), slice(start, end)) + for start, end in [(1, 4), (-5, -1), (0, 2**100)]] +cases.append((full, (), slice(None))) +for n in small_indices + [-2**100, 2**100]: + cases.extend([(from_start, (n,), slice(n, None)), (to_end, (n,), slice(None, n))]) +for f, args, expected_slice in cases: + assert f(SubBytes(b), *args) == bytearray([0, 255, 128]) + with assertRaises(ValueError, 'slice failed'): + f(BadSlice(b), *args) + with assertRaises(TypeError): + f(BadResult(b), *args) # Evaluate the source and both bounds exactly once, in order, on either path. def argument(name, value, fail=False): @@ -374,6 +424,18 @@ for fail_at in range(3): from_slice_calls(*args) assert calls == names[:fail_at + 1] +for f, direct in [(from_start_calls, from_start), (to_end_calls, to_end)]: + for n in [2, -2, 2**100]: + for fail in [False, True]: + calls = [] + args = [argument('source', b), argument('bound', n, fail)] + if fail: + with assertRaises(ValueError, 'bound'): + f(*args) + else: + assert f(*args) == direct(b, n) + assert calls == ['source', 'bound'] + # Unsupported forms must retain the ordinary constructor and slicing behavior. for step in [-2, -1, 1, 2]: assert from_slice_step(b, 1, 6, step) == bytearray(b[1:6:step])