From 853670804a90c4fc6b4c05bb868cebf7bc034449 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 24 Sep 2026 18:04:59 +0300 Subject: [PATCH 1/4] gh-158102: Add tests for the slice C API Test PySlice_Check(), PySlice_New(), PySlice_GetIndices(), PySlice_Unpack(), PySlice_AdjustIndices() and PySlice_GetIndicesEx() -- both the macro and the deprecated function. Co-Authored-By: Claude Opus 5 (1M context) --- Lib/test/test_capi/test_slice.py | 284 +++++++++++++++++++++++ Modules/Setup.stdlib.in | 2 +- Modules/_testlimitedcapi.c | 3 + Modules/_testlimitedcapi/parts.h | 1 + Modules/_testlimitedcapi/slice.c | 239 +++++++++++++++++++ PCbuild/_testlimitedcapi.vcxproj | 1 + PCbuild/_testlimitedcapi.vcxproj.filters | 1 + 7 files changed, 530 insertions(+), 1 deletion(-) create mode 100644 Lib/test/test_capi/test_slice.py create mode 100644 Modules/_testlimitedcapi/slice.c diff --git a/Lib/test/test_capi/test_slice.py b/Lib/test/test_capi/test_slice.py new file mode 100644 index 000000000000000..b7e505e626fd28b --- /dev/null +++ b/Lib/test/test_capi/test_slice.py @@ -0,0 +1,284 @@ +import sys +import unittest +from test.support import import_helper + +_testlimitedcapi = import_helper.import_module('_testlimitedcapi') + +NULL = None +SIZE_MAX = sys.maxsize +SIZE_MIN = -sys.maxsize - 1 + +VALUES = [None, 0, 1, 2, 3, 5, 7, -1, -2, -3, -5, -7] +STEPS = [None, 1, 2, 3, 5, -1, -2, -3, -5] +LENGTHS = [0, 1, 2, 5, 10] + + +class Index: + def __init__(self, value): + self.value = value + + def __index__(self): + return self.value + + +class BadIndex: + def __index__(self): + raise RuntimeError('bad index') + + +class PopIndex: + # __index__() removes the last item of the list. + def __init__(self, value, seq): + self.value = value + self.seq = seq + + def __index__(self): + self.seq.pop() + return self.value + + +class SliceTest(unittest.TestCase): + + def test_check(self): + # Test PySlice_Check() + check = _testlimitedcapi.slice_check + self.assertTrue(check(slice(1, 7, 2))) + self.assertFalse(check(object())) + + # CRASHES check(NULL) + + def test_new(self): + # Test PySlice_New() + new = _testlimitedcapi.slice_new + self.assertEqual(new(1, 7, 2), slice(1, 7, 2)) + self.assertEqual(new(7, 1, -2), slice(7, 1, -2)) + self.assertEqual(new('a', 'b', 'c'), slice('a', 'b', 'c')) + self.assertEqual(new(NULL, NULL, NULL), slice(None, None, None)) + + def test_getindices(self): + # Test PySlice_GetIndices() + getindices = _testlimitedcapi.slice_getindices + self.assertEqual(getindices(slice(1, 7, 2), 10), (1, 7, 2)) + self.assertEqual(getindices(slice(None), 10), (0, 10, 1)) + self.assertEqual(getindices(slice(None, None, -1), 10), (9, -1, -1)) + self.assertEqual(getindices(slice(-3, -1), 10), (7, 9, 1)) + self.assertEqual(getindices(slice(-1, -3, -1), 10), (9, 7, -1)) + + # It fails without setting an exception for out of bounds indices, + # a zero step and non-integer indices. + self.assertIsNone(getindices(slice(1, 11), 10)) + self.assertIsNone(getindices(slice(10, 1), 10)) + self.assertIsNone(getindices(slice(1, 7, 0), 10)) + self.assertIsNone(getindices(slice(Index(1)), 10)) + self.assertIsNone(getindices(slice('a'), 10)) + self.assertIsNone(getindices(slice(1, 'a'), 10)) + self.assertIsNone(getindices(slice(1, 7, 'a'), 10)) + + # CRASHES getindices(NULL, 10) + # CRASHES getindices(object(), 10) + + def test_unpack(self): + # Test PySlice_Unpack() + unpack = _testlimitedcapi.slice_unpack + self.assertEqual(unpack(slice(1, 7)), (1, 7, 1)) + self.assertEqual(unpack(slice(1, 7, 2)), (1, 7, 2)) + self.assertEqual(unpack(slice(7, 1, -2)), (7, 1, -2)) + self.assertEqual(unpack(slice(None, 7, 2)), (0, 7, 2)) + self.assertEqual(unpack(slice(None, 7, -2)), (SIZE_MAX, 7, -2)) + self.assertEqual(unpack(slice(1, None, 2)), (1, SIZE_MAX, 2)) + self.assertEqual(unpack(slice(1, None, -2)), (1, SIZE_MIN, -2)) + self.assertEqual(unpack(slice(None)), (0, SIZE_MAX, 1)) + self.assertEqual(unpack(slice(None, None, -1)), + (SIZE_MAX, SIZE_MIN, -1)) + # Negative indices are not adjusted. + self.assertEqual(unpack(slice(-3, -1)), (-3, -1, 1)) + self.assertEqual(unpack(slice(Index(1), Index(7), Index(2))), + (1, 7, 2)) + + # Values which do not fit in Py_ssize_t are silently clipped. + self.assertEqual(unpack(slice(1, 2**1000)), (1, SIZE_MAX, 1)) + self.assertEqual(unpack(slice(1, -2**1000)), (1, SIZE_MIN, 1)) + self.assertEqual(unpack(slice(2**1000, 7)), (SIZE_MAX, 7, 1)) + self.assertEqual(unpack(slice(-2**1000, 7)), (SIZE_MIN, 7, 1)) + self.assertEqual(unpack(slice(1, 7, 2**1000)), (1, 7, SIZE_MAX)) + # The step is boosted to -PY_SSIZE_T_MAX, not PY_SSIZE_T_MIN, so + # that negating it is safe. + self.assertEqual(unpack(slice(7, 1, -2**1000)), (7, 1, -SIZE_MAX)) + self.assertEqual(unpack(slice(7, 1, SIZE_MIN)), (7, 1, -SIZE_MAX)) + + with self.assertRaisesRegex(ValueError, 'slice step cannot be zero'): + unpack(slice(1, 1, 0)) + with self.assertRaisesRegex(TypeError, + 'slice indices must be integers'): + unpack(slice('a', 7)) + with self.assertRaisesRegex(TypeError, + 'slice indices must be integers'): + unpack(slice(1, 'a')) + with self.assertRaisesRegex(TypeError, + 'slice indices must be integers'): + unpack(slice(1, 7, 'a')) + with self.assertRaisesRegex(RuntimeError, 'bad index'): + unpack(slice(BadIndex(), 7)) + with self.assertRaisesRegex(RuntimeError, 'bad index'): + unpack(slice(1, BadIndex())) + with self.assertRaisesRegex(RuntimeError, 'bad index'): + unpack(slice(1, 7, BadIndex())) + + # CRASHES unpack(NULL) + # CRASHES unpack(object()) + + def test_adjustindices(self): + # Test PySlice_AdjustIndices() + adjust = _testlimitedcapi.slice_adjustindices + self.assertEqual(adjust(10, 1, 7, 1), (6, 1, 7)) + self.assertEqual(adjust(10, 1, 7, 2), (3, 1, 7)) + self.assertEqual(adjust(10, 7, 1, -1), (6, 7, 1)) + self.assertEqual(adjust(10, 7, 1, -2), (3, 7, 1)) + # An empty slice keeps the adjusted indices. + self.assertEqual(adjust(10, 7, 1, 1), (0, 7, 1)) + self.assertEqual(adjust(10, 1, 7, -1), (0, 1, 7)) + + # Negative indices are added to the length. + self.assertEqual(adjust(10, -9, -3, 1), (6, 1, 7)) + self.assertEqual(adjust(10, -3, -9, -1), (6, 7, 1)) + + # Out of bounds indices are clipped. + self.assertEqual(adjust(10, -100, 100, 1), (10, 0, 10)) + self.assertEqual(adjust(10, 100, -100, -1), (10, 9, -1)) + self.assertEqual(adjust(10, SIZE_MIN, SIZE_MAX, 1), (10, 0, 10)) + self.assertEqual(adjust(10, SIZE_MAX, SIZE_MIN, -1), (10, 9, -1)) + self.assertEqual(adjust(0, 1, 7, 1), (0, 0, 0)) + self.assertEqual(adjust(0, 7, 1, -1), (0, -1, -1)) + + # The returned length is the length of the corresponding range. + for length in LENGTHS: + for start in VALUES[1:]: + for stop in VALUES[1:]: + for step in STEPS[1:]: + with self.subTest(length=length, start=start, + stop=stop, step=step): + slicelength, start2, stop2 = adjust(length, start, + stop, step) + self.assertEqual(slicelength, + len(range(start2, stop2, step))) + + # The step is asserted to be neither zero nor less than + # -PY_SSIZE_T_MAX. + # CRASHES adjust(10, 0, 10, 0) + # CRASHES adjust(10, 0, 10, SIZE_MIN) + + +class GetIndicesExTest(unittest.TestCase): + # PySlice_GetIndicesEx() is a macro using PySlice_Unpack() and + # PySlice_AdjustIndices(). It is also a deprecated function, exported + # for the stable ABI. + getindicesex = staticmethod(_testlimitedcapi.slice_getindicesex) + getindicesex_seq = staticmethod(_testlimitedcapi.slice_getindicesex_seq) + # The macro evaluates the length after calling PySlice_Unpack(), so the + # size of the list after removing an item is used. + resized = (6, 8, 1, 2) + + def test_getindicesex(self): + # Test PySlice_GetIndicesEx() + getindicesex = self.getindicesex + self.assertEqual(getindicesex(slice(1, 7, 2), 10), (1, 7, 2, 3)) + self.assertEqual(getindicesex(slice(7, 1, -2), 10), (7, 1, -2, 3)) + self.assertEqual(getindicesex(slice(Index(1), Index(7), Index(2)), 10), + (1, 7, 2, 3)) + + # The result agrees with slice.indices() and the slice length is + # the length of the corresponding range. + for length in LENGTHS: + for start in VALUES: + for stop in VALUES: + for step in STEPS: + s = slice(start, stop, step) + with self.subTest(slice=s, length=length): + indices = s.indices(length) + self.assertEqual(getindicesex(s, length), + indices + (len(range(*indices)),)) + + # Negative indices are added to the length. + self.assertEqual(getindicesex(slice(-9, -3), 10), (1, 7, 1, 6)) + self.assertEqual(getindicesex(slice(-3, -9, -1), 10), (7, 1, -1, 6)) + + # Out of bounds indices are clipped. + self.assertEqual(getindicesex(slice(-100, 100), 10), (0, 10, 1, 10)) + self.assertEqual(getindicesex(slice(100, -100, -1), 10), + (9, -1, -1, 10)) + self.assertEqual(getindicesex(slice(None), 0), (0, 0, 1, 0)) + + # Indices which do not fit in Py_ssize_t are clipped, not rejected. + # Note that slice.indices() does not clip the step. + self.assertEqual(getindicesex(slice(1, 2**1000), 10), (1, 10, 1, 9)) + self.assertEqual(getindicesex(slice(2**1000, 7), 10), (10, 7, 1, 0)) + self.assertEqual(getindicesex(slice(1, 7, 2**1000), 10), + (1, 7, SIZE_MAX, 1)) + # -PY_SSIZE_T_MAX-1 is replaced with -PY_SSIZE_T_MAX. + self.assertEqual(getindicesex(slice(7, 1, -2**1000), 10), + (7, 1, -SIZE_MAX, 1)) + self.assertEqual(getindicesex(slice(7, 1, SIZE_MIN), 10), + (7, 1, -SIZE_MAX, 1)) + + with self.assertRaisesRegex(ValueError, 'slice step cannot be zero'): + getindicesex(slice(1, 7, 0), 10) + with self.assertRaisesRegex(TypeError, + 'slice indices must be integers'): + getindicesex(slice('a', 7), 10) + with self.assertRaisesRegex(TypeError, + 'slice indices must be integers'): + getindicesex(slice(1, 'a'), 10) + with self.assertRaisesRegex(TypeError, + 'slice indices must be integers'): + getindicesex(slice(1, 7, 'a'), 10) + with self.assertRaisesRegex(RuntimeError, 'bad index'): + getindicesex(slice(BadIndex(), 7), 10) + with self.assertRaisesRegex(RuntimeError, 'bad index'): + getindicesex(slice(1, BadIndex()), 10) + with self.assertRaisesRegex(RuntimeError, 'bad index'): + getindicesex(slice(1, 7, BadIndex()), 10) + + # CRASHES getindicesex(NULL, 10) + # CRASHES getindicesex(object(), 10) + + def test_getindicesex_seq(self): + # The length is the size of a sequence. + getindicesex_seq = self.getindicesex_seq + seq = list(range(10)) + self.assertEqual(getindicesex_seq(slice(-3, -1), seq), (7, 9, 1, 2)) + + # gh-72054: __index__() can resize the sequence. Negative indices + # are adjusted by the length, so the result depends on when it is + # evaluated. + seq = list(range(10)) + self.assertEqual(getindicesex_seq(slice(PopIndex(-3, seq), -1), seq), + self.resized) + self.assertEqual(len(seq), 9, seq) + + seq = list(range(10)) + self.assertEqual(getindicesex_seq(slice(-3, PopIndex(-1, seq)), seq), + self.resized) + self.assertEqual(len(seq), 9, seq) + + seq = list(range(10)) + self.assertEqual( + getindicesex_seq(slice(-3, -1, PopIndex(1, seq)), seq), + self.resized) + self.assertEqual(len(seq), 9, seq) + + # CRASHES getindicesex_seq(slice(None), NULL) + # CRASHES getindicesex_seq(slice(None), object()) + + +class GetIndicesExDeprecatedTest(GetIndicesExTest): + # The deprecated function is equivalent to the macro, except that the + # length is evaluated before the call. + getindicesex = staticmethod(_testlimitedcapi.slice_getindicesex_deprecated) + getindicesex_seq = staticmethod( + _testlimitedcapi.slice_getindicesex_seq_deprecated) + # The size of the list before removing an item is used. + resized = (7, 9, 1, 2) + + +if __name__ == "__main__": + unittest.main() diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in index 696fec1290e5f3e..0ca1d90ac4f30b7 100644 --- a/Modules/Setup.stdlib.in +++ b/Modules/Setup.stdlib.in @@ -174,7 +174,7 @@ @MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c @MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c _testinternalcapi/complex.c _testinternalcapi/interpreter.c _testinternalcapi/tokenizer.c _testinternalcapi/tuple.c _testinternalcapi/typecache.c @MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/modsupport.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c _testcapi/type.c _testcapi/function.c _testcapi/module.c _testcapi/weakref.c _testcapi/marshal.c -@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/capsule.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/slots.c _testlimitedcapi/sys.c _testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c _testlimitedcapi/weakref.c _testlimitedcapi/run.c _testlimitedcapi/type.c _testlimitedcapi/hash.c +@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/capsule.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/slice.c _testlimitedcapi/slots.c _testlimitedcapi/sys.c _testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c _testlimitedcapi/weakref.c _testlimitedcapi/run.c _testlimitedcapi/type.c _testlimitedcapi/hash.c @MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c @MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c diff --git a/Modules/_testlimitedcapi.c b/Modules/_testlimitedcapi.c index b30e32c56c57041..0d290eb5ef5b9e4 100644 --- a/Modules/_testlimitedcapi.c +++ b/Modules/_testlimitedcapi.c @@ -58,6 +58,9 @@ module_exec(PyObject *mod) if (_PyTestLimitedCAPI_Init_Set(mod) < 0) { return -1; } + if (_PyTestLimitedCAPI_Init_Slice(mod) < 0) { + return -1; + } if (_PyTestLimitedCAPI_Init_Slots(mod) < 0) { return -1; } diff --git a/Modules/_testlimitedcapi/parts.h b/Modules/_testlimitedcapi/parts.h index e8a2d82fb94aea4..ab6f76f3d06699e 100644 --- a/Modules/_testlimitedcapi/parts.h +++ b/Modules/_testlimitedcapi/parts.h @@ -40,6 +40,7 @@ int _PyTestLimitedCAPI_Init_List(PyObject *module); int _PyTestLimitedCAPI_Init_Long(PyObject *module); int _PyTestLimitedCAPI_Init_PyOS(PyObject *module); int _PyTestLimitedCAPI_Init_Set(PyObject *module); +int _PyTestLimitedCAPI_Init_Slice(PyObject *module); int _PyTestLimitedCAPI_Init_Slots(PyObject *module); int _PyTestLimitedCAPI_Init_Sys(PyObject *module); int _PyTestLimitedCAPI_Init_ThreadState(PyObject *module); diff --git a/Modules/_testlimitedcapi/slice.c b/Modules/_testlimitedcapi/slice.c new file mode 100644 index 000000000000000..bd4049c73364a51 --- /dev/null +++ b/Modules/_testlimitedcapi/slice.c @@ -0,0 +1,239 @@ +#include "pyconfig.h" // Py_GIL_DISABLED + +// Need limited C API 3.6.1 for PySlice_Unpack() and PySlice_AdjustIndices() +// and for PySlice_GetIndicesEx() implemented as a macro. +#if !defined(Py_GIL_DISABLED) && !defined(Py_LIMITED_API) +# define Py_LIMITED_API 0x03060100 +#endif + +#include "parts.h" +#include "util.h" + + +static PyObject * +slice_check(PyObject *Py_UNUSED(module), PyObject *obj) +{ + NULLABLE(obj); + return PyLong_FromLong(PySlice_Check(obj)); +} + +static PyObject * +slice_new(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *start, *stop, *step; + + if (!PyArg_ParseTuple(args, "OOO", &start, &stop, &step)) { + return NULL; + } + NULLABLE(start); + NULLABLE(stop); + NULLABLE(step); + return PySlice_New(start, stop, step); +} + +/* Returns the (start, stop, step) triple on success. If PySlice_GetIndices() + * fails without setting an exception, returns None. */ +static PyObject * +slice_getindices(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *slice; + Py_ssize_t length; + Py_ssize_t start = UNINITIALIZED_SIZE; + Py_ssize_t stop = UNINITIALIZED_SIZE; + Py_ssize_t step = UNINITIALIZED_SIZE; + + if (!PyArg_ParseTuple(args, "On", &slice, &length)) { + return NULL; + } + NULLABLE(slice); + if (PySlice_GetIndices(slice, length, &start, &stop, &step) < 0) { + if (PyErr_Occurred()) { + return NULL; + } + Py_RETURN_NONE; + } + assert(!PyErr_Occurred()); + assert(start != UNINITIALIZED_SIZE); + assert(stop != UNINITIALIZED_SIZE); + assert(step != UNINITIALIZED_SIZE); + return Py_BuildValue("nnn", start, stop, step); +} + +/* Test PySlice_GetIndicesEx() implemented as a macro using PySlice_Unpack() + * and PySlice_AdjustIndices(). */ +static PyObject * +slice_getindicesex(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *slice; + Py_ssize_t length = UNINITIALIZED_SIZE; + Py_ssize_t start = UNINITIALIZED_SIZE; + Py_ssize_t stop = UNINITIALIZED_SIZE; + Py_ssize_t step = UNINITIALIZED_SIZE; + Py_ssize_t slicelength = UNINITIALIZED_SIZE; + + if (!PyArg_ParseTuple(args, "On", &slice, &length)) { + return NULL; + } + NULLABLE(slice); + if (PySlice_GetIndicesEx(slice, length, + &start, &stop, &step, &slicelength) < 0) { + assert(PyErr_Occurred()); + /* The macro sets the slice length to 0 on error. */ + assert(slicelength == 0); + return NULL; + } + assert(!PyErr_Occurred()); + assert(start != UNINITIALIZED_SIZE); + assert(stop != UNINITIALIZED_SIZE); + assert(step != UNINITIALIZED_SIZE); + assert(slicelength != UNINITIALIZED_SIZE); + return Py_BuildValue("nnnn", start, stop, step, slicelength); +} + +/* Same as slice_getindicesex(), but the length is the size of a sequence. + * The macro evaluates it after calling PySlice_Unpack(), which can execute + * arbitrary Python code and resize the sequence. */ +static PyObject * +slice_getindicesex_seq(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *slice, *seq; + Py_ssize_t start = UNINITIALIZED_SIZE; + Py_ssize_t stop = UNINITIALIZED_SIZE; + Py_ssize_t step = UNINITIALIZED_SIZE; + Py_ssize_t slicelength = UNINITIALIZED_SIZE; + + if (!PyArg_ParseTuple(args, "OO", &slice, &seq)) { + return NULL; + } + NULLABLE(slice); + NULLABLE(seq); + if (PySlice_GetIndicesEx(slice, Py_SIZE(seq), + &start, &stop, &step, &slicelength) < 0) { + assert(PyErr_Occurred()); + assert(slicelength == 0); + return NULL; + } + assert(!PyErr_Occurred()); + return Py_BuildValue("nnnn", start, stop, step, slicelength); +} + +static PyObject * +slice_unpack(PyObject *Py_UNUSED(module), PyObject *slice) +{ + Py_ssize_t start = UNINITIALIZED_SIZE; + Py_ssize_t stop = UNINITIALIZED_SIZE; + Py_ssize_t step = UNINITIALIZED_SIZE; + + NULLABLE(slice); + if (PySlice_Unpack(slice, &start, &stop, &step) < 0) { + assert(PyErr_Occurred()); + return NULL; + } + assert(!PyErr_Occurred()); + assert(start != UNINITIALIZED_SIZE); + assert(stop != UNINITIALIZED_SIZE); + assert(step != UNINITIALIZED_SIZE); + return Py_BuildValue("nnn", start, stop, step); +} + +static PyObject * +slice_adjustindices(PyObject *Py_UNUSED(module), PyObject *args) +{ + Py_ssize_t length, start, stop, step; + + if (!PyArg_ParseTuple(args, "nnnn", &length, &start, &stop, &step)) { + return NULL; + } + Py_ssize_t slicelength = PySlice_AdjustIndices(length, &start, &stop, step); + assert(!PyErr_Occurred()); + return Py_BuildValue("nnn", slicelength, start, stop); +} + +#undef PySlice_GetIndicesEx + +/* Test the deprecated PySlice_GetIndicesEx() function. It is still exported + * for the stable ABI and used if Py_LIMITED_API is older than 3.5.4. */ +static PyObject * +slice_getindicesex_deprecated(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *slice; + Py_ssize_t length; + Py_ssize_t start = UNINITIALIZED_SIZE; + Py_ssize_t stop = UNINITIALIZED_SIZE; + Py_ssize_t step = UNINITIALIZED_SIZE; + Py_ssize_t slicelength = UNINITIALIZED_SIZE; + + if (!PyArg_ParseTuple(args, "On", &slice, &length)) { + return NULL; + } + NULLABLE(slice); +// Ignore deprecation warnings +_Py_COMP_DIAG_PUSH +_Py_COMP_DIAG_IGNORE_DEPR_DECLS + int res = PySlice_GetIndicesEx(slice, length, + &start, &stop, &step, &slicelength); +_Py_COMP_DIAG_POP + if (res < 0) { + assert(PyErr_Occurred()); + return NULL; + } + assert(!PyErr_Occurred()); + assert(start != UNINITIALIZED_SIZE); + assert(stop != UNINITIALIZED_SIZE); + assert(step != UNINITIALIZED_SIZE); + assert(slicelength != UNINITIALIZED_SIZE); + return Py_BuildValue("nnnn", start, stop, step, slicelength); +} + + +/* Same as slice_getindicesex_seq(), but using the deprecated function. + * The length is evaluated before the call. */ +static PyObject * +slice_getindicesex_seq_deprecated(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *slice, *seq; + Py_ssize_t start = UNINITIALIZED_SIZE; + Py_ssize_t stop = UNINITIALIZED_SIZE; + Py_ssize_t step = UNINITIALIZED_SIZE; + Py_ssize_t slicelength = UNINITIALIZED_SIZE; + + if (!PyArg_ParseTuple(args, "OO", &slice, &seq)) { + return NULL; + } + NULLABLE(slice); + NULLABLE(seq); +// Ignore deprecation warnings +_Py_COMP_DIAG_PUSH +_Py_COMP_DIAG_IGNORE_DEPR_DECLS + int res = PySlice_GetIndicesEx(slice, Py_SIZE(seq), + &start, &stop, &step, &slicelength); +_Py_COMP_DIAG_POP + if (res < 0) { + assert(PyErr_Occurred()); + return NULL; + } + assert(!PyErr_Occurred()); + return Py_BuildValue("nnnn", start, stop, step, slicelength); +} + + +static PyMethodDef test_methods[] = { + {"slice_check", slice_check, METH_O}, + {"slice_new", slice_new, METH_VARARGS}, + {"slice_getindices", slice_getindices, METH_VARARGS}, + {"slice_getindicesex", slice_getindicesex, METH_VARARGS}, + {"slice_getindicesex_seq", slice_getindicesex_seq, METH_VARARGS}, + {"slice_getindicesex_deprecated", slice_getindicesex_deprecated, + METH_VARARGS}, + {"slice_getindicesex_seq_deprecated", slice_getindicesex_seq_deprecated, + METH_VARARGS}, + {"slice_unpack", slice_unpack, METH_O}, + {"slice_adjustindices", slice_adjustindices, METH_VARARGS}, + {NULL}, +}; + +int +_PyTestLimitedCAPI_Init_Slice(PyObject *m) +{ + return PyModule_AddFunctions(m, test_methods); +} diff --git a/PCbuild/_testlimitedcapi.vcxproj b/PCbuild/_testlimitedcapi.vcxproj index 4218e0ed3945d27..2995dbb469ed3dd 100644 --- a/PCbuild/_testlimitedcapi.vcxproj +++ b/PCbuild/_testlimitedcapi.vcxproj @@ -110,6 +110,7 @@ + diff --git a/PCbuild/_testlimitedcapi.vcxproj.filters b/PCbuild/_testlimitedcapi.vcxproj.filters index ddef60e599d4853..92f1c5dedbd62e8 100644 --- a/PCbuild/_testlimitedcapi.vcxproj.filters +++ b/PCbuild/_testlimitedcapi.vcxproj.filters @@ -25,6 +25,7 @@ + From 1916314c4a327ba3ec8bb904fb13bb71065f4359 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 25 Sep 2026 12:37:58 +0300 Subject: [PATCH 2/4] Apply batched suggestions from code review Co-authored-by: Victor Stinner --- Lib/test/test_capi/test_slice.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Lib/test/test_capi/test_slice.py b/Lib/test/test_capi/test_slice.py index b7e505e626fd28b..d0b75990c1976c8 100644 --- a/Lib/test/test_capi/test_slice.py +++ b/Lib/test/test_capi/test_slice.py @@ -63,6 +63,8 @@ def test_getindices(self): self.assertEqual(getindices(slice(None, None, -1), 10), (9, -1, -1)) self.assertEqual(getindices(slice(-3, -1), 10), (7, 9, 1)) self.assertEqual(getindices(slice(-1, -3, -1), 10), (9, 7, -1)) + self.assertEqual(getindices(slice(None, None, -2), 0), (-1, -1, -2)) + self.assertEqual(getindices(slice(-3, -5, 1), 0), (-3, -5, 1)) # It fails without setting an exception for out of bounds indices, # a zero step and non-integer indices. @@ -74,6 +76,12 @@ def test_getindices(self): self.assertIsNone(getindices(slice(1, 'a'), 10)) self.assertIsNone(getindices(slice(1, 7, 'a'), 10)) + # negative length + self.assertIsNone(getindices(slice(None), -1)) + self.assertIsNone(getindices(slice(1, 7, 2), -1)) + self.assertEqual(getindices(slice(-10, -5, 1), -1), (-11, -6, 1)) + self.assertEqual(getindices(slice(-5, -10, -2), -1), (-6, -11, -2)) + # CRASHES getindices(NULL, 10) # CRASHES getindices(object(), 10) From 731ae1f2b94eb44845527bb2db42383f8e7e84e3 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 25 Sep 2026 12:53:14 +0300 Subject: [PATCH 3/4] Document the step precondition of PySlice_AdjustIndices() Also remove trailing whitespace added by a suggestion. --- Doc/c-api/slice.rst | 3 +++ Lib/test/test_capi/test_slice.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Doc/c-api/slice.rst b/Doc/c-api/slice.rst index c6d761fe7fd1c96..53b5237428a0cfe 100644 --- a/Doc/c-api/slice.rst +++ b/Doc/c-api/slice.rst @@ -108,6 +108,9 @@ Slice Objects Out of bounds indices are clipped in a manner consistent with the handling of normal slices. + *step* must not be zero and must not be less than ``-PY_SSIZE_T_MAX``, + as guaranteed by :c:func:`PySlice_Unpack`. + Return the length of the slice. Always successful. Doesn't call Python code. diff --git a/Lib/test/test_capi/test_slice.py b/Lib/test/test_capi/test_slice.py index d0b75990c1976c8..8cd53ca6f9b4809 100644 --- a/Lib/test/test_capi/test_slice.py +++ b/Lib/test/test_capi/test_slice.py @@ -64,7 +64,7 @@ def test_getindices(self): self.assertEqual(getindices(slice(-3, -1), 10), (7, 9, 1)) self.assertEqual(getindices(slice(-1, -3, -1), 10), (9, 7, -1)) self.assertEqual(getindices(slice(None, None, -2), 0), (-1, -1, -2)) - self.assertEqual(getindices(slice(-3, -5, 1), 0), (-3, -5, 1)) + self.assertEqual(getindices(slice(-3, -5, 1), 0), (-3, -5, 1)) # It fails without setting an exception for out of bounds indices, # a zero step and non-integer indices. From 764aae98cca1d3c70642b595c30d9e60fd383da2 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 25 Sep 2026 13:04:31 +0300 Subject: [PATCH 4/4] Address code review Rename SIZE_MAX and SIZE_MIN to SSIZE_MAX and SSIZE_MIN, and the PySlice_GetIndicesEx() wrappers to slice_getindicesex_macro() and slice_getindicesex_func(). Test the limited C API also on free threading. Reduce the number of tested combinations. Add tests for zero and negative length, and document that the length must not be negative. --- Doc/c-api/slice.rst | 2 + Lib/test/test_capi/test_slice.py | 85 +++++++++++++++++++------------- Modules/_testlimitedcapi/slice.c | 29 +++++------ 3 files changed, 67 insertions(+), 49 deletions(-) diff --git a/Doc/c-api/slice.rst b/Doc/c-api/slice.rst index 53b5237428a0cfe..ce2b64a8aca8530 100644 --- a/Doc/c-api/slice.rst +++ b/Doc/c-api/slice.rst @@ -53,6 +53,7 @@ Slice Objects length *length*, and store the length of the slice in *slicelength*. Out of bounds indices are clipped in a manner consistent with the handling of normal slices. + *length* must not be negative. Return ``0`` on success and ``-1`` on error with an exception set. @@ -108,6 +109,7 @@ Slice Objects Out of bounds indices are clipped in a manner consistent with the handling of normal slices. + *length* must not be negative. *step* must not be zero and must not be less than ``-PY_SSIZE_T_MAX``, as guaranteed by :c:func:`PySlice_Unpack`. diff --git a/Lib/test/test_capi/test_slice.py b/Lib/test/test_capi/test_slice.py index 8cd53ca6f9b4809..3f41b55a609f48f 100644 --- a/Lib/test/test_capi/test_slice.py +++ b/Lib/test/test_capi/test_slice.py @@ -5,12 +5,12 @@ _testlimitedcapi = import_helper.import_module('_testlimitedcapi') NULL = None -SIZE_MAX = sys.maxsize -SIZE_MIN = -sys.maxsize - 1 +SSIZE_MAX = sys.maxsize +SSIZE_MIN = -sys.maxsize - 1 -VALUES = [None, 0, 1, 2, 3, 5, 7, -1, -2, -3, -5, -7] -STEPS = [None, 1, 2, 3, 5, -1, -2, -3, -5] -LENGTHS = [0, 1, 2, 5, 10] +VALUES = [None, 0, 1, 3, 7, -1, -3, -7] +STEPS = [None, 1, 3, 5, -1, -3, -5] +LENGTHS = [0, 1, 3, 10] class Index: @@ -76,11 +76,11 @@ def test_getindices(self): self.assertIsNone(getindices(slice(1, 'a'), 10)) self.assertIsNone(getindices(slice(1, 7, 'a'), 10)) - # negative length - self.assertIsNone(getindices(slice(None), -1)) - self.assertIsNone(getindices(slice(1, 7, 2), -1)) - self.assertEqual(getindices(slice(-10, -5, 1), -1), (-11, -6, 1)) - self.assertEqual(getindices(slice(-5, -10, -2), -1), (-6, -11, -2)) + # Negative length is not supported, but does not fail. + self.assertIsNone(getindices(slice(None), -3)) + self.assertIsNone(getindices(slice(1, 7, 2), -3)) + self.assertEqual(getindices(slice(-10, -5, 1), -3), (-13, -8, 1)) + self.assertEqual(getindices(slice(-5, -10, -2), -3), (-8, -13, -2)) # CRASHES getindices(NULL, 10) # CRASHES getindices(object(), 10) @@ -92,27 +92,27 @@ def test_unpack(self): self.assertEqual(unpack(slice(1, 7, 2)), (1, 7, 2)) self.assertEqual(unpack(slice(7, 1, -2)), (7, 1, -2)) self.assertEqual(unpack(slice(None, 7, 2)), (0, 7, 2)) - self.assertEqual(unpack(slice(None, 7, -2)), (SIZE_MAX, 7, -2)) - self.assertEqual(unpack(slice(1, None, 2)), (1, SIZE_MAX, 2)) - self.assertEqual(unpack(slice(1, None, -2)), (1, SIZE_MIN, -2)) - self.assertEqual(unpack(slice(None)), (0, SIZE_MAX, 1)) + self.assertEqual(unpack(slice(None, 7, -2)), (SSIZE_MAX, 7, -2)) + self.assertEqual(unpack(slice(1, None, 2)), (1, SSIZE_MAX, 2)) + self.assertEqual(unpack(slice(1, None, -2)), (1, SSIZE_MIN, -2)) + self.assertEqual(unpack(slice(None)), (0, SSIZE_MAX, 1)) self.assertEqual(unpack(slice(None, None, -1)), - (SIZE_MAX, SIZE_MIN, -1)) + (SSIZE_MAX, SSIZE_MIN, -1)) # Negative indices are not adjusted. self.assertEqual(unpack(slice(-3, -1)), (-3, -1, 1)) self.assertEqual(unpack(slice(Index(1), Index(7), Index(2))), (1, 7, 2)) # Values which do not fit in Py_ssize_t are silently clipped. - self.assertEqual(unpack(slice(1, 2**1000)), (1, SIZE_MAX, 1)) - self.assertEqual(unpack(slice(1, -2**1000)), (1, SIZE_MIN, 1)) - self.assertEqual(unpack(slice(2**1000, 7)), (SIZE_MAX, 7, 1)) - self.assertEqual(unpack(slice(-2**1000, 7)), (SIZE_MIN, 7, 1)) - self.assertEqual(unpack(slice(1, 7, 2**1000)), (1, 7, SIZE_MAX)) + self.assertEqual(unpack(slice(1, 2**1000)), (1, SSIZE_MAX, 1)) + self.assertEqual(unpack(slice(1, -2**1000)), (1, SSIZE_MIN, 1)) + self.assertEqual(unpack(slice(2**1000, 7)), (SSIZE_MAX, 7, 1)) + self.assertEqual(unpack(slice(-2**1000, 7)), (SSIZE_MIN, 7, 1)) + self.assertEqual(unpack(slice(1, 7, 2**1000)), (1, 7, SSIZE_MAX)) # The step is boosted to -PY_SSIZE_T_MAX, not PY_SSIZE_T_MIN, so # that negating it is safe. - self.assertEqual(unpack(slice(7, 1, -2**1000)), (7, 1, -SIZE_MAX)) - self.assertEqual(unpack(slice(7, 1, SIZE_MIN)), (7, 1, -SIZE_MAX)) + self.assertEqual(unpack(slice(7, 1, -2**1000)), (7, 1, -SSIZE_MAX)) + self.assertEqual(unpack(slice(7, 1, SSIZE_MIN)), (7, 1, -SSIZE_MAX)) with self.assertRaisesRegex(ValueError, 'slice step cannot be zero'): unpack(slice(1, 1, 0)) @@ -153,8 +153,8 @@ def test_adjustindices(self): # Out of bounds indices are clipped. self.assertEqual(adjust(10, -100, 100, 1), (10, 0, 10)) self.assertEqual(adjust(10, 100, -100, -1), (10, 9, -1)) - self.assertEqual(adjust(10, SIZE_MIN, SIZE_MAX, 1), (10, 0, 10)) - self.assertEqual(adjust(10, SIZE_MAX, SIZE_MIN, -1), (10, 9, -1)) + self.assertEqual(adjust(10, SSIZE_MIN, SSIZE_MAX, 1), (10, 0, 10)) + self.assertEqual(adjust(10, SSIZE_MAX, SSIZE_MIN, -1), (10, 9, -1)) self.assertEqual(adjust(0, 1, 7, 1), (0, 0, 0)) self.assertEqual(adjust(0, 7, 1, -1), (0, -1, -1)) @@ -170,18 +170,24 @@ def test_adjustindices(self): self.assertEqual(slicelength, len(range(start2, stop2, step))) + # Negative length is not supported, but does not fail. + self.assertEqual(adjust(-3, 1, 7, 1), (0, -3, -3)) + self.assertEqual(adjust(-3, 7, 1, -1), (0, -4, -4)) + self.assertEqual(adjust(-3, -10, -5, 1), (0, 0, 0)) + # The step is asserted to be neither zero nor less than # -PY_SSIZE_T_MAX. # CRASHES adjust(10, 0, 10, 0) - # CRASHES adjust(10, 0, 10, SIZE_MIN) + # CRASHES adjust(10, 0, 10, SSIZE_MIN) -class GetIndicesExTest(unittest.TestCase): +class GetIndicesExMacroTest(unittest.TestCase): # PySlice_GetIndicesEx() is a macro using PySlice_Unpack() and # PySlice_AdjustIndices(). It is also a deprecated function, exported # for the stable ABI. - getindicesex = staticmethod(_testlimitedcapi.slice_getindicesex) - getindicesex_seq = staticmethod(_testlimitedcapi.slice_getindicesex_seq) + getindicesex = staticmethod(_testlimitedcapi.slice_getindicesex_macro) + getindicesex_seq = staticmethod( + _testlimitedcapi.slice_getindicesex_seq_macro) # The macro evaluates the length after calling PySlice_Unpack(), so the # size of the list after removing an item is used. resized = (6, 8, 1, 2) @@ -215,18 +221,21 @@ def test_getindicesex(self): self.assertEqual(getindicesex(slice(100, -100, -1), 10), (9, -1, -1, 10)) self.assertEqual(getindicesex(slice(None), 0), (0, 0, 1, 0)) + self.assertEqual(getindicesex(slice(1, 7, 2), 0), (0, 0, 2, 0)) + self.assertEqual(getindicesex(slice(None, None, -1), 0), + (-1, -1, -1, 0)) # Indices which do not fit in Py_ssize_t are clipped, not rejected. # Note that slice.indices() does not clip the step. self.assertEqual(getindicesex(slice(1, 2**1000), 10), (1, 10, 1, 9)) self.assertEqual(getindicesex(slice(2**1000, 7), 10), (10, 7, 1, 0)) self.assertEqual(getindicesex(slice(1, 7, 2**1000), 10), - (1, 7, SIZE_MAX, 1)) + (1, 7, SSIZE_MAX, 1)) # -PY_SSIZE_T_MAX-1 is replaced with -PY_SSIZE_T_MAX. self.assertEqual(getindicesex(slice(7, 1, -2**1000), 10), - (7, 1, -SIZE_MAX, 1)) - self.assertEqual(getindicesex(slice(7, 1, SIZE_MIN), 10), - (7, 1, -SIZE_MAX, 1)) + (7, 1, -SSIZE_MAX, 1)) + self.assertEqual(getindicesex(slice(7, 1, SSIZE_MIN), 10), + (7, 1, -SSIZE_MAX, 1)) with self.assertRaisesRegex(ValueError, 'slice step cannot be zero'): getindicesex(slice(1, 7, 0), 10) @@ -246,6 +255,11 @@ def test_getindicesex(self): with self.assertRaisesRegex(RuntimeError, 'bad index'): getindicesex(slice(1, 7, BadIndex()), 10) + # Negative length is not supported, but does not fail. + self.assertEqual(getindicesex(slice(None), -3), (-3, -3, 1, 0)) + self.assertEqual(getindicesex(slice(1, 7, 2), -3), (-3, -3, 2, 0)) + self.assertEqual(getindicesex(slice(7, 1, -2), -3), (-4, -4, -2, 0)) + # CRASHES getindicesex(NULL, 10) # CRASHES getindicesex(object(), 10) @@ -254,6 +268,7 @@ def test_getindicesex_seq(self): getindicesex_seq = self.getindicesex_seq seq = list(range(10)) self.assertEqual(getindicesex_seq(slice(-3, -1), seq), (7, 9, 1, 2)) + self.assertEqual(getindicesex_seq(slice(-3, -1), []), (0, 0, 1, 0)) # gh-72054: __index__() can resize the sequence. Negative indices # are adjusted by the length, so the result depends on when it is @@ -278,12 +293,12 @@ def test_getindicesex_seq(self): # CRASHES getindicesex_seq(slice(None), object()) -class GetIndicesExDeprecatedTest(GetIndicesExTest): +class GetIndicesExFuncTest(GetIndicesExMacroTest): # The deprecated function is equivalent to the macro, except that the # length is evaluated before the call. - getindicesex = staticmethod(_testlimitedcapi.slice_getindicesex_deprecated) + getindicesex = staticmethod(_testlimitedcapi.slice_getindicesex_func) getindicesex_seq = staticmethod( - _testlimitedcapi.slice_getindicesex_seq_deprecated) + _testlimitedcapi.slice_getindicesex_seq_func) # The size of the list before removing an item is used. resized = (7, 9, 1, 2) diff --git a/Modules/_testlimitedcapi/slice.c b/Modules/_testlimitedcapi/slice.c index bd4049c73364a51..febb3d0de3cfa6c 100644 --- a/Modules/_testlimitedcapi/slice.c +++ b/Modules/_testlimitedcapi/slice.c @@ -1,8 +1,9 @@ #include "pyconfig.h" // Py_GIL_DISABLED - -// Need limited C API 3.6.1 for PySlice_Unpack() and PySlice_AdjustIndices() -// and for PySlice_GetIndicesEx() implemented as a macro. -#if !defined(Py_GIL_DISABLED) && !defined(Py_LIMITED_API) +#ifdef Py_GIL_DISABLED +# define Py_TARGET_ABI3T 0x030f0000 +#else + // Need limited C API 3.6.1 for PySlice_Unpack() and PySlice_AdjustIndices() + // and for PySlice_GetIndicesEx() implemented as a macro. # define Py_LIMITED_API 0x03060100 #endif @@ -62,7 +63,7 @@ slice_getindices(PyObject *Py_UNUSED(module), PyObject *args) /* Test PySlice_GetIndicesEx() implemented as a macro using PySlice_Unpack() * and PySlice_AdjustIndices(). */ static PyObject * -slice_getindicesex(PyObject *Py_UNUSED(module), PyObject *args) +slice_getindicesex_macro(PyObject *Py_UNUSED(module), PyObject *args) { PyObject *slice; Py_ssize_t length = UNINITIALIZED_SIZE; @@ -90,11 +91,11 @@ slice_getindicesex(PyObject *Py_UNUSED(module), PyObject *args) return Py_BuildValue("nnnn", start, stop, step, slicelength); } -/* Same as slice_getindicesex(), but the length is the size of a sequence. +/* Same as slice_getindicesex_macro(), but the length is the size of a sequence. * The macro evaluates it after calling PySlice_Unpack(), which can execute * arbitrary Python code and resize the sequence. */ static PyObject * -slice_getindicesex_seq(PyObject *Py_UNUSED(module), PyObject *args) +slice_getindicesex_seq_macro(PyObject *Py_UNUSED(module), PyObject *args) { PyObject *slice, *seq; Py_ssize_t start = UNINITIALIZED_SIZE; @@ -154,7 +155,7 @@ slice_adjustindices(PyObject *Py_UNUSED(module), PyObject *args) /* Test the deprecated PySlice_GetIndicesEx() function. It is still exported * for the stable ABI and used if Py_LIMITED_API is older than 3.5.4. */ static PyObject * -slice_getindicesex_deprecated(PyObject *Py_UNUSED(module), PyObject *args) +slice_getindicesex_func(PyObject *Py_UNUSED(module), PyObject *args) { PyObject *slice; Py_ssize_t length; @@ -186,10 +187,10 @@ _Py_COMP_DIAG_POP } -/* Same as slice_getindicesex_seq(), but using the deprecated function. +/* Same as slice_getindicesex_seq_macro(), but using the deprecated function. * The length is evaluated before the call. */ static PyObject * -slice_getindicesex_seq_deprecated(PyObject *Py_UNUSED(module), PyObject *args) +slice_getindicesex_seq_func(PyObject *Py_UNUSED(module), PyObject *args) { PyObject *slice, *seq; Py_ssize_t start = UNINITIALIZED_SIZE; @@ -221,11 +222,11 @@ static PyMethodDef test_methods[] = { {"slice_check", slice_check, METH_O}, {"slice_new", slice_new, METH_VARARGS}, {"slice_getindices", slice_getindices, METH_VARARGS}, - {"slice_getindicesex", slice_getindicesex, METH_VARARGS}, - {"slice_getindicesex_seq", slice_getindicesex_seq, METH_VARARGS}, - {"slice_getindicesex_deprecated", slice_getindicesex_deprecated, + {"slice_getindicesex_macro", slice_getindicesex_macro, METH_VARARGS}, + {"slice_getindicesex_seq_macro", slice_getindicesex_seq_macro, METH_VARARGS}, + {"slice_getindicesex_func", slice_getindicesex_func, METH_VARARGS}, - {"slice_getindicesex_seq_deprecated", slice_getindicesex_seq_deprecated, + {"slice_getindicesex_seq_func", slice_getindicesex_seq_func, METH_VARARGS}, {"slice_unpack", slice_unpack, METH_O}, {"slice_adjustindices", slice_adjustindices, METH_VARARGS},