diff --git a/Doc/c-api/slice.rst b/Doc/c-api/slice.rst
index c6d761fe7fd1c96..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,10 @@ 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`.
+
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
new file mode 100644
index 000000000000000..3f41b55a609f48f
--- /dev/null
+++ b/Lib/test/test_capi/test_slice.py
@@ -0,0 +1,307 @@
+import sys
+import unittest
+from test.support import import_helper
+
+_testlimitedcapi = import_helper.import_module('_testlimitedcapi')
+
+NULL = None
+SSIZE_MAX = sys.maxsize
+SSIZE_MIN = -sys.maxsize - 1
+
+VALUES = [None, 0, 1, 3, 7, -1, -3, -7]
+STEPS = [None, 1, 3, 5, -1, -3, -5]
+LENGTHS = [0, 1, 3, 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))
+ 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.
+ 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))
+
+ # 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)
+
+ 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)), (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)),
+ (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, 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, -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))
+ 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, 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))
+
+ # 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)))
+
+ # 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, SSIZE_MIN)
+
+
+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_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)
+
+ 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))
+ 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, 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, -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)
+ 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)
+
+ # 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)
+
+ 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))
+ 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
+ # 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 GetIndicesExFuncTest(GetIndicesExMacroTest):
+ # The deprecated function is equivalent to the macro, except that the
+ # length is evaluated before the call.
+ getindicesex = staticmethod(_testlimitedcapi.slice_getindicesex_func)
+ getindicesex_seq = staticmethod(
+ _testlimitedcapi.slice_getindicesex_seq_func)
+ # 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..febb3d0de3cfa6c
--- /dev/null
+++ b/Modules/_testlimitedcapi/slice.c
@@ -0,0 +1,240 @@
+#include "pyconfig.h" // Py_GIL_DISABLED
+#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
+
+#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_macro(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_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_macro(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_func(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_macro(), but using the deprecated function.
+ * The length is evaluated before the call. */
+static PyObject *
+slice_getindicesex_seq_func(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_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_func", slice_getindicesex_seq_func,
+ 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 @@
+