From 3e50341c49bc654b6ed15cf0e7b7b0eec3c3637e Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 12 Jun 2024 10:15:47 +0200 Subject: [PATCH 01/11] gh-120389: Add PyLong_FromInt64() and PyLong_ToInt64() Add new functions to convert C numbers from/to Python int: * PyLong_FromInt32() * PyLong_FromUInt32() * PyLong_FromInt64() * PyLong_FromUInt64() * PyLong_ToInt32() * PyLong_ToUInt32() * PyLong_ToInt64() * PyLong_ToUInt64() --- Doc/c-api/long.rst | 80 +++++++++++++++++ Doc/data/stable_abi.dat | 8 ++ Doc/whatsnew/3.14.rst | 14 +++ Include/longobject.h | 12 +++ Lib/test/test_capi/test_long.py | 89 ++++++++++++------- Lib/test/test_stable_abi_ctypes.py | 8 ++ ...-06-19-17-27-22.gh-issue-120389.GSZeHF.rst | 13 +++ Misc/stable_abi.toml | 16 ++++ Modules/_testcapimodule.c | 6 ++ Modules/_testlimitedcapi/long.c | 54 ++++++++++- Objects/longobject.c | 81 +++++++++++++++++ PC/python3dll.c | 8 ++ 12 files changed, 357 insertions(+), 32 deletions(-) create mode 100644 Misc/NEWS.d/next/C API/2024-06-19-17-27-22.gh-issue-120389.GSZeHF.rst diff --git a/Doc/c-api/long.rst b/Doc/c-api/long.rst index a0e111af5996d7..0a28f4ffa8b83f 100644 --- a/Doc/c-api/long.rst +++ b/Doc/c-api/long.rst @@ -69,12 +69,44 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate. on failure. +.. c:function:: PyObject* PyLong_FromInt32(int32_t value) + + Return a new :c:type:`PyLongObject` object from a signed C + :c:expr:`int32_t`, or ``NULL`` on failure. + + .. versionadded:: 3.14 + + +.. c:function:: PyObject* PyLong_FromInt64(int64_t value) + + Return a new :c:type:`PyLongObject` object from a signed C + :c:expr:`int64_t`, or ``NULL`` on failure. + + .. versionadded:: 3.14 + + .. c:function:: PyObject* PyLong_FromUnsignedLongLong(unsigned long long v) Return a new :c:type:`PyLongObject` object from a C :c:expr:`unsigned long long`, or ``NULL`` on failure. +.. c:function:: PyObject* PyLong_FromUInt32(uint32_t value) + + Return a new :c:type:`PyLongObject` object from an unsigned C + :c:expr:`uint32_t`, or ``NULL`` on failure. + + .. versionadded:: 3.14 + + +.. c:function:: PyObject* PyLong_FromUInt64(uint64_t value) + + Return a new :c:type:`PyLongObject` object from an unsigned C + :c:expr:`uint64_t`, or ``NULL`` on failure. + + .. versionadded:: 3.14 + + .. c:function:: PyObject* PyLong_FromDouble(double v) Return a new :c:type:`PyLongObject` object from the integer part of *v*, or @@ -337,6 +369,54 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate. This function will no longer use :meth:`~object.__int__`. +.. c:function:: int PyLong_ToInt32(PyObject *obj, int32_t *value) + + Return a signed C :c:expr:`int32_t` representation of *obj*. + + If the *obj* value is out of range, raise an :exc:`OverflowError`. + + Set *\*value* and return ``0`` on success. + Set an exception and return ``-1`` on error. + + .. versionadded:: 3.14 + + +.. c:function:: int PyLong_ToUInt32(PyObject *obj, uint32_t *value) + + Return an unsigned C :c:expr:`uint32_t` representation of *obj*. + + If the *obj* value is out of range, raise an :exc:`OverflowError`. + + Set *\*value* and return ``0`` on success. + Set an exception and return ``-1`` on error. + + .. versionadded:: 3.14 + + +.. c:function:: int PyLong_ToInt64(PyObject *obj, int64_t *value) + + Return a signed C :c:expr:`int64_t` representation of *obj*. + + If the *obj* value is out of range, raise an :exc:`OverflowError`. + + Set *\*value* and return ``0`` on success. + Set an exception and return ``-1`` on error. + + .. versionadded:: 3.14 + + +.. c:function:: int PyLong_ToUInt64(PyObject *obj, uint64_t *value) + + Return an unsigned C :c:expr:`uint64_t` representation of *obj*. + + If the *obj* value is out of range, raise an :exc:`OverflowError`. + + Set *\*value* and return ``0`` on success. + Set an exception and return ``-1`` on error. + + .. versionadded:: 3.14 + + .. c:function:: double PyLong_AsDouble(PyObject *pylong) Return a C :c:expr:`double` representation of *pylong*. *pylong* must be diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat index c18c813104cf65..4f5635a380d1ec 100644 --- a/Doc/data/stable_abi.dat +++ b/Doc/data/stable_abi.dat @@ -368,15 +368,23 @@ function,PyLong_AsUnsignedLongLongMask,3.2,, function,PyLong_AsUnsignedLongMask,3.2,, function,PyLong_AsVoidPtr,3.2,, function,PyLong_FromDouble,3.2,, +function,PyLong_FromInt32,3.14,, +function,PyLong_FromInt64,3.14,, function,PyLong_FromLong,3.2,, function,PyLong_FromLongLong,3.2,, function,PyLong_FromSize_t,3.2,, function,PyLong_FromSsize_t,3.2,, function,PyLong_FromString,3.2,, +function,PyLong_FromUInt32,3.14,, +function,PyLong_FromUInt64,3.14,, function,PyLong_FromUnsignedLong,3.2,, function,PyLong_FromUnsignedLongLong,3.2,, function,PyLong_FromVoidPtr,3.2,, function,PyLong_GetInfo,3.2,, +function,PyLong_ToInt32,3.14,, +function,PyLong_ToInt64,3.14,, +function,PyLong_ToUInt32,3.14,, +function,PyLong_ToUInt64,3.14,, var,PyLong_Type,3.2,, var,PyMap_Type,3.2,, function,PyMapping_Check,3.2,, diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 804d39ab64646d..14471f6a8cac2c 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -298,6 +298,20 @@ New Features (Contributed by Victor Stinner in :gh:`119182`.) +* Add new functions to convert C ```` numbers from/to Python + :class:`int`: + + * :c:func:`PyLong_FromInt32` + * :c:func:`PyLong_FromUInt32` + * :c:func:`PyLong_FromInt64` + * :c:func:`PyLong_FromUInt64` + * :c:func:`PyLong_ToInt32` + * :c:func:`PyLong_ToUInt32` + * :c:func:`PyLong_ToInt64` + * :c:func:`PyLong_ToUInt64` + + (Contributed by Victor Stinner in :gh:`120389`.) + Porting to Python 3.14 ---------------------- diff --git a/Include/longobject.h b/Include/longobject.h index 19104cd9d1bef9..14b710d939fee6 100644 --- a/Include/longobject.h +++ b/Include/longobject.h @@ -30,6 +30,18 @@ PyAPI_FUNC(unsigned long) PyLong_AsUnsignedLongMask(PyObject *); PyAPI_FUNC(int) PyLong_AsInt(PyObject *); #endif +#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030e0000 +PyAPI_FUNC(PyObject*) PyLong_FromInt32(int32_t value); +PyAPI_FUNC(PyObject*) PyLong_FromUInt32(uint32_t value); +PyAPI_FUNC(PyObject*) PyLong_FromInt64(int64_t value); +PyAPI_FUNC(PyObject*) PyLong_FromUInt64(uint64_t value); + +PyAPI_FUNC(int) PyLong_ToInt32(PyObject *obj, int32_t *value); +PyAPI_FUNC(int) PyLong_ToUInt32(PyObject *obj, uint32_t *value); +PyAPI_FUNC(int) PyLong_ToInt64(PyObject *obj, int64_t *value); +PyAPI_FUNC(int) PyLong_ToUInt64(PyObject *obj, uint64_t *value); +#endif + PyAPI_FUNC(PyObject *) PyLong_GetInfo(void); /* It may be useful in the future. I've added it in the PyInt -> PyLong diff --git a/Lib/test/test_capi/test_long.py b/Lib/test/test_capi/test_long.py index 06a29b5a0505b4..dac799f0adfbd4 100644 --- a/Lib/test/test_capi/test_long.py +++ b/Lib/test/test_capi/test_long.py @@ -185,25 +185,28 @@ def test_long_asint(self): self.assertRaises(TypeError, PyLong_AsInt, '3') self.assertRaises(SystemError, PyLong_AsInt, NULL) + def check_long_asint(self, long_asint, min_val, max_val): + # round trip (object -> C integer -> object) + for value in (min_val, max_val, -1, 0, 1, 1234): + with self.subTest(value=value): + self.assertEqual(long_asint(value), value) + + self.assertEqual(long_asint(IntSubclass(42)), 42) + self.assertEqual(long_asint(Index(42)), 42) + self.assertEqual(long_asint(MyIndexAndInt()), 10) + + self.assertRaises(OverflowError, long_asint, min_val - 1) + self.assertRaises(OverflowError, long_asint, max_val + 1) + self.assertRaises(TypeError, long_asint, 1.0) + self.assertRaises(TypeError, long_asint, b'2') + self.assertRaises(TypeError, long_asint, '3') + self.assertRaises(SystemError, long_asint, NULL) + def test_long_aslong(self): # Test PyLong_AsLong() and PyLong_FromLong() aslong = _testlimitedcapi.pylong_aslong from _testcapi import LONG_MIN, LONG_MAX - # round trip (object -> long -> object) - for value in (LONG_MIN, LONG_MAX, -1, 0, 1, 1234): - with self.subTest(value=value): - self.assertEqual(aslong(value), value) - - self.assertEqual(aslong(IntSubclass(42)), 42) - self.assertEqual(aslong(Index(42)), 42) - self.assertEqual(aslong(MyIndexAndInt()), 10) - - self.assertRaises(OverflowError, aslong, LONG_MIN - 1) - self.assertRaises(OverflowError, aslong, LONG_MAX + 1) - self.assertRaises(TypeError, aslong, 1.0) - self.assertRaises(TypeError, aslong, b'2') - self.assertRaises(TypeError, aslong, '3') - self.assertRaises(SystemError, aslong, NULL) + self.check_long_asint(aslong, LONG_MIN, LONG_MAX) def test_long_aslongandoverflow(self): # Test PyLong_AsLongAndOverflow() @@ -223,25 +226,28 @@ def test_long_aslongandoverflow(self): # CRASHES aslongandoverflow(1.0) # CRASHES aslongandoverflow(NULL) - def test_long_asunsignedlong(self): - # Test PyLong_AsUnsignedLong() and PyLong_FromUnsignedLong() - asunsignedlong = _testlimitedcapi.pylong_asunsignedlong - from _testcapi import ULONG_MAX + def check_long_asunsignedint(self, long_asuint, max_val): # round trip (object -> unsigned long -> object) - for value in (ULONG_MAX, 0, 1, 1234): + for value in (0, 1, 1234, max_val): with self.subTest(value=value): - self.assertEqual(asunsignedlong(value), value) + self.assertEqual(long_asuint(value), value) + + self.assertEqual(long_asuint(IntSubclass(42)), 42) + self.assertRaises(TypeError, long_asuint, Index(42)) + self.assertRaises(TypeError, long_asuint, MyIndexAndInt()) - self.assertEqual(asunsignedlong(IntSubclass(42)), 42) - self.assertRaises(TypeError, asunsignedlong, Index(42)) - self.assertRaises(TypeError, asunsignedlong, MyIndexAndInt()) + self.assertRaises(OverflowError, long_asuint, -1) + self.assertRaises(OverflowError, long_asuint, max_val + 1) + self.assertRaises(TypeError, long_asuint, 1.0) + self.assertRaises(TypeError, long_asuint, b'2') + self.assertRaises(TypeError, long_asuint, '3') + self.assertRaises(SystemError, long_asuint, NULL) - self.assertRaises(OverflowError, asunsignedlong, -1) - self.assertRaises(OverflowError, asunsignedlong, ULONG_MAX + 1) - self.assertRaises(TypeError, asunsignedlong, 1.0) - self.assertRaises(TypeError, asunsignedlong, b'2') - self.assertRaises(TypeError, asunsignedlong, '3') - self.assertRaises(SystemError, asunsignedlong, NULL) + def test_long_asunsignedlong(self): + # Test PyLong_AsUnsignedLong() and PyLong_FromUnsignedLong() + asunsignedlong = _testlimitedcapi.pylong_asunsignedlong + from _testcapi import ULONG_MAX + self.check_long_asunsignedint(asunsignedlong, ULONG_MAX) def test_long_asunsignedlongmask(self): # Test PyLong_AsUnsignedLongMask() @@ -737,6 +743,29 @@ def test_long_getsign(self): # CRASHES getsign(NULL) + def test_long_asint32(self): + # Test PyLong_ToInt32() and PyLong_FromInt32() + to_int32 = _testlimitedcapi.pylong_toint32 + from _testcapi import INT32_MIN, INT32_MAX + self.check_long_asint(to_int32, INT32_MIN, INT32_MAX) + + def test_long_asuint32(self): + # Test PyLong_ToUInt32() and PyLong_FromUInt32() + to_uint32 = _testlimitedcapi.pylong_touint32 + from _testcapi import UINT32_MAX + self.check_long_asunsignedint(to_uint32, UINT32_MAX) + + def test_long_asint64(self): + # Test PyLong_ToInt64() and PyLong_FromInt64() + to_int64 = _testlimitedcapi.pylong_toint64 + from _testcapi import INT64_MIN, INT64_MAX + self.check_long_asint(to_int64, INT64_MIN, INT64_MAX) + + def test_long_asuint64(self): + # Test PyLong_ToUInt64() and PyLong_FromUInt64() + to_uint64 = _testlimitedcapi.pylong_touint64 + from _testcapi import UINT64_MAX + self.check_long_asunsignedint(to_uint64, UINT64_MAX) if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py index 47dff5c28f6ff8..fa10f0cfcedc20 100644 --- a/Lib/test/test_stable_abi_ctypes.py +++ b/Lib/test/test_stable_abi_ctypes.py @@ -403,15 +403,23 @@ def test_windows_feature_macros(self): "PyLong_AsUnsignedLongMask", "PyLong_AsVoidPtr", "PyLong_FromDouble", + "PyLong_FromInt32", + "PyLong_FromInt64", "PyLong_FromLong", "PyLong_FromLongLong", "PyLong_FromSize_t", "PyLong_FromSsize_t", "PyLong_FromString", + "PyLong_FromUInt32", + "PyLong_FromUInt64", "PyLong_FromUnsignedLong", "PyLong_FromUnsignedLongLong", "PyLong_FromVoidPtr", "PyLong_GetInfo", + "PyLong_ToInt32", + "PyLong_ToInt64", + "PyLong_ToUInt32", + "PyLong_ToUInt64", "PyLong_Type", "PyMap_Type", "PyMapping_Check", diff --git a/Misc/NEWS.d/next/C API/2024-06-19-17-27-22.gh-issue-120389.GSZeHF.rst b/Misc/NEWS.d/next/C API/2024-06-19-17-27-22.gh-issue-120389.GSZeHF.rst new file mode 100644 index 00000000000000..a382fa26448ac2 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2024-06-19-17-27-22.gh-issue-120389.GSZeHF.rst @@ -0,0 +1,13 @@ +Add new functions to convert C ```` numbers from/to Python +:class:`int`: + +* :c:func:`PyLong_FromInt32` +* :c:func:`PyLong_FromUInt32` +* :c:func:`PyLong_FromInt64` +* :c:func:`PyLong_FromUInt64` +* :c:func:`PyLong_ToInt32` +* :c:func:`PyLong_ToUInt32` +* :c:func:`PyLong_ToInt64` +* :c:func:`PyLong_ToUInt64` + +Patch by Victor Stinner. diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml index 305978f9f0c5c4..163918631a7e42 100644 --- a/Misc/stable_abi.toml +++ b/Misc/stable_abi.toml @@ -2510,3 +2510,19 @@ [function.Py_TYPE] added = '3.14' +[function.PyLong_FromInt32] + added = '3.14' +[function.PyLong_FromUInt32] + added = '3.14' +[function.PyLong_ToInt32] + added = '3.14' +[function.PyLong_ToUInt32] + added = '3.14' +[function.PyLong_FromInt64] + added = '3.14' +[function.PyLong_FromUInt64] + added = '3.14' +[function.PyLong_ToInt64] + added = '3.14' +[function.PyLong_ToUInt64] + added = '3.14' diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 708f2de6c5b9dd..400d360de8c489 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -4047,6 +4047,12 @@ PyInit__testcapi(void) PyModule_AddIntConstant(m, "the_number_three", 3); PyModule_AddIntMacro(m, Py_C_RECURSION_LIMIT); + PyModule_AddObject(m, "INT32_MIN", PyLong_FromInt32(INT32_MIN)); + PyModule_AddObject(m, "INT32_MAX", PyLong_FromInt32(INT32_MAX)); + PyModule_AddObject(m, "UINT32_MAX", PyLong_FromUInt32(UINT32_MAX)); + PyModule_AddObject(m, "INT64_MIN", PyLong_FromInt64(INT64_MIN)); + PyModule_AddObject(m, "INT64_MAX", PyLong_FromInt64(INT64_MAX)); + PyModule_AddObject(m, "UINT64_MAX", PyLong_FromUInt64(UINT64_MAX)); if (PyModule_AddIntMacro(m, Py_single_input)) { return NULL; diff --git a/Modules/_testlimitedcapi/long.c b/Modules/_testlimitedcapi/long.c index 5953009b6ef9b7..ab9138190c9f53 100644 --- a/Modules/_testlimitedcapi/long.c +++ b/Modules/_testlimitedcapi/long.c @@ -1,7 +1,7 @@ #include "pyconfig.h" // Py_GIL_DISABLED #ifndef Py_GIL_DISABLED - // Need limited C API 3.13 to test PyLong_AsInt() -# define Py_LIMITED_API 0x030d0000 + // Need limited C API 3.14 to test PyLong_AsInt64() +# define Py_LIMITED_API 0x030e0000 #endif #include "parts.h" @@ -758,6 +758,52 @@ pylong_aspid(PyObject *module, PyObject *arg) } +static PyObject * +pylong_toint32(PyObject *module, PyObject *arg) +{ + NULLABLE(arg); + int32_t value; + if (PyLong_ToInt32(arg, &value) < 0) { + return NULL; + } + return PyLong_FromInt32(value); +} + +static PyObject * +pylong_touint32(PyObject *module, PyObject *arg) +{ + NULLABLE(arg); + uint32_t value; + if (PyLong_ToUInt32(arg, &value) < 0) { + return NULL; + } + return PyLong_FromUInt32(value); +} + + +static PyObject * +pylong_toint64(PyObject *module, PyObject *arg) +{ + NULLABLE(arg); + int64_t value; + if (PyLong_ToInt64(arg, &value) < 0) { + return NULL; + } + return PyLong_FromInt64(value); +} + +static PyObject * +pylong_touint64(PyObject *module, PyObject *arg) +{ + NULLABLE(arg); + uint64_t value; + if (PyLong_ToUInt64(arg, &value) < 0) { + return NULL; + } + return PyLong_FromUInt64(value); +} + + static PyMethodDef test_methods[] = { _TESTLIMITEDCAPI_TEST_LONG_AND_OVERFLOW_METHODDEF _TESTLIMITEDCAPI_TEST_LONG_API_METHODDEF @@ -785,6 +831,10 @@ static PyMethodDef test_methods[] = { {"pylong_asdouble", pylong_asdouble, METH_O}, {"pylong_asvoidptr", pylong_asvoidptr, METH_O}, {"pylong_aspid", pylong_aspid, METH_O}, + {"pylong_toint32", pylong_toint32, METH_O}, + {"pylong_touint32", pylong_touint32, METH_O}, + {"pylong_toint64", pylong_toint64, METH_O}, + {"pylong_touint64", pylong_touint64, METH_O}, {NULL}, }; diff --git a/Objects/longobject.c b/Objects/longobject.c index a3a59a20f0bb97..0dce956a2865f5 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -6681,3 +6681,84 @@ Py_ssize_t PyUnstable_Long_CompactValue(const PyLongObject* op) { return _PyLong_CompactValue(op); } + +PyObject* PyLong_FromInt32(int32_t value) +{ return PyLong_FromNativeBytes(&value, sizeof(value), -1); } + +PyObject* PyLong_FromUInt32(uint32_t value) +{ return PyLong_FromUnsignedNativeBytes(&value, sizeof(value), -1); } + +PyObject* PyLong_FromInt64(int64_t value) +{ return PyLong_FromNativeBytes(&value, sizeof(value), -1); } + +PyObject* PyLong_FromUInt64(uint64_t value) +{ return PyLong_FromUnsignedNativeBytes(&value, sizeof(value), -1); } + +int PyLong_ToInt32(PyObject *obj, int32_t *value) +{ +#if SIZEOF_INT == 4 + int res = PyLong_AsInt(obj); +#elif SIZEOF_LONG == 4 + long res = PyLong_AsLong(obj); +#else +# error "unknown int type" +#endif + if (res == -1 && PyErr_Occurred()) { + return -1; + } + *value = res; + return 0; +} + +int PyLong_ToInt64(PyObject *obj, int64_t *value) +{ +#if SIZEOF_LONG == 8 + long res = PyLong_AsLong(obj); +#elif SIZEOF_LONG_LONG == 8 + long long res = PyLong_AsLongLong(obj); +#else +# error "unknown long type" +#endif + if (res == -1 && PyErr_Occurred()) { + return -1; + } + *value = res; + return 0; +} + +int PyLong_ToUInt32(PyObject *obj, uint32_t *value) +{ + Py_BUILD_ASSERT(sizeof(unsigned long) >= sizeof(uint32_t)); + unsigned long res = PyLong_AsUnsignedLong(obj); + if (res == (unsigned long)-1 && PyErr_Occurred()) { + return -1; + } +#if SIZEOF_LONG > 4 + if (res > (unsigned long)UINT32_MAX) { + PyErr_SetString(PyExc_OverflowError, + "Python int too large to convert to C uint32_t"); + return -1; + } +#endif + *value = res; + return 0; +} + +int PyLong_ToUInt64(PyObject *obj, uint64_t *value) +{ +#if SIZEOF_LONG == 8 + unsigned long res = PyLong_AsUnsignedLong(obj); + if (res == (unsigned long)-1 && PyErr_Occurred()) { + return -1; + } +#elif SIZEOF_LONG_LONG == 8 + unsigned long long res = PyLong_AsUnsignedLongLong(obj); + if (res == (unsigned long long)-1 && PyErr_Occurred()) { + return -1; + } +#else +# error "unknown long type" +#endif + *value = res; + return 0; +} diff --git a/PC/python3dll.c b/PC/python3dll.c index 0bcf1cc507e1e8..2ae9e49cc35b0e 100755 --- a/PC/python3dll.c +++ b/PC/python3dll.c @@ -353,15 +353,23 @@ EXPORT_FUNC(PyLong_AsUnsignedLongLongMask) EXPORT_FUNC(PyLong_AsUnsignedLongMask) EXPORT_FUNC(PyLong_AsVoidPtr) EXPORT_FUNC(PyLong_FromDouble) +EXPORT_FUNC(PyLong_FromInt32) +EXPORT_FUNC(PyLong_FromInt64) EXPORT_FUNC(PyLong_FromLong) EXPORT_FUNC(PyLong_FromLongLong) EXPORT_FUNC(PyLong_FromSize_t) EXPORT_FUNC(PyLong_FromSsize_t) EXPORT_FUNC(PyLong_FromString) +EXPORT_FUNC(PyLong_FromUInt32) +EXPORT_FUNC(PyLong_FromUInt64) EXPORT_FUNC(PyLong_FromUnsignedLong) EXPORT_FUNC(PyLong_FromUnsignedLongLong) EXPORT_FUNC(PyLong_FromVoidPtr) EXPORT_FUNC(PyLong_GetInfo) +EXPORT_FUNC(PyLong_ToInt32) +EXPORT_FUNC(PyLong_ToInt64) +EXPORT_FUNC(PyLong_ToUInt32) +EXPORT_FUNC(PyLong_ToUInt64) EXPORT_FUNC(PyMapping_Check) EXPORT_FUNC(PyMapping_GetItemString) EXPORT_FUNC(PyMapping_GetOptionalItem) From 7c7b97688ddab7e70c7865925ba351834a0002e2 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 19 Jun 2024 17:59:02 +0200 Subject: [PATCH 02/11] Add uint32_t to Doc/conf.py --- Doc/conf.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Doc/conf.py b/Doc/conf.py index 8a14646801ebac..f23e4a93fd3311 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -140,6 +140,7 @@ ('c:type', 'size_t'), ('c:type', 'ssize_t'), ('c:type', 'time_t'), + ('c:type', 'uint32_t'), ('c:type', 'uint64_t'), ('c:type', 'uintmax_t'), ('c:type', 'uintptr_t'), From c239d18d738e3c0c23278bcde4938e3c70494993 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 20 Jun 2024 09:25:25 +0200 Subject: [PATCH 03/11] Use PyLong_AsNativeBytes() PyLong_ToUInt32() and PyLong_ToUInt64() can now use the __index__() method if the object has the method. --- Doc/c-api/long.rst | 30 ++++++----- Doc/whatsnew/3.14.rst | 4 +- Lib/test/test_capi/test_long.py | 39 +++++++++------ Objects/longobject.c | 89 +++++++++++++-------------------- 4 files changed, 77 insertions(+), 85 deletions(-) diff --git a/Doc/c-api/long.rst b/Doc/c-api/long.rst index 0a28f4ffa8b83f..aa92b55cb8ce00 100644 --- a/Doc/c-api/long.rst +++ b/Doc/c-api/long.rst @@ -381,23 +381,25 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate. .. versionadded:: 3.14 -.. c:function:: int PyLong_ToUInt32(PyObject *obj, uint32_t *value) - - Return an unsigned C :c:expr:`uint32_t` representation of *obj*. - - If the *obj* value is out of range, raise an :exc:`OverflowError`. +.. c:function:: int PyLong_ToInt64(PyObject *obj, int64_t *value) - Set *\*value* and return ``0`` on success. - Set an exception and return ``-1`` on error. + Similar to :c:func:`PyLong_ToInt32`, but return a signed C + :c:expr:`int64_t` representation. .. versionadded:: 3.14 -.. c:function:: int PyLong_ToInt64(PyObject *obj, int64_t *value) +.. c:function:: int PyLong_ToUInt32(PyObject *obj, uint32_t *value) - Return a signed C :c:expr:`int64_t` representation of *obj*. + Return an unsigned C :c:expr:`uint32_t` representation of *obj*. - If the *obj* value is out of range, raise an :exc:`OverflowError`. + If *obj* is not an instance of :c:type:`PyLongObject`, first call its + :meth:`~object.__index__` method (if present) to convert it to a + :c:type:`PyLongObject`. + + * If *obj* is negative, raise a :exc:`ValueError`. + * If the *obj* value is out of :c:expr:`uint32_t` range, raise an + :exc:`OverflowError`. Set *\*value* and return ``0`` on success. Set an exception and return ``-1`` on error. @@ -407,12 +409,8 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate. .. c:function:: int PyLong_ToUInt64(PyObject *obj, uint64_t *value) - Return an unsigned C :c:expr:`uint64_t` representation of *obj*. - - If the *obj* value is out of range, raise an :exc:`OverflowError`. - - Set *\*value* and return ``0`` on success. - Set an exception and return ``-1`` on error. + Similar to :c:func:`PyLong_ToUInt32`, but return an unsigned C + :c:expr:`int64_t` representation. .. versionadded:: 3.14 diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 14471f6a8cac2c..9b274c85e5f03f 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -302,12 +302,12 @@ New Features :class:`int`: * :c:func:`PyLong_FromInt32` - * :c:func:`PyLong_FromUInt32` * :c:func:`PyLong_FromInt64` + * :c:func:`PyLong_FromUInt32` * :c:func:`PyLong_FromUInt64` * :c:func:`PyLong_ToInt32` - * :c:func:`PyLong_ToUInt32` * :c:func:`PyLong_ToInt64` + * :c:func:`PyLong_ToUInt32` * :c:func:`PyLong_ToUInt64` (Contributed by Victor Stinner in :gh:`120389`.) diff --git a/Lib/test/test_capi/test_long.py b/Lib/test/test_capi/test_long.py index dac799f0adfbd4..fbe983d94ed98a 100644 --- a/Lib/test/test_capi/test_long.py +++ b/Lib/test/test_capi/test_long.py @@ -190,9 +190,9 @@ def check_long_asint(self, long_asint, min_val, max_val): for value in (min_val, max_val, -1, 0, 1, 1234): with self.subTest(value=value): self.assertEqual(long_asint(value), value) + self.assertEqual(long_asint(IntSubclass(value)), value) + self.assertEqual(long_asint(Index(value)), value) - self.assertEqual(long_asint(IntSubclass(42)), 42) - self.assertEqual(long_asint(Index(42)), 42) self.assertEqual(long_asint(MyIndexAndInt()), 10) self.assertRaises(OverflowError, long_asint, min_val - 1) @@ -226,17 +226,24 @@ def test_long_aslongandoverflow(self): # CRASHES aslongandoverflow(1.0) # CRASHES aslongandoverflow(NULL) - def check_long_asunsignedint(self, long_asuint, max_val): + def check_long_asunsignedint(self, long_asuint, max_val, + use_index=False, negative_value_error=False): # round trip (object -> unsigned long -> object) for value in (0, 1, 1234, max_val): with self.subTest(value=value): self.assertEqual(long_asuint(value), value) + if use_index: + self.assertEqual(long_asuint(Index(value)), value) self.assertEqual(long_asuint(IntSubclass(42)), 42) - self.assertRaises(TypeError, long_asuint, Index(42)) - self.assertRaises(TypeError, long_asuint, MyIndexAndInt()) - - self.assertRaises(OverflowError, long_asuint, -1) + if not use_index: + self.assertRaises(TypeError, long_asuint, Index(42)) + self.assertRaises(TypeError, long_asuint, MyIndexAndInt()) + + if negative_value_error: + self.assertRaises(ValueError, long_asuint, -1) + else: + self.assertRaises(OverflowError, long_asuint, -1) self.assertRaises(OverflowError, long_asuint, max_val + 1) self.assertRaises(TypeError, long_asuint, 1.0) self.assertRaises(TypeError, long_asuint, b'2') @@ -749,23 +756,27 @@ def test_long_asint32(self): from _testcapi import INT32_MIN, INT32_MAX self.check_long_asint(to_int32, INT32_MIN, INT32_MAX) - def test_long_asuint32(self): - # Test PyLong_ToUInt32() and PyLong_FromUInt32() - to_uint32 = _testlimitedcapi.pylong_touint32 - from _testcapi import UINT32_MAX - self.check_long_asunsignedint(to_uint32, UINT32_MAX) - def test_long_asint64(self): # Test PyLong_ToInt64() and PyLong_FromInt64() to_int64 = _testlimitedcapi.pylong_toint64 from _testcapi import INT64_MIN, INT64_MAX self.check_long_asint(to_int64, INT64_MIN, INT64_MAX) + def test_long_asuint32(self): + # Test PyLong_ToUInt32() and PyLong_FromUInt32() + to_uint32 = _testlimitedcapi.pylong_touint32 + from _testcapi import UINT32_MAX + self.check_long_asunsignedint(to_uint32, UINT32_MAX, + use_index=True, + negative_value_error=True) + def test_long_asuint64(self): # Test PyLong_ToUInt64() and PyLong_FromUInt64() to_uint64 = _testlimitedcapi.pylong_touint64 from _testcapi import UINT64_MAX - self.check_long_asunsignedint(to_uint64, UINT64_MAX) + self.check_long_asunsignedint(to_uint64, UINT64_MAX, + use_index=True, + negative_value_error=True) if __name__ == "__main__": unittest.main() diff --git a/Objects/longobject.c b/Objects/longobject.c index 0dce956a2865f5..9cd5d52ec72d9e 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -6694,71 +6694,54 @@ PyObject* PyLong_FromInt64(int64_t value) PyObject* PyLong_FromUInt64(uint64_t value) { return PyLong_FromUnsignedNativeBytes(&value, sizeof(value), -1); } +#define LONG_TO_INT(obj, value, type_name) \ + do { \ + int flags = Py_ASNATIVEBYTES_NATIVE_ENDIAN; \ + Py_ssize_t bytes = PyLong_AsNativeBytes(obj, value, sizeof(*value), flags); \ + if (bytes < 0) { \ + return -1; \ + } \ + if ((size_t)bytes > sizeof(*value)) { \ + PyErr_SetString(PyExc_OverflowError, \ + "Python int too large to convert to " type_name); \ + return -1; \ + } \ + return 0; \ + } while (0) + int PyLong_ToInt32(PyObject *obj, int32_t *value) { -#if SIZEOF_INT == 4 - int res = PyLong_AsInt(obj); -#elif SIZEOF_LONG == 4 - long res = PyLong_AsLong(obj); -#else -# error "unknown int type" -#endif - if (res == -1 && PyErr_Occurred()) { - return -1; - } - *value = res; - return 0; + LONG_TO_INT(obj, value, "C int32_t"); } int PyLong_ToInt64(PyObject *obj, int64_t *value) { -#if SIZEOF_LONG == 8 - long res = PyLong_AsLong(obj); -#elif SIZEOF_LONG_LONG == 8 - long long res = PyLong_AsLongLong(obj); -#else -# error "unknown long type" -#endif - if (res == -1 && PyErr_Occurred()) { - return -1; - } - *value = res; - return 0; + LONG_TO_INT(obj, value, "C int64_t"); } +#define LONG_TO_UINT(obj, value, type_name) \ + do { \ + int flags = (Py_ASNATIVEBYTES_NATIVE_ENDIAN \ + | Py_ASNATIVEBYTES_UNSIGNED_BUFFER \ + | Py_ASNATIVEBYTES_REJECT_NEGATIVE); \ + Py_ssize_t bytes = PyLong_AsNativeBytes(obj, value, sizeof(*value), flags); \ + if (bytes < 0) { \ + return -1; \ + } \ + if ((size_t)bytes > sizeof(*value)) { \ + PyErr_SetString(PyExc_OverflowError, \ + "Python int too large to convert to " type_name); \ + return -1; \ + } \ + return 0; \ + } while (0) + int PyLong_ToUInt32(PyObject *obj, uint32_t *value) { - Py_BUILD_ASSERT(sizeof(unsigned long) >= sizeof(uint32_t)); - unsigned long res = PyLong_AsUnsignedLong(obj); - if (res == (unsigned long)-1 && PyErr_Occurred()) { - return -1; - } -#if SIZEOF_LONG > 4 - if (res > (unsigned long)UINT32_MAX) { - PyErr_SetString(PyExc_OverflowError, - "Python int too large to convert to C uint32_t"); - return -1; - } -#endif - *value = res; - return 0; + LONG_TO_UINT(obj, value, "C uint32_t"); } int PyLong_ToUInt64(PyObject *obj, uint64_t *value) { -#if SIZEOF_LONG == 8 - unsigned long res = PyLong_AsUnsignedLong(obj); - if (res == (unsigned long)-1 && PyErr_Occurred()) { - return -1; - } -#elif SIZEOF_LONG_LONG == 8 - unsigned long long res = PyLong_AsUnsignedLongLong(obj); - if (res == (unsigned long long)-1 && PyErr_Occurred()) { - return -1; - } -#else -# error "unknown long type" -#endif - *value = res; - return 0; + LONG_TO_UINT(obj, value, "C uint64_t"); } From 118604366c2698c943e7e0709cffafa1094fd224 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 20 Jun 2024 20:51:15 +0200 Subject: [PATCH 04/11] Group documentations --- Doc/c-api/long.rst | 49 +++++++++++----------------------------------- 1 file changed, 11 insertions(+), 38 deletions(-) diff --git a/Doc/c-api/long.rst b/Doc/c-api/long.rst index aa92b55cb8ce00..035c9baac4f14e 100644 --- a/Doc/c-api/long.rst +++ b/Doc/c-api/long.rst @@ -70,17 +70,10 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate. .. c:function:: PyObject* PyLong_FromInt32(int32_t value) + PyObject* PyLong_FromInt64(int64_t value) Return a new :c:type:`PyLongObject` object from a signed C - :c:expr:`int32_t`, or ``NULL`` on failure. - - .. versionadded:: 3.14 - - -.. c:function:: PyObject* PyLong_FromInt64(int64_t value) - - Return a new :c:type:`PyLongObject` object from a signed C - :c:expr:`int64_t`, or ``NULL`` on failure. + :c:expr:`int32_t` or :c:expr:`int64_t`, or ``NULL`` on failure. .. versionadded:: 3.14 @@ -92,17 +85,10 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate. .. c:function:: PyObject* PyLong_FromUInt32(uint32_t value) + PyObject* PyLong_FromUInt64(uint64_t value) Return a new :c:type:`PyLongObject` object from an unsigned C - :c:expr:`uint32_t`, or ``NULL`` on failure. - - .. versionadded:: 3.14 - - -.. c:function:: PyObject* PyLong_FromUInt64(uint64_t value) - - Return a new :c:type:`PyLongObject` object from an unsigned C - :c:expr:`uint64_t`, or ``NULL`` on failure. + :c:expr:`uint32_t` or :c:expr:`uint64_t`, or ``NULL`` on failure. .. versionadded:: 3.14 @@ -370,8 +356,10 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate. .. c:function:: int PyLong_ToInt32(PyObject *obj, int32_t *value) + int PyLong_ToInt64(PyObject *obj, int64_t *value) - Return a signed C :c:expr:`int32_t` representation of *obj*. + Return a signed C :c:expr:`int32_t` or :c:expr:`int64_t` representation of + *obj*. If the *obj* value is out of range, raise an :exc:`OverflowError`. @@ -381,25 +369,18 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate. .. versionadded:: 3.14 -.. c:function:: int PyLong_ToInt64(PyObject *obj, int64_t *value) - - Similar to :c:func:`PyLong_ToInt32`, but return a signed C - :c:expr:`int64_t` representation. - - .. versionadded:: 3.14 - - .. c:function:: int PyLong_ToUInt32(PyObject *obj, uint32_t *value) + int PyLong_ToUInt64(PyObject *obj, uint64_t *value) - Return an unsigned C :c:expr:`uint32_t` representation of *obj*. + Return an unsigned C :c:expr:`uint32_t` or :c:expr:`uint64_t` representation + of *obj*. If *obj* is not an instance of :c:type:`PyLongObject`, first call its :meth:`~object.__index__` method (if present) to convert it to a :c:type:`PyLongObject`. * If *obj* is negative, raise a :exc:`ValueError`. - * If the *obj* value is out of :c:expr:`uint32_t` range, raise an - :exc:`OverflowError`. + * If the *obj* value is out of range, raise an :exc:`OverflowError`. Set *\*value* and return ``0`` on success. Set an exception and return ``-1`` on error. @@ -407,14 +388,6 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate. .. versionadded:: 3.14 -.. c:function:: int PyLong_ToUInt64(PyObject *obj, uint64_t *value) - - Similar to :c:func:`PyLong_ToUInt32`, but return an unsigned C - :c:expr:`int64_t` representation. - - .. versionadded:: 3.14 - - .. c:function:: double PyLong_AsDouble(PyObject *pylong) Return a C :c:expr:`double` representation of *pylong*. *pylong* must be From c2150c1397bb16f5dc47e2d09f8d4fc38cd02873 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 27 Aug 2024 14:45:14 +0200 Subject: [PATCH 05/11] Replace "To" with "As" --- Doc/c-api/long.rst | 8 +++---- Doc/whatsnew/3.14.rst | 8 +++---- Include/longobject.h | 8 +++---- Lib/test/test_capi/test_long.py | 22 ++++++++--------- ...-06-19-17-27-22.gh-issue-120389.GSZeHF.rst | 8 +++---- Modules/_testlimitedcapi/long.c | 24 +++++++++---------- Objects/longobject.c | 8 +++---- 7 files changed, 43 insertions(+), 43 deletions(-) diff --git a/Doc/c-api/long.rst b/Doc/c-api/long.rst index 035c9baac4f14e..804af51c927ca9 100644 --- a/Doc/c-api/long.rst +++ b/Doc/c-api/long.rst @@ -355,8 +355,8 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate. This function will no longer use :meth:`~object.__int__`. -.. c:function:: int PyLong_ToInt32(PyObject *obj, int32_t *value) - int PyLong_ToInt64(PyObject *obj, int64_t *value) +.. c:function:: int PyLong_AsInt32(PyObject *obj, int32_t *value) + int PyLong_AsInt64(PyObject *obj, int64_t *value) Return a signed C :c:expr:`int32_t` or :c:expr:`int64_t` representation of *obj*. @@ -369,8 +369,8 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate. .. versionadded:: 3.14 -.. c:function:: int PyLong_ToUInt32(PyObject *obj, uint32_t *value) - int PyLong_ToUInt64(PyObject *obj, uint64_t *value) +.. c:function:: int PyLong_AsUInt32(PyObject *obj, uint32_t *value) + int PyLong_AsUInt64(PyObject *obj, uint64_t *value) Return an unsigned C :c:expr:`uint32_t` or :c:expr:`uint64_t` representation of *obj*. diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 9b274c85e5f03f..d650355f7fa367 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -305,10 +305,10 @@ New Features * :c:func:`PyLong_FromInt64` * :c:func:`PyLong_FromUInt32` * :c:func:`PyLong_FromUInt64` - * :c:func:`PyLong_ToInt32` - * :c:func:`PyLong_ToInt64` - * :c:func:`PyLong_ToUInt32` - * :c:func:`PyLong_ToUInt64` + * :c:func:`PyLong_AsInt32` + * :c:func:`PyLong_AsInt64` + * :c:func:`PyLong_AsUInt32` + * :c:func:`PyLong_AsUInt64` (Contributed by Victor Stinner in :gh:`120389`.) diff --git a/Include/longobject.h b/Include/longobject.h index 14b710d939fee6..45c0d218c13f2f 100644 --- a/Include/longobject.h +++ b/Include/longobject.h @@ -36,10 +36,10 @@ PyAPI_FUNC(PyObject*) PyLong_FromUInt32(uint32_t value); PyAPI_FUNC(PyObject*) PyLong_FromInt64(int64_t value); PyAPI_FUNC(PyObject*) PyLong_FromUInt64(uint64_t value); -PyAPI_FUNC(int) PyLong_ToInt32(PyObject *obj, int32_t *value); -PyAPI_FUNC(int) PyLong_ToUInt32(PyObject *obj, uint32_t *value); -PyAPI_FUNC(int) PyLong_ToInt64(PyObject *obj, int64_t *value); -PyAPI_FUNC(int) PyLong_ToUInt64(PyObject *obj, uint64_t *value); +PyAPI_FUNC(int) PyLong_AsInt32(PyObject *obj, int32_t *value); +PyAPI_FUNC(int) PyLong_AsUInt32(PyObject *obj, uint32_t *value); +PyAPI_FUNC(int) PyLong_AsInt64(PyObject *obj, int64_t *value); +PyAPI_FUNC(int) PyLong_AsUInt64(PyObject *obj, uint64_t *value); #endif PyAPI_FUNC(PyObject *) PyLong_GetInfo(void); diff --git a/Lib/test/test_capi/test_long.py b/Lib/test/test_capi/test_long.py index fbe983d94ed98a..6ef8033c21f051 100644 --- a/Lib/test/test_capi/test_long.py +++ b/Lib/test/test_capi/test_long.py @@ -751,30 +751,30 @@ def test_long_getsign(self): # CRASHES getsign(NULL) def test_long_asint32(self): - # Test PyLong_ToInt32() and PyLong_FromInt32() - to_int32 = _testlimitedcapi.pylong_toint32 + # Test PyLong_AsInt32() and PyLong_FromInt32() + to_int32 = _testlimitedcapi.pylong_asint32 from _testcapi import INT32_MIN, INT32_MAX self.check_long_asint(to_int32, INT32_MIN, INT32_MAX) def test_long_asint64(self): - # Test PyLong_ToInt64() and PyLong_FromInt64() - to_int64 = _testlimitedcapi.pylong_toint64 + # Test PyLong_AsInt64() and PyLong_FromInt64() + as_int64 = _testlimitedcapi.pylong_asint64 from _testcapi import INT64_MIN, INT64_MAX - self.check_long_asint(to_int64, INT64_MIN, INT64_MAX) + self.check_long_asint(as_int64, INT64_MIN, INT64_MAX) def test_long_asuint32(self): - # Test PyLong_ToUInt32() and PyLong_FromUInt32() - to_uint32 = _testlimitedcapi.pylong_touint32 + # Test PyLong_AsUInt32() and PyLong_FromUInt32() + as_uint32 = _testlimitedcapi.pylong_asuint32 from _testcapi import UINT32_MAX - self.check_long_asunsignedint(to_uint32, UINT32_MAX, + self.check_long_asunsignedint(as_uint32, UINT32_MAX, use_index=True, negative_value_error=True) def test_long_asuint64(self): - # Test PyLong_ToUInt64() and PyLong_FromUInt64() - to_uint64 = _testlimitedcapi.pylong_touint64 + # Test PyLong_AsUInt64() and PyLong_FromUInt64() + as_uint64 = _testlimitedcapi.pylong_asuint64 from _testcapi import UINT64_MAX - self.check_long_asunsignedint(to_uint64, UINT64_MAX, + self.check_long_asunsignedint(as_uint64, UINT64_MAX, use_index=True, negative_value_error=True) diff --git a/Misc/NEWS.d/next/C API/2024-06-19-17-27-22.gh-issue-120389.GSZeHF.rst b/Misc/NEWS.d/next/C API/2024-06-19-17-27-22.gh-issue-120389.GSZeHF.rst index a382fa26448ac2..094c8b00ac6859 100644 --- a/Misc/NEWS.d/next/C API/2024-06-19-17-27-22.gh-issue-120389.GSZeHF.rst +++ b/Misc/NEWS.d/next/C API/2024-06-19-17-27-22.gh-issue-120389.GSZeHF.rst @@ -5,9 +5,9 @@ Add new functions to convert C ```` numbers from/to Python * :c:func:`PyLong_FromUInt32` * :c:func:`PyLong_FromInt64` * :c:func:`PyLong_FromUInt64` -* :c:func:`PyLong_ToInt32` -* :c:func:`PyLong_ToUInt32` -* :c:func:`PyLong_ToInt64` -* :c:func:`PyLong_ToUInt64` +* :c:func:`PyLong_AsInt32` +* :c:func:`PyLong_AsUInt32` +* :c:func:`PyLong_AsInt64` +* :c:func:`PyLong_AsUInt64` Patch by Victor Stinner. diff --git a/Modules/_testlimitedcapi/long.c b/Modules/_testlimitedcapi/long.c index ab9138190c9f53..b9c35803b423c2 100644 --- a/Modules/_testlimitedcapi/long.c +++ b/Modules/_testlimitedcapi/long.c @@ -759,22 +759,22 @@ pylong_aspid(PyObject *module, PyObject *arg) static PyObject * -pylong_toint32(PyObject *module, PyObject *arg) +pylong_asint32(PyObject *module, PyObject *arg) { NULLABLE(arg); int32_t value; - if (PyLong_ToInt32(arg, &value) < 0) { + if (PyLong_AsInt32(arg, &value) < 0) { return NULL; } return PyLong_FromInt32(value); } static PyObject * -pylong_touint32(PyObject *module, PyObject *arg) +pylong_asuint32(PyObject *module, PyObject *arg) { NULLABLE(arg); uint32_t value; - if (PyLong_ToUInt32(arg, &value) < 0) { + if (PyLong_AsUInt32(arg, &value) < 0) { return NULL; } return PyLong_FromUInt32(value); @@ -782,22 +782,22 @@ pylong_touint32(PyObject *module, PyObject *arg) static PyObject * -pylong_toint64(PyObject *module, PyObject *arg) +pylong_asint64(PyObject *module, PyObject *arg) { NULLABLE(arg); int64_t value; - if (PyLong_ToInt64(arg, &value) < 0) { + if (PyLong_AsInt64(arg, &value) < 0) { return NULL; } return PyLong_FromInt64(value); } static PyObject * -pylong_touint64(PyObject *module, PyObject *arg) +pylong_asuint64(PyObject *module, PyObject *arg) { NULLABLE(arg); uint64_t value; - if (PyLong_ToUInt64(arg, &value) < 0) { + if (PyLong_AsUInt64(arg, &value) < 0) { return NULL; } return PyLong_FromUInt64(value); @@ -831,10 +831,10 @@ static PyMethodDef test_methods[] = { {"pylong_asdouble", pylong_asdouble, METH_O}, {"pylong_asvoidptr", pylong_asvoidptr, METH_O}, {"pylong_aspid", pylong_aspid, METH_O}, - {"pylong_toint32", pylong_toint32, METH_O}, - {"pylong_touint32", pylong_touint32, METH_O}, - {"pylong_toint64", pylong_toint64, METH_O}, - {"pylong_touint64", pylong_touint64, METH_O}, + {"pylong_asint32", pylong_asint32, METH_O}, + {"pylong_asuint32", pylong_asuint32, METH_O}, + {"pylong_asint64", pylong_asint64, METH_O}, + {"pylong_asuint64", pylong_asuint64, METH_O}, {NULL}, }; diff --git a/Objects/longobject.c b/Objects/longobject.c index 9cd5d52ec72d9e..55eedbd4a2c294 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -6709,12 +6709,12 @@ PyObject* PyLong_FromUInt64(uint64_t value) return 0; \ } while (0) -int PyLong_ToInt32(PyObject *obj, int32_t *value) +int PyLong_AsInt32(PyObject *obj, int32_t *value) { LONG_TO_INT(obj, value, "C int32_t"); } -int PyLong_ToInt64(PyObject *obj, int64_t *value) +int PyLong_AsInt64(PyObject *obj, int64_t *value) { LONG_TO_INT(obj, value, "C int64_t"); } @@ -6736,12 +6736,12 @@ int PyLong_ToInt64(PyObject *obj, int64_t *value) return 0; \ } while (0) -int PyLong_ToUInt32(PyObject *obj, uint32_t *value) +int PyLong_AsUInt32(PyObject *obj, uint32_t *value) { LONG_TO_UINT(obj, value, "C uint32_t"); } -int PyLong_ToUInt64(PyObject *obj, uint64_t *value) +int PyLong_AsUInt64(PyObject *obj, uint64_t *value) { LONG_TO_UINT(obj, value, "C uint64_t"); } From 830d250818ea4a7cc2d337d75b46891c7a484af6 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 27 Aug 2024 14:54:18 +0200 Subject: [PATCH 06/11] Use Py_ASNATIVEBYTES_ALLOW_INDEX --- Lib/test/test_capi/test_long.py | 8 ++++---- Objects/longobject.c | 6 ++++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_capi/test_long.py b/Lib/test/test_capi/test_long.py index 2a55c43eb7c4a8..afc16c5e3a2873 100644 --- a/Lib/test/test_capi/test_long.py +++ b/Lib/test/test_capi/test_long.py @@ -659,26 +659,26 @@ def test_long_asint32(self): # Test PyLong_AsInt32() and PyLong_FromInt32() to_int32 = _testlimitedcapi.pylong_asint32 from _testcapi import INT32_MIN, INT32_MAX - self.check_long_asint(to_int32, INT32_MIN, INT32_MAX, use_index=False) + self.check_long_asint(to_int32, INT32_MIN, INT32_MAX) def test_long_asint64(self): # Test PyLong_AsInt64() and PyLong_FromInt64() as_int64 = _testlimitedcapi.pylong_asint64 from _testcapi import INT64_MIN, INT64_MAX - self.check_long_asint(as_int64, INT64_MIN, INT64_MAX, use_index=False) + self.check_long_asint(as_int64, INT64_MIN, INT64_MAX) def test_long_asuint32(self): # Test PyLong_AsUInt32() and PyLong_FromUInt32() as_uint32 = _testlimitedcapi.pylong_asuint32 from _testcapi import UINT32_MAX - self.check_long_asunsignedint(as_uint32, UINT32_MAX, + self.check_long_asunsignedint(as_uint32, UINT32_MAX, use_index=True, negative_value_error=True) def test_long_asuint64(self): # Test PyLong_AsUInt64() and PyLong_FromUInt64() as_uint64 = _testlimitedcapi.pylong_asuint64 from _testcapi import UINT64_MAX - self.check_long_asunsignedint(as_uint64, UINT64_MAX, + self.check_long_asunsignedint(as_uint64, UINT64_MAX, use_index=True, negative_value_error=True) if __name__ == "__main__": diff --git a/Objects/longobject.c b/Objects/longobject.c index a5714a4cbafb22..fde98418920673 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -6743,7 +6743,8 @@ PyObject* PyLong_FromUInt64(uint64_t value) #define LONG_TO_INT(obj, value, type_name) \ do { \ - int flags = Py_ASNATIVEBYTES_NATIVE_ENDIAN; \ + int flags = (Py_ASNATIVEBYTES_NATIVE_ENDIAN \ + | Py_ASNATIVEBYTES_ALLOW_INDEX); \ Py_ssize_t bytes = PyLong_AsNativeBytes(obj, value, sizeof(*value), flags); \ if (bytes < 0) { \ return -1; \ @@ -6770,7 +6771,8 @@ int PyLong_AsInt64(PyObject *obj, int64_t *value) do { \ int flags = (Py_ASNATIVEBYTES_NATIVE_ENDIAN \ | Py_ASNATIVEBYTES_UNSIGNED_BUFFER \ - | Py_ASNATIVEBYTES_REJECT_NEGATIVE); \ + | Py_ASNATIVEBYTES_REJECT_NEGATIVE \ + | Py_ASNATIVEBYTES_ALLOW_INDEX); \ Py_ssize_t bytes = PyLong_AsNativeBytes(obj, value, sizeof(*value), flags); \ if (bytes < 0) { \ return -1; \ From 42b9902ed58a702703e26b19526f96f5d2a420ca Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 27 Aug 2024 14:57:07 +0200 Subject: [PATCH 07/11] stable_abi: replace "To" with "As" --- Doc/data/stable_abi.dat | 8 ++++---- Lib/test/test_stable_abi_ctypes.py | 8 ++++---- Misc/stable_abi.toml | 8 ++++---- PC/python3dll.c | 8 ++++---- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat index 7d679e11e47706..7eeee270bb7f32 100644 --- a/Doc/data/stable_abi.dat +++ b/Doc/data/stable_abi.dat @@ -356,12 +356,16 @@ type,PyLongObject,3.2,,opaque data,PyLongRangeIter_Type,3.2,, func,PyLong_AsDouble,3.2,, func,PyLong_AsInt,3.13,, +func,PyLong_AsInt32,3.14,, +func,PyLong_AsInt64,3.14,, func,PyLong_AsLong,3.2,, func,PyLong_AsLongAndOverflow,3.2,, func,PyLong_AsLongLong,3.2,, func,PyLong_AsLongLongAndOverflow,3.2,, func,PyLong_AsSize_t,3.2,, func,PyLong_AsSsize_t,3.2,, +func,PyLong_AsUInt32,3.14,, +func,PyLong_AsUInt64,3.14,, func,PyLong_AsUnsignedLong,3.2,, func,PyLong_AsUnsignedLongLong,3.2,, func,PyLong_AsUnsignedLongLongMask,3.2,, @@ -381,10 +385,6 @@ func,PyLong_FromUnsignedLong,3.2,, func,PyLong_FromUnsignedLongLong,3.2,, func,PyLong_FromVoidPtr,3.2,, func,PyLong_GetInfo,3.2,, -func,PyLong_ToInt32,3.14,, -func,PyLong_ToInt64,3.14,, -func,PyLong_ToUInt32,3.14,, -func,PyLong_ToUInt64,3.14,, data,PyLong_Type,3.2,, data,PyMap_Type,3.2,, func,PyMapping_Check,3.2,, diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py index f95f4f63462e61..4bca33b7451f80 100644 --- a/Lib/test/test_stable_abi_ctypes.py +++ b/Lib/test/test_stable_abi_ctypes.py @@ -391,12 +391,16 @@ def test_windows_feature_macros(self): "PyLongRangeIter_Type", "PyLong_AsDouble", "PyLong_AsInt", + "PyLong_AsInt32", + "PyLong_AsInt64", "PyLong_AsLong", "PyLong_AsLongAndOverflow", "PyLong_AsLongLong", "PyLong_AsLongLongAndOverflow", "PyLong_AsSize_t", "PyLong_AsSsize_t", + "PyLong_AsUInt32", + "PyLong_AsUInt64", "PyLong_AsUnsignedLong", "PyLong_AsUnsignedLongLong", "PyLong_AsUnsignedLongLongMask", @@ -416,10 +420,6 @@ def test_windows_feature_macros(self): "PyLong_FromUnsignedLongLong", "PyLong_FromVoidPtr", "PyLong_GetInfo", - "PyLong_ToInt32", - "PyLong_ToInt64", - "PyLong_ToUInt32", - "PyLong_ToUInt64", "PyLong_Type", "PyMap_Type", "PyMapping_Check", diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml index 3d062aa9e2f95e..d28b13204e1a3c 100644 --- a/Misc/stable_abi.toml +++ b/Misc/stable_abi.toml @@ -2514,15 +2514,15 @@ added = '3.14' [function.PyLong_FromUInt32] added = '3.14' -[function.PyLong_ToInt32] +[function.PyLong_AsInt32] added = '3.14' -[function.PyLong_ToUInt32] +[function.PyLong_AsUInt32] added = '3.14' [function.PyLong_FromInt64] added = '3.14' [function.PyLong_FromUInt64] added = '3.14' -[function.PyLong_ToInt64] +[function.PyLong_AsInt64] added = '3.14' -[function.PyLong_ToUInt64] +[function.PyLong_AsUInt64] added = '3.14' diff --git a/PC/python3dll.c b/PC/python3dll.c index c2919506fdc217..1845334b244d8c 100755 --- a/PC/python3dll.c +++ b/PC/python3dll.c @@ -342,12 +342,16 @@ EXPORT_FUNC(PyList_Size) EXPORT_FUNC(PyList_Sort) EXPORT_FUNC(PyLong_AsDouble) EXPORT_FUNC(PyLong_AsInt) +EXPORT_FUNC(PyLong_AsInt32) +EXPORT_FUNC(PyLong_AsInt64) EXPORT_FUNC(PyLong_AsLong) EXPORT_FUNC(PyLong_AsLongAndOverflow) EXPORT_FUNC(PyLong_AsLongLong) EXPORT_FUNC(PyLong_AsLongLongAndOverflow) EXPORT_FUNC(PyLong_AsSize_t) EXPORT_FUNC(PyLong_AsSsize_t) +EXPORT_FUNC(PyLong_AsUInt32) +EXPORT_FUNC(PyLong_AsUInt64) EXPORT_FUNC(PyLong_AsUnsignedLong) EXPORT_FUNC(PyLong_AsUnsignedLongLong) EXPORT_FUNC(PyLong_AsUnsignedLongLongMask) @@ -367,10 +371,6 @@ EXPORT_FUNC(PyLong_FromUnsignedLong) EXPORT_FUNC(PyLong_FromUnsignedLongLong) EXPORT_FUNC(PyLong_FromVoidPtr) EXPORT_FUNC(PyLong_GetInfo) -EXPORT_FUNC(PyLong_ToInt32) -EXPORT_FUNC(PyLong_ToInt64) -EXPORT_FUNC(PyLong_ToUInt32) -EXPORT_FUNC(PyLong_ToUInt64) EXPORT_FUNC(PyMapping_Check) EXPORT_FUNC(PyMapping_GetItemString) EXPORT_FUNC(PyMapping_GetOptionalItem) From 0199a80231f0c463a278969d3da7684ec8b8a558 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 27 Aug 2024 15:03:35 +0200 Subject: [PATCH 08/11] =?UTF-8?q?Address=20B=C3=A9n=C3=A9dikt's=20review?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Doc/c-api/long.rst | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Doc/c-api/long.rst b/Doc/c-api/long.rst index be7e86659e1f8b..6283bdd00a3d29 100644 --- a/Doc/c-api/long.rst +++ b/Doc/c-api/long.rst @@ -358,22 +358,24 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate. .. c:function:: int PyLong_AsInt32(PyObject *obj, int32_t *value) int PyLong_AsInt64(PyObject *obj, int64_t *value) - Return a signed C :c:expr:`int32_t` or :c:expr:`int64_t` representation of - *obj*. + Set *\*value* to a signed C :c:expr:`int32_t` or :c:expr:`int64_t` + representation of *obj*. If the *obj* value is out of range, raise an :exc:`OverflowError`. Set *\*value* and return ``0`` on success. Set an exception and return ``-1`` on error. + *value* must not be ``NULL``. + .. versionadded:: 3.14 .. c:function:: int PyLong_AsUInt32(PyObject *obj, uint32_t *value) int PyLong_AsUInt64(PyObject *obj, uint64_t *value) - Return an unsigned C :c:expr:`uint32_t` or :c:expr:`uint64_t` representation - of *obj*. + Set *\*value* to an unsigned C :c:expr:`uint32_t` or :c:expr:`uint64_t` + representation of *obj*. If *obj* is not an instance of :c:type:`PyLongObject`, first call its :meth:`~object.__index__` method (if present) to convert it to a @@ -385,6 +387,8 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate. Set *\*value* and return ``0`` on success. Set an exception and return ``-1`` on error. + *value* must not be ``NULL``. + .. versionadded:: 3.14 From 71748a7cdf29cf693a8d2f09041bc2d8921c7988 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 28 Aug 2024 10:37:02 +0200 Subject: [PATCH 09/11] Remove check_long_asunsignedint() in tests Reuse check_long_asint(). --- Lib/test/test_capi/test_long.py | 32 ++++---------------------------- 1 file changed, 4 insertions(+), 28 deletions(-) diff --git a/Lib/test/test_capi/test_long.py b/Lib/test/test_capi/test_long.py index afc16c5e3a2873..48e795f97b3544 100644 --- a/Lib/test/test_capi/test_long.py +++ b/Lib/test/test_capi/test_long.py @@ -232,30 +232,6 @@ def test_long_aslongandoverflow(self): from _testcapi import LONG_MIN, LONG_MAX self.check_long_asintandoverflow(aslongandoverflow, LONG_MIN, LONG_MAX) - def check_long_asunsignedint(self, long_asuint, max_val, - use_index=False, negative_value_error=False): - # round trip (object -> unsigned long -> object) - for value in (0, 1, 1234, max_val): - with self.subTest(value=value): - self.assertEqual(long_asuint(value), value) - if use_index: - self.assertEqual(long_asuint(Index(value)), value) - - self.assertEqual(long_asuint(IntSubclass(42)), 42) - if not use_index: - self.assertRaises(TypeError, long_asuint, Index(42)) - self.assertRaises(TypeError, long_asuint, MyIndexAndInt()) - - if negative_value_error: - self.assertRaises(ValueError, long_asuint, -1) - else: - self.assertRaises(OverflowError, long_asuint, -1) - self.assertRaises(OverflowError, long_asuint, max_val + 1) - self.assertRaises(TypeError, long_asuint, 1.0) - self.assertRaises(TypeError, long_asuint, b'2') - self.assertRaises(TypeError, long_asuint, '3') - self.assertRaises(SystemError, long_asuint, NULL) - def test_long_asunsignedlong(self): # Test PyLong_AsUnsignedLong() and PyLong_FromUnsignedLong() asunsignedlong = _testlimitedcapi.pylong_asunsignedlong @@ -671,15 +647,15 @@ def test_long_asuint32(self): # Test PyLong_AsUInt32() and PyLong_FromUInt32() as_uint32 = _testlimitedcapi.pylong_asuint32 from _testcapi import UINT32_MAX - self.check_long_asunsignedint(as_uint32, UINT32_MAX, use_index=True, - negative_value_error=True) + self.check_long_asint(as_uint32, 0, UINT32_MAX, + negative_value_error=ValueError) def test_long_asuint64(self): # Test PyLong_AsUInt64() and PyLong_FromUInt64() as_uint64 = _testlimitedcapi.pylong_asuint64 from _testcapi import UINT64_MAX - self.check_long_asunsignedint(as_uint64, UINT64_MAX, use_index=True, - negative_value_error=True) + self.check_long_asint(as_uint64, 0, UINT64_MAX, + negative_value_error=ValueError) if __name__ == "__main__": unittest.main() From dd2194e9df1dcba92ce4ceea3d0e9d314b256148 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 28 Aug 2024 11:51:33 +0200 Subject: [PATCH 10/11] Update Doc/c-api/long.rst Co-authored-by: Petr Viktorin --- Doc/c-api/long.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Doc/c-api/long.rst b/Doc/c-api/long.rst index 6283bdd00a3d29..682aa38d84b070 100644 --- a/Doc/c-api/long.rst +++ b/Doc/c-api/long.rst @@ -73,7 +73,8 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate. PyObject* PyLong_FromInt64(int64_t value) Return a new :c:type:`PyLongObject` object from a signed C - :c:expr:`int32_t` or :c:expr:`int64_t`, or ``NULL`` on failure. + :c:expr:`int32_t` or :c:expr:`int64_t`, or ``NULL`` + with an exception set on failure. .. versionadded:: 3.14 From 7070514226263f3d3f9ace506868b19df50e14ec Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 28 Aug 2024 11:51:42 +0200 Subject: [PATCH 11/11] Update Doc/c-api/long.rst Co-authored-by: Petr Viktorin --- Doc/c-api/long.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Doc/c-api/long.rst b/Doc/c-api/long.rst index 682aa38d84b070..30133a9c5cfa2f 100644 --- a/Doc/c-api/long.rst +++ b/Doc/c-api/long.rst @@ -89,7 +89,8 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate. PyObject* PyLong_FromUInt64(uint64_t value) Return a new :c:type:`PyLongObject` object from an unsigned C - :c:expr:`uint32_t` or :c:expr:`uint64_t`, or ``NULL`` on failure. + :c:expr:`uint32_t` or :c:expr:`uint64_t`, or ``NULL`` + with an exception set on failure. .. versionadded:: 3.14