// Export for '_bisect' shared extension.
PyAPI_FUNC(int) _Py_convert_optional_to_ssize_t(PyObject *, void *);
+// Convert Python int to Py_ssize_t. Do nothing if the argument is None.
+// Raises ValueError if argument is negative.
+PyAPI_FUNC(int) _Py_convert_optional_to_non_negative_ssize_t(PyObject *, void *);
+
// Same as PyNumber_Index() but can return an instance of a subclass of int.
// Export for 'math' shared extension.
PyAPI_FUNC(PyObject*) _PyNumber_Index(PyObject *o);
a: Py_ssize_t = 12
b: Py_ssize_t(accept={int}) = 34
c: Py_ssize_t(accept={int, NoneType}) = 56
+ d: Py_ssize_t(accept={int}, allow_negative=False) = 78
+ e: Py_ssize_t(accept={int, NoneType}, allow_negative=False) = 90
+ f: Py_ssize_t(accept={int}, allow_negative=True) = -12
+ g: Py_ssize_t(accept={int, NoneType}, allow_negative=True) = -34
/
[clinic start generated code]*/
PyDoc_STRVAR(test_Py_ssize_t_converter__doc__,
-"test_Py_ssize_t_converter($module, a=12, b=34, c=56, /)\n"
+"test_Py_ssize_t_converter($module, a=12, b=34, c=56, d=78, e=90, f=-12,\n"
+" g=-34, /)\n"
"--\n"
"\n");
static PyObject *
test_Py_ssize_t_converter_impl(PyObject *module, Py_ssize_t a, Py_ssize_t b,
- Py_ssize_t c);
+ Py_ssize_t c, Py_ssize_t d, Py_ssize_t e,
+ Py_ssize_t f, Py_ssize_t g);
static PyObject *
test_Py_ssize_t_converter(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
Py_ssize_t a = 12;
Py_ssize_t b = 34;
Py_ssize_t c = 56;
+ Py_ssize_t d = 78;
+ Py_ssize_t e = 90;
+ Py_ssize_t f = -12;
+ Py_ssize_t g = -34;
- if (!_PyArg_CheckPositional("test_Py_ssize_t_converter", nargs, 0, 3)) {
+ if (!_PyArg_CheckPositional("test_Py_ssize_t_converter", nargs, 0, 7)) {
goto exit;
}
if (nargs < 1) {
if (!_Py_convert_optional_to_ssize_t(args[2], &c)) {
goto exit;
}
+ if (nargs < 4) {
+ goto skip_optional;
+ }
+ {
+ Py_ssize_t ival = -1;
+ PyObject *iobj = _PyNumber_Index(args[3]);
+ if (iobj != NULL) {
+ ival = PyLong_AsSsize_t(iobj);
+ Py_DECREF(iobj);
+ }
+ if (ival == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ d = ival;
+ if (d < 0) {
+ PyErr_SetString(PyExc_ValueError,
+ "d cannot be negative");
+ goto exit;
+ }
+ }
+ if (nargs < 5) {
+ goto skip_optional;
+ }
+ if (!_Py_convert_optional_to_non_negative_ssize_t(args[4], &e)) {
+ goto exit;
+ }
+ if (nargs < 6) {
+ goto skip_optional;
+ }
+ {
+ Py_ssize_t ival = -1;
+ PyObject *iobj = _PyNumber_Index(args[5]);
+ if (iobj != NULL) {
+ ival = PyLong_AsSsize_t(iobj);
+ Py_DECREF(iobj);
+ }
+ if (ival == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ f = ival;
+ }
+ if (nargs < 7) {
+ goto skip_optional;
+ }
+ if (!_Py_convert_optional_to_ssize_t(args[6], &g)) {
+ goto exit;
+ }
skip_optional:
- return_value = test_Py_ssize_t_converter_impl(module, a, b, c);
+ return_value = test_Py_ssize_t_converter_impl(module, a, b, c, d, e, f, g);
exit:
return return_value;
static PyObject *
test_Py_ssize_t_converter_impl(PyObject *module, Py_ssize_t a, Py_ssize_t b,
- Py_ssize_t c)
-/*[clinic end generated code: output=48214bc3d01f4dd7 input=3855f184bb3f299d]*/
+ Py_ssize_t c, Py_ssize_t d, Py_ssize_t e,
+ Py_ssize_t f, Py_ssize_t g)
+/*[clinic end generated code: output=4ae0a56a1447fba9 input=a25bac8ecf2890aa]*/
/*[clinic input]
"""
self.expect_failure(block, err, lineno=2)
+ def test_allow_negative_accepted_by_py_ssize_t_converter_only(self):
+ errmsg = re.escape("converter_init() got an unexpected keyword argument 'allow_negative'")
+ unsupported_converters = [converter_name for converter_name in converters.keys()
+ if converter_name != "Py_ssize_t"]
+ for converter in unsupported_converters:
+ with self.subTest(converter=converter):
+ block = f"""
+ module m
+ m.func
+ a: {converter}(allow_negative=True)
+ """
+ with self.assertRaisesRegex((AssertionError, TypeError), errmsg):
+ self.parse_function(block)
class ClinicExternalTest(TestCase):
maxDiff = None
ac_tester.py_ssize_t_converter(PY_SSIZE_T_MAX + 1)
with self.assertRaises(TypeError):
ac_tester.py_ssize_t_converter([])
- self.assertEqual(ac_tester.py_ssize_t_converter(), (12, 34, 56))
- self.assertEqual(ac_tester.py_ssize_t_converter(1, 2, None), (1, 2, 56))
+ with self.assertRaises(ValueError):
+ ac_tester.py_ssize_t_converter(12, 34, 56, -1)
+ with self.assertRaises(ValueError):
+ ac_tester.py_ssize_t_converter(12, 34, 56, 78, -1)
+ self.assertEqual(ac_tester.py_ssize_t_converter(), (12, 34, 56, 78, 90, -12, -34))
+ self.assertEqual(ac_tester.py_ssize_t_converter(1, 2, None, 3, None, 4, None), (1, 2, 56, 3, 90, 4, -34))
def test_slice_index_converter(self):
from _testcapi import PY_SSIZE_T_MIN, PY_SSIZE_T_MAX
a: Py_ssize_t = 12
b: Py_ssize_t(accept={int}) = 34
c: Py_ssize_t(accept={int, NoneType}) = 56
+ d: Py_ssize_t(accept={int}, allow_negative=False) = 78
+ e: Py_ssize_t(accept={int, NoneType}, allow_negative=False) = 90
+ f: Py_ssize_t(accept={int}, allow_negative=False) = -12
+ g: Py_ssize_t(accept={int, NoneType}, py_default="-34", allow_negative=False) = -34
/
[clinic start generated code]*/
static PyObject *
py_ssize_t_converter_impl(PyObject *module, Py_ssize_t a, Py_ssize_t b,
- Py_ssize_t c)
-/*[clinic end generated code: output=ce252143e0ed0372 input=76d0f342e9317a1f]*/
+ Py_ssize_t c, Py_ssize_t d, Py_ssize_t e,
+ Py_ssize_t f, Py_ssize_t g)
+/*[clinic end generated code: output=ecf8e1a4a9abc95e input=7b7fa954780c1cb0]*/
{
- RETURN_PACKED_ARGS(3, PyLong_FromSsize_t, Py_ssize_t, a, b, c);
+ RETURN_PACKED_ARGS(7, PyLong_FromSsize_t, Py_ssize_t, a, b, c, d, e, f, g);
}
}
PyDoc_STRVAR(py_ssize_t_converter__doc__,
-"py_ssize_t_converter($module, a=12, b=34, c=56, /)\n"
+"py_ssize_t_converter($module, a=12, b=34, c=56, d=78, e=90, f=-12,\n"
+" g=-34, /)\n"
"--\n"
"\n");
static PyObject *
py_ssize_t_converter_impl(PyObject *module, Py_ssize_t a, Py_ssize_t b,
- Py_ssize_t c);
+ Py_ssize_t c, Py_ssize_t d, Py_ssize_t e,
+ Py_ssize_t f, Py_ssize_t g);
static PyObject *
py_ssize_t_converter(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
Py_ssize_t a = 12;
Py_ssize_t b = 34;
Py_ssize_t c = 56;
+ Py_ssize_t d = 78;
+ Py_ssize_t e = 90;
+ Py_ssize_t f = -12;
+ Py_ssize_t g = -34;
- if (!_PyArg_CheckPositional("py_ssize_t_converter", nargs, 0, 3)) {
+ if (!_PyArg_CheckPositional("py_ssize_t_converter", nargs, 0, 7)) {
goto exit;
}
if (nargs < 1) {
if (!_Py_convert_optional_to_ssize_t(args[2], &c)) {
goto exit;
}
+ if (nargs < 4) {
+ goto skip_optional;
+ }
+ {
+ Py_ssize_t ival = -1;
+ PyObject *iobj = _PyNumber_Index(args[3]);
+ if (iobj != NULL) {
+ ival = PyLong_AsSsize_t(iobj);
+ Py_DECREF(iobj);
+ }
+ if (ival == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ d = ival;
+ if (d < 0) {
+ PyErr_SetString(PyExc_ValueError,
+ "d cannot be negative");
+ goto exit;
+ }
+ }
+ if (nargs < 5) {
+ goto skip_optional;
+ }
+ if (!_Py_convert_optional_to_non_negative_ssize_t(args[4], &e)) {
+ goto exit;
+ }
+ if (nargs < 6) {
+ goto skip_optional;
+ }
+ {
+ Py_ssize_t ival = -1;
+ PyObject *iobj = _PyNumber_Index(args[5]);
+ if (iobj != NULL) {
+ ival = PyLong_AsSsize_t(iobj);
+ Py_DECREF(iobj);
+ }
+ if (ival == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ f = ival;
+ if (f < 0) {
+ PyErr_SetString(PyExc_ValueError,
+ "f cannot be negative");
+ goto exit;
+ }
+ }
+ if (nargs < 7) {
+ goto skip_optional;
+ }
+ if (!_Py_convert_optional_to_non_negative_ssize_t(args[6], &g)) {
+ goto exit;
+ }
skip_optional:
- return_value = py_ssize_t_converter_impl(module, a, b, c);
+ return_value = py_ssize_t_converter_impl(module, a, b, c, d, e, f, g);
exit:
return return_value;
exit:
return return_value;
}
-/*[clinic end generated code: output=6b04671afdafbecf input=a9049054013a1b77]*/
+/*[clinic end generated code: output=0764e6f8c9d94057 input=a9049054013a1b77]*/
return 1;
}
+int
+_Py_convert_optional_to_non_negative_ssize_t(PyObject *obj, void *result)
+{
+ if (!_Py_convert_optional_to_ssize_t(obj, result)) {
+ return 0;
+ }
+ if (obj != Py_None && *((Py_ssize_t *)result) < 0) {
+ PyErr_SetString(PyExc_ValueError, "argument cannot be negative");
+ return 0;
+ }
+ return 1;
+}
+
/* Helper for mkvalue() to scan the length of a format */
type = 'Py_ssize_t'
c_ignored_default = "0"
- def converter_init(self, *, accept: TypeSet = {int}) -> None:
+ def converter_init(self, *, accept: TypeSet = {int},
+ allow_negative: bool = True) -> None:
+ self.allow_negative = allow_negative
if accept == {int}:
self.format_unit = 'n'
self.default_type = int
elif accept == {int, NoneType}:
- self.converter = '_Py_convert_optional_to_ssize_t'
+ if self.allow_negative:
+ self.converter = '_Py_convert_optional_to_ssize_t'
+ else:
+ self.converter = '_Py_convert_optional_to_non_negative_ssize_t'
else:
fail(f"Py_ssize_t_converter: illegal 'accept' argument {accept!r}")
def use_converter(self) -> None:
- if self.converter == '_Py_convert_optional_to_ssize_t':
- self.add_include('pycore_abstract.h',
- '_Py_convert_optional_to_ssize_t()')
+ if self.converter in {
+ '_Py_convert_optional_to_ssize_t',
+ '_Py_convert_optional_to_non_negative_ssize_t',
+ }:
+ self.add_include('pycore_abstract.h', f'{self.converter}()')
def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None:
+ if self.allow_negative:
+ non_negative_check = ''
+ else:
+ non_negative_check = self.format_code("""
+ if ({paramname} < 0) {{{{
+ PyErr_SetString(PyExc_ValueError,
+ "{paramname} cannot be negative");
+ goto exit;
+ }}}}""",
+ argname=argname,
+ )
if self.format_unit == 'n':
if limited_capi:
PyNumber_Index = 'PyNumber_Index'
if (ival == -1 && PyErr_Occurred()) {{{{
goto exit;
}}}}
- {paramname} = ival;
+ {paramname} = ival;{non_negative_check}
}}}}
""",
argname=argname,
- PyNumber_Index=PyNumber_Index)
+ PyNumber_Index=PyNumber_Index,
+ non_negative_check=non_negative_check,
+ )
if not limited_capi:
return super().parse_arg(argname, displayname, limited_capi=limited_capi)
return self.format_code("""
{paramname} = PyNumber_AsSsize_t({argname}, PyExc_OverflowError);
if ({paramname} == -1 && PyErr_Occurred()) {{{{
goto exit;
- }}}}
+ }}}}{non_negative_check}
}}}}
else {{{{
{bad_argument}
""",
argname=argname,
bad_argument=self.bad_argument(displayname, 'integer or None', limited_capi=limited_capi),
+ non_negative_check=non_negative_check,
)