diff --git a/Doc/c-api/long.rst b/Doc/c-api/long.rst index f1354a34f2b..045604870d3 100644 --- a/Doc/c-api/long.rst +++ b/Doc/c-api/long.rst @@ -136,6 +136,14 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate. This function will no longer use :meth:`~object.__int__`. +.. c:function:: int PyLong_AsInt(PyObject *obj) + + Similar to :c:func:`PyLong_AsLong`, but store the result in a C + :c:expr:`int` instead of a C :c:expr:`long`. + + .. versionadded:: 3.13 + + .. c:function:: long PyLong_AsLongAndOverflow(PyObject *obj, int *overflow) Return a C :c:expr:`long` representation of *obj*. If *obj* is not an diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat index ed415a4dc64..cc6349a0330 100644 --- a/Doc/data/stable_abi.dat +++ b/Doc/data/stable_abi.dat @@ -347,6 +347,7 @@ var,PyList_Type,3.2,, type,PyLongObject,3.2,,opaque var,PyLongRangeIter_Type,3.2,, function,PyLong_AsDouble,3.2,, +function,PyLong_AsInt,3.13,, function,PyLong_AsLong,3.2,, function,PyLong_AsLongAndOverflow,3.2,, function,PyLong_AsLongLong,3.2,, diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 25eb5e981c5..4ff12b16d00 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -871,6 +871,12 @@ New Features :term:`shutting down `. (Contributed by Victor Stinner in :gh:`108014`.) +* Add :c:func:`PyLong_AsInt` function: similar to :c:func:`PyLong_AsLong`, but + store the result in a C :c:expr:`int` instead of a C :c:expr:`long`. + Previously, it was known as the private function :c:func:`!_PyLong_AsInt` + (with an underscore prefix). + (Contributed by Victor Stinner in :gh:`108014`.) + Porting to Python 3.13 ---------------------- diff --git a/Include/cpython/longobject.h b/Include/cpython/longobject.h index c581f51cbcd..c96f35188c4 100644 --- a/Include/cpython/longobject.h +++ b/Include/cpython/longobject.h @@ -2,7 +2,8 @@ # error "this header file must not be included directly" #endif -PyAPI_FUNC(int) _PyLong_AsInt(PyObject *); +// Alias for backport compatibility +#define _PyLong_AsInt PyLong_AsInt PyAPI_FUNC(int) _PyLong_UnsignedShort_Converter(PyObject *, void *); PyAPI_FUNC(int) _PyLong_UnsignedInt_Converter(PyObject *, void *); diff --git a/Include/longobject.h b/Include/longobject.h index e559e238ae5..7393254cd24 100644 --- a/Include/longobject.h +++ b/Include/longobject.h @@ -18,12 +18,18 @@ PyAPI_FUNC(PyObject *) PyLong_FromUnsignedLong(unsigned long); PyAPI_FUNC(PyObject *) PyLong_FromSize_t(size_t); PyAPI_FUNC(PyObject *) PyLong_FromSsize_t(Py_ssize_t); PyAPI_FUNC(PyObject *) PyLong_FromDouble(double); + PyAPI_FUNC(long) PyLong_AsLong(PyObject *); PyAPI_FUNC(long) PyLong_AsLongAndOverflow(PyObject *, int *); PyAPI_FUNC(Py_ssize_t) PyLong_AsSsize_t(PyObject *); PyAPI_FUNC(size_t) PyLong_AsSize_t(PyObject *); PyAPI_FUNC(unsigned long) PyLong_AsUnsignedLong(PyObject *); PyAPI_FUNC(unsigned long) PyLong_AsUnsignedLongMask(PyObject *); + +#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030d0000 +PyAPI_FUNC(int) PyLong_AsInt(PyObject *); +#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 8928fd94a1d..101fe1f0de7 100644 --- a/Lib/test/test_capi/test_long.py +++ b/Lib/test/test_capi/test_long.py @@ -34,6 +34,36 @@ class LongTests(unittest.TestCase): self.assertEqual(_testcapi.call_long_compact_api(sys.maxsize), (False, -1)) + def test_long_asint(self): + PyLong_AsInt = _testcapi.PyLong_AsInt + INT_MIN = _testcapi.INT_MIN + INT_MAX = _testcapi.INT_MAX + + # round trip (object -> int -> object) + for value in (INT_MIN, INT_MAX, -1, 0, 1, 123): + with self.subTest(value=value): + self.assertEqual(PyLong_AsInt(value), value) + + # use __index__(), not __int__() + class MyIndex: + def __index__(self): + return 10 + def __int__(self): + return 22 + self.assertEqual(PyLong_AsInt(MyIndex()), 10) + + # bound checking + with self.assertRaises(OverflowError): + PyLong_AsInt(INT_MIN - 1) + with self.assertRaises(OverflowError): + PyLong_AsInt(INT_MAX + 1) + + # invalid type + for value in (1.0, b'2', '3'): + with self.subTest(value=value): + with self.assertRaises(TypeError): + PyLong_AsInt(value) + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py index 566d36a3f5b..1f3cf612c18 100644 --- a/Lib/test/test_stable_abi_ctypes.py +++ b/Lib/test/test_stable_abi_ctypes.py @@ -375,6 +375,7 @@ SYMBOL_NAMES = ( "PyList_Type", "PyLongRangeIter_Type", "PyLong_AsDouble", + "PyLong_AsInt", "PyLong_AsLong", "PyLong_AsLongAndOverflow", "PyLong_AsLongLong", diff --git a/Misc/NEWS.d/next/C API/2023-08-24-20-08-02.gh-issue-108014.20DOSS.rst b/Misc/NEWS.d/next/C API/2023-08-24-20-08-02.gh-issue-108014.20DOSS.rst new file mode 100644 index 00000000000..5c1b04f3237 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2023-08-24-20-08-02.gh-issue-108014.20DOSS.rst @@ -0,0 +1,4 @@ +Add :c:func:`PyLong_AsInt` function: similar to :c:func:`PyLong_AsLong`, but +store the result in a C :c:expr:`int` instead of a C :c:expr:`long`. +Previously, it was known as the the private function :c:func:`!_PyLong_AsInt` +(with an underscore prefix). Patch by Victor Stinner. diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml index 16d5c1a07ae..2030a085abf 100644 --- a/Misc/stable_abi.toml +++ b/Misc/stable_abi.toml @@ -2450,3 +2450,5 @@ added = '3.13' [function.PyDict_GetItemStringRef] added = '3.13' +[function.PyLong_AsInt] + added = '3.13' diff --git a/Modules/_testcapi/clinic/long.c.h b/Modules/_testcapi/clinic/long.c.h index 95885e0ae17..87bba4cfacf 100644 --- a/Modules/_testcapi/clinic/long.c.h +++ b/Modules/_testcapi/clinic/long.c.h @@ -163,4 +163,12 @@ PyDoc_STRVAR(_testcapi_call_long_compact_api__doc__, #define _TESTCAPI_CALL_LONG_COMPACT_API_METHODDEF \ {"call_long_compact_api", (PyCFunction)_testcapi_call_long_compact_api, METH_O, _testcapi_call_long_compact_api__doc__}, -/*[clinic end generated code: output=d000a1b58fa81eab input=a9049054013a1b77]*/ + +PyDoc_STRVAR(_testcapi_PyLong_AsInt__doc__, +"PyLong_AsInt($module, arg, /)\n" +"--\n" +"\n"); + +#define _TESTCAPI_PYLONG_ASINT_METHODDEF \ + {"PyLong_AsInt", (PyCFunction)_testcapi_PyLong_AsInt, METH_O, _testcapi_PyLong_AsInt__doc__}, +/*[clinic end generated code: output=1631a18f1193486a input=a9049054013a1b77]*/ diff --git a/Modules/_testcapi/long.c b/Modules/_testcapi/long.c index ede43f60d06..6b74e0ab8e0 100644 --- a/Modules/_testcapi/long.c +++ b/Modules/_testcapi/long.c @@ -37,6 +37,9 @@ raise_test_long_error(const char* msg) return raiseTestError("test_long_api", msg); } +// Test PyLong_FromLong()/PyLong_AsLong() +// and PyLong_FromUnsignedLong()/PyLong_AsUnsignedLong(). + #define TESTNAME test_long_api_inner #define TYPENAME long #define F_S_TO_PY PyLong_FromLong @@ -64,6 +67,9 @@ _testcapi_test_long_api_impl(PyObject *module) #undef F_U_TO_PY #undef F_PY_TO_U +// Test PyLong_FromLongLong()/PyLong_AsLongLong() +// and PyLong_FromUnsignedLongLong()/PyLong_AsUnsignedLongLong(). + static PyObject * raise_test_longlong_error(const char* msg) { @@ -595,6 +601,24 @@ _testcapi_call_long_compact_api(PyObject *module, PyObject *arg) return Py_BuildValue("in", is_compact, value); } +/*[clinic input] +_testcapi.PyLong_AsInt + arg: object + / +[clinic start generated code]*/ + +static PyObject * +_testcapi_PyLong_AsInt(PyObject *module, PyObject *arg) +/*[clinic end generated code: output=0df9f19de5fa575b input=9561b97105493a67]*/ +{ + assert(!PyErr_Occurred()); + int value = PyLong_AsInt(arg); + if (value == -1 && PyErr_Occurred()) { + return NULL; + } + return PyLong_FromLong(value); +} + static PyMethodDef test_methods[] = { _TESTCAPI_TEST_LONG_AND_OVERFLOW_METHODDEF _TESTCAPI_TEST_LONG_API_METHODDEF @@ -605,6 +629,7 @@ static PyMethodDef test_methods[] = { _TESTCAPI_TEST_LONG_NUMBITS_METHODDEF _TESTCAPI_TEST_LONGLONG_API_METHODDEF _TESTCAPI_CALL_LONG_COMPACT_API_METHODDEF + _TESTCAPI_PYLONG_ASINT_METHODDEF {NULL}, }; diff --git a/Objects/longobject.c b/Objects/longobject.c index 354cba9d6d8..d20ef412367 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -549,7 +549,7 @@ PyLong_AsLong(PyObject *obj) method. Return -1 and set an error if overflow occurs. */ int -_PyLong_AsInt(PyObject *obj) +PyLong_AsInt(PyObject *obj) { int overflow; long result = PyLong_AsLongAndOverflow(obj, &overflow); diff --git a/PC/python3dll.c b/PC/python3dll.c index 64dfbba3e42..ee3a7d7b4e5 100755 --- a/PC/python3dll.c +++ b/PC/python3dll.c @@ -331,6 +331,7 @@ EXPORT_FUNC(PyList_SetSlice) EXPORT_FUNC(PyList_Size) EXPORT_FUNC(PyList_Sort) EXPORT_FUNC(PyLong_AsDouble) +EXPORT_FUNC(PyLong_AsInt) EXPORT_FUNC(PyLong_AsLong) EXPORT_FUNC(PyLong_AsLongAndOverflow) EXPORT_FUNC(PyLong_AsLongLong)