Skip to content

Commit

Permalink
pythongh-120389: Add PyLong_FromInt64() and PyLong_AsInt64()
Browse files Browse the repository at this point in the history
  • Loading branch information
vstinner committed Jun 12, 2024
1 parent f5a9c34 commit 14a0635
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 32 deletions.
8 changes: 8 additions & 0 deletions Include/longobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ 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_FromInt64(int64_t value);
PyAPI_FUNC(PyObject*) PyLong_FromUInt64(uint64_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
77 changes: 47 additions & 30 deletions Lib/test/test_capi/test_long.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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()
Expand Down Expand Up @@ -737,6 +743,17 @@ def test_long_getsign(self):

# CRASHES getsign(NULL)

def test_long_asint64(self):
# Test PyLong_AsInt64() and PyLong_FromInt64()
asint64 = _testlimitedcapi.pylong_asint64
from _testcapi import INT64_MIN, INT64_MAX
self.check_long_asint(asint64, INT64_MIN, INT64_MAX)

def test_long_asuint64(self):
# Test PyLong_AsUInt64() and PyLong_FromUInt64()
asuint64 = _testlimitedcapi.pylong_asuint64
from _testcapi import UINT64_MAX
self.check_long_asunsignedint(asuint64, UINT64_MAX)

if __name__ == "__main__":
unittest.main()
3 changes: 3 additions & 0 deletions Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -4023,6 +4023,9 @@ PyInit__testcapi(void)

PyModule_AddIntConstant(m, "the_number_three", 3);
PyModule_AddIntMacro(m, Py_C_RECURSION_LIMIT);
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
29 changes: 27 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,29 @@ pylong_aspid(PyObject *module, PyObject *arg)
}


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 +808,8 @@ static PyMethodDef test_methods[] = {
{"pylong_asdouble", pylong_asdouble, METH_O},
{"pylong_asvoidptr", pylong_asvoidptr, METH_O},
{"pylong_aspid", pylong_aspid, METH_O},
{"pylong_asint64", pylong_asint64, METH_O},
{"pylong_asuint64", pylong_asuint64, METH_O},
{NULL},
};

Expand Down
48 changes: 48 additions & 0 deletions Objects/longobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,11 @@ PyLong_FromUnsignedLongLong(unsigned long long ival)
PYLONG_FROM_UINT(unsigned long long, ival);
}

PyObject* PyLong_FromUInt64(uint64_t value)
{
PYLONG_FROM_UINT(uint64_t, value);
}

/* Create a new int object from a C size_t. */

PyObject *
Expand Down Expand Up @@ -6681,3 +6686,46 @@ Py_ssize_t
PyUnstable_Long_CompactValue(const PyLongObject* op) {
return _PyLong_CompactValue(op);
}

PyObject* PyLong_FromInt64(int64_t value)
{
#if SIZEOF_LONG >= 8
return PyLong_FromLong(value);
#elif SIZEOF_LONG_LONG >= 8
return PyLong_FromLongLong(value);
#else
# error "unknown long type"
#endif
}

int PyLong_AsInt64(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_AsUInt64(PyObject *obj, uint64_t *value)
{
#if SIZEOF_LONG >= 8
long res = PyLong_AsUnsignedLong(obj);
#elif SIZEOF_LONG_LONG >= 8
long long res = PyLong_AsUnsignedLongLong(obj);
#else
# error "unknown long type"
#endif
if (res == -1 && PyErr_Occurred()) {
return -1;
}
*value = res;
return 0;
}

0 comments on commit 14a0635

Please sign in to comment.