From 0f0047871071b24086664517e82e6ec56e41c522 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 9 Apr 2021 15:16:41 +0200 Subject: [PATCH] Add Py_Is(), Py_IsNone(), Py_IsTrue(), Py_IsFalse() --- README.rst | 14 +++++++ pythoncapi_compat.h | 17 ++++++++ tests/test_pythoncapi_compat_cext.c | 35 ++++++++++++++++ tests/test_upgrade_pythoncapi.py | 63 +++++++++++++++++++++++++++++ upgrade_pythoncapi.py | 29 +++++++++++++ 5 files changed, 158 insertions(+) diff --git a/README.rst b/README.rst index c9d9d82..c6e6ce8 100644 --- a/README.rst +++ b/README.rst @@ -108,6 +108,15 @@ Upgrade Operations * Replace ``obj->ob_refcnt = refcnt;`` with ``Py_SET_REFCNT(obj, refcnt);``. * Replace ``Py_REFCNT(obj) = refcnt;`` with ``Py_SET_REFCNT(obj, refcnt);``. +* ``Py_Is``: + + * Replace ``x == Py_None`` with ``Py_IsNone(x)``. + * Replace ``x == Py_True`` with ``Py_IsTrue(x)``. + * Replace ``x == Py_False`` with ``Py_IsFalse(x)``. + * Replace ``x != Py_None`` with ``!Py_IsNone(x)``. + * Replace ``x != Py_True`` with ``!Py_IsTrue(x)``. + * Replace ``x != Py_False`` with ``!Py_IsFalse(x)``. + * ``PyObject_NEW``: * Replace ``PyObject_NEW(...)`` with ``PyObject_New(...)``. @@ -156,6 +165,10 @@ Python 3.10 PyObject* Py_NewRef(PyObject *obj); PyObject* Py_XNewRef(PyObject *obj); + int Py_Is(PyObject *x, PyObject *y); + int Py_IsNone(PyObject *x); + int Py_IsTrue(PyObject *x); + int Py_IsFalse(PyObject *x); int PyModule_AddObjectRef(PyObject *module, const char *name, PyObject *value); @@ -324,6 +337,7 @@ Links Changelog ========= +* 2021-04-09: Add Py_Is(), Py_IsNone(), Py_IsTrue(), Py_IsFalse() functions. * 2021-04-01: * Add ``Py_SETREF()``, ``Py_XSETREF()`` and ``Py_UNUSED()``. diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index 4b04cce..21a5ff7 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -113,6 +113,23 @@ static inline void _Py_SET_REFCNT(PyObject *ob, Py_ssize_t refcnt) } while (0) #endif + +// bpo-43753 added Py_Is(), Py_IsNone(), Py_IsTrue() and Py_IsFalse() +// to Python 3.10.0b1. +#if PY_VERSION_HEX < 0x030A00B1 && !defined(Py_Is) +# define Py_Is(x, y) ((x) == (y)) +#endif +#if PY_VERSION_HEX < 0x030A00B1 && !defined(Py_IsNone) +# define Py_IsNone(x) Py_Is(x, Py_None) +#endif +#if PY_VERSION_HEX < 0x030A00B1 && !defined(Py_IsTrue) +# define Py_IsTrue(x) Py_Is(x, Py_True) +#endif +#if PY_VERSION_HEX < 0x030A00B1 && !defined(Py_IsFalse) +# define Py_IsFalse(x) Py_Is(x, Py_False) +#endif + + // bpo-39573 added Py_SET_TYPE() to Python 3.9.0a4 #if PY_VERSION_HEX < 0x030900A4 && !defined(Py_SET_TYPE) static inline void diff --git a/tests/test_pythoncapi_compat_cext.c b/tests/test_pythoncapi_compat_cext.c index aa5ed0f..147bf46 100644 --- a/tests/test_pythoncapi_compat_cext.c +++ b/tests/test_pythoncapi_compat_cext.c @@ -86,6 +86,40 @@ test_object(PyObject *Py_UNUSED(module), PyObject* Py_UNUSED(ignored)) } +static PyObject * +test_py_is(PyObject *Py_UNUSED(module), PyObject* Py_UNUSED(ignored)) +{ + PyObject *o_none = Py_None; + PyObject *o_true = Py_True; + PyObject *o_false = Py_False; + PyObject *obj = PyList_New(0); + if (obj == NULL) { + return NULL; + } + + /* test Py_Is() */ + assert(Py_Is(obj, obj)); + assert(!Py_Is(obj, o_none)); + + /* test Py_IsNone() */ + assert(Py_IsNone(o_none)); + assert(!Py_IsNone(obj)); + + /* test Py_IsTrue() */ + assert(Py_IsTrue(o_true)); + assert(!Py_IsTrue(o_false)); + assert(!Py_IsTrue(obj)); + + /* testPy_IsFalse() */ + assert(Py_IsFalse(o_false)); + assert(!Py_IsFalse(o_true)); + assert(!Py_IsFalse(obj)); + + Py_DECREF(obj); + Py_RETURN_NONE; +} + + static PyObject * test_steal_ref(PyObject *Py_UNUSED(module), PyObject* Py_UNUSED(ignored)) { @@ -358,6 +392,7 @@ test_module(PyObject *Py_UNUSED(module), PyObject* Py_UNUSED(ignored)) static struct PyMethodDef methods[] = { {"test_object", test_object, METH_NOARGS, NULL}, + {"test_py_is", test_py_is, METH_NOARGS, NULL}, {"test_steal_ref", test_steal_ref, METH_NOARGS, NULL}, #if !defined(PYPY_VERSION) {"test_frame", test_frame, METH_NOARGS, NULL}, diff --git a/tests/test_upgrade_pythoncapi.py b/tests/test_upgrade_pythoncapi.py index 11d614f..c16a1ce 100644 --- a/tests/test_upgrade_pythoncapi.py +++ b/tests/test_upgrade_pythoncapi.py @@ -22,6 +22,8 @@ def reformat(source): class Tests(unittest.TestCase): + maxDiff = 80 * 30 + def _test_patch_file(self, tmp_dir): # test Patcher.patcher() source = """ @@ -473,6 +475,67 @@ def test_py_decref_assign(self): } """) + def test_py_is(self): + self.check_replace(""" + void test_py_is(PyObject *x) + { + if (x == Py_None) { + return 1; + } + if (x == Py_True) { + return 2; + } + if (x == Py_False) { + return 3; + } + return 0; + } + + void test_py_is_not(PyObject *x) + { + if (x != Py_None) { + return 1; + } + if (x != Py_True) { + return 2; + } + if (x != Py_False) { + return 3; + } + return 0; + } + """, """ + #include "pythoncapi_compat.h" + + void test_py_is(PyObject *x) + { + if (Py_IsNone(x)) { + return 1; + } + if (Py_IsTrue(x)) { + return 2; + } + if (Py_IsFalse(x)) { + return 3; + } + return 0; + } + + void test_py_is_not(PyObject *x) + { + if (!Py_IsNone(x)) { + return 1; + } + if (!Py_IsTrue(x)) { + return 2; + } + if (!Py_IsFalse(x)) { + return 3; + } + return 0; + } + """) + if __name__ == "__main__": unittest.main() diff --git a/upgrade_pythoncapi.py b/upgrade_pythoncapi.py index b2ca1cc..3e9e543 100755 --- a/upgrade_pythoncapi.py +++ b/upgrade_pythoncapi.py @@ -301,6 +301,33 @@ def replace2(regs): NEED_PYTHONCAPI_COMPAT = True +class Py_Is(Operation): + NAME = "Py_Is" + DOC = ('replace "x == Py_None" with Py_IsNone(x), ' + 'replace "x != Py_None" with !Py_IsNone(x), ' + 'and do the same for Py_True/Py_False with Py_IsTrue()/Py_IsFalse()') + + def replace2(regs): + x = regs.group(1) + y = regs.group(2) + if y == 'NULL': + return regs.group(0) + return f'{x} = _Py_StealRef({y});' + + REPLACE = [] + id_regex = r'(%s)' % ID_REGEX + for name in ('None', 'True', 'False'): + REPLACE.extend(( + (re.compile(fr'({ID_REGEX}) == Py_{name}\b'), + fr'Py_Is{name}(\1)'), + (re.compile(fr'({ID_REGEX}) != Py_{name}\b'), + fr'!Py_Is{name}(\1)'), + )) + + # Need Py_IsNone(), Py_IsTrue(), Py_IsFalse(): new in Python 3.10 + NEED_PYTHONCAPI_COMPAT = True + + OPERATIONS = [ Py_SET_TYPE, Py_SET_SIZE, @@ -310,6 +337,8 @@ def replace2(regs): Py_SIZE, Py_REFCNT, + Py_Is, + PyObject_NEW, PyMem_MALLOC, PyObject_MALLOC,