Skip to content

Commit

Permalink
pythongh-120389: Add PyLong_FromInt64() and PyLong_AsInt64() (python#…
Browse files Browse the repository at this point in the history
…120390)

Add new functions to convert C <stdint.h> numbers from/to Python int:

* PyLong_FromInt32()
* PyLong_FromUInt32()
* PyLong_FromInt64()
* PyLong_FromUInt64()
* PyLong_AsInt32()
* PyLong_AsUInt32()
* PyLong_AsInt64()
* PyLong_AsUInt64()
  • Loading branch information
vstinner committed Aug 28, 2024
1 parent 1a0b828 commit 4c6dca8
Show file tree
Hide file tree
Showing 13 changed files with 286 additions and 2 deletions.
57 changes: 57 additions & 0 deletions Doc/c-api/long.rst
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,32 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate.
on failure.
.. 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 :c:expr:`int64_t`, or ``NULL``
with an exception set 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)
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``
with an exception set 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
Expand Down Expand Up @@ -337,6 +357,43 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate.
This function will no longer use :meth:`~object.__int__`.
.. c:function:: int PyLong_AsInt32(PyObject *obj, int32_t *value)
int PyLong_AsInt64(PyObject *obj, int64_t *value)
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)
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
:c:type:`PyLongObject`.
* If *obj* is negative, raise a :exc:`ValueError`.
* 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:: double PyLong_AsDouble(PyObject *pylong)
Return a C :c:expr:`double` representation of *pylong*. *pylong* must be
Expand Down
1 change: 1 addition & 0 deletions Doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,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'),
Expand Down
8 changes: 8 additions & 0 deletions Doc/data/stable_abi.dat

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,20 @@ New Features
an interned string and deallocate it during module shutdown.
(Contribued by Eddie Elizondo in :gh:`113601`.)

* Add new functions to convert C ``<stdint.h>`` numbers from/to Python
:class:`int`:

* :c:func:`PyLong_FromInt32`
* :c:func:`PyLong_FromInt64`
* :c:func:`PyLong_FromUInt32`
* :c:func:`PyLong_FromUInt64`
* :c:func:`PyLong_AsInt32`
* :c:func:`PyLong_AsInt64`
* :c:func:`PyLong_AsUInt32`
* :c:func:`PyLong_AsUInt64`

(Contributed by Victor Stinner in :gh:`120389`.)

Porting to Python 3.14
----------------------

Expand Down
12 changes: 12 additions & 0 deletions Include/longobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -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_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);

/* It may be useful in the future. I've added it in the PyInt -> PyLong
Expand Down
25 changes: 25 additions & 0 deletions Lib/test/test_capi/test_long.py
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,31 @@ def test_long_getsign(self):

# CRASHES getsign(NULL)

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)

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)

def test_long_asuint32(self):
# Test PyLong_AsUInt32() and PyLong_FromUInt32()
as_uint32 = _testlimitedcapi.pylong_asuint32
from _testcapi import UINT32_MAX
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_asint(as_uint64, 0, UINT64_MAX,
negative_value_error=ValueError)

if __name__ == "__main__":
unittest.main()
8 changes: 8 additions & 0 deletions Lib/test/test_stable_abi_ctypes.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Add new functions to convert C ``<stdint.h>`` 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_AsInt32`
* :c:func:`PyLong_AsUInt32`
* :c:func:`PyLong_AsInt64`
* :c:func:`PyLong_AsUInt64`

Patch by Victor Stinner.
16 changes: 16 additions & 0 deletions Misc/stable_abi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2510,3 +2510,19 @@
added = '3.14'
[function.PyIter_NextItem]
added = '3.14'
[function.PyLong_FromInt32]
added = '3.14'
[function.PyLong_FromUInt32]
added = '3.14'
[function.PyLong_AsInt32]
added = '3.14'
[function.PyLong_AsUInt32]
added = '3.14'
[function.PyLong_FromInt64]
added = '3.14'
[function.PyLong_FromUInt64]
added = '3.14'
[function.PyLong_AsInt64]
added = '3.14'
[function.PyLong_AsUInt64]
added = '3.14'
6 changes: 6 additions & 0 deletions Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -4046,6 +4046,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;
Expand Down
54 changes: 52 additions & 2 deletions Modules/_testlimitedcapi/long.c
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -758,6 +758,52 @@ pylong_aspid(PyObject *module, PyObject *arg)
}


static PyObject *
pylong_asint32(PyObject *module, PyObject *arg)
{
NULLABLE(arg);
int32_t value;
if (PyLong_AsInt32(arg, &value) < 0) {
return NULL;
}
return PyLong_FromInt32(value);
}

static PyObject *
pylong_asuint32(PyObject *module, PyObject *arg)
{
NULLABLE(arg);
uint32_t value;
if (PyLong_AsUInt32(arg, &value) < 0) {
return NULL;
}
return PyLong_FromUInt32(value);
}


static PyObject *
pylong_asint64(PyObject *module, PyObject *arg)
{
NULLABLE(arg);
int64_t value;
if (PyLong_AsInt64(arg, &value) < 0) {
return NULL;
}
return PyLong_FromInt64(value);
}

static PyObject *
pylong_asuint64(PyObject *module, PyObject *arg)
{
NULLABLE(arg);
uint64_t value;
if (PyLong_AsUInt64(arg, &value) < 0) {
return NULL;
}
return PyLong_FromUInt64(value);
}


static PyMethodDef test_methods[] = {
_TESTLIMITEDCAPI_TEST_LONG_AND_OVERFLOW_METHODDEF
_TESTLIMITEDCAPI_TEST_LONG_API_METHODDEF
Expand Down Expand Up @@ -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_asint32", pylong_asint32, METH_O},
{"pylong_asuint32", pylong_asuint32, METH_O},
{"pylong_asint64", pylong_asint64, METH_O},
{"pylong_asuint64", pylong_asuint64, METH_O},
{NULL},
};

Expand Down
Loading

0 comments on commit 4c6dca8

Please sign in to comment.