Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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(...)``.
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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()``.
Expand Down
17 changes: 17 additions & 0 deletions pythoncapi_compat.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
35 changes: 35 additions & 0 deletions tests/test_pythoncapi_compat_cext.c
Original file line number Diff line number Diff line change
Expand Up @@ -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))
{
Expand Down Expand Up @@ -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},
Expand Down
63 changes: 63 additions & 0 deletions tests/test_upgrade_pythoncapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = """
Expand Down Expand Up @@ -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()
29 changes: 29 additions & 0 deletions upgrade_pythoncapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -310,6 +337,8 @@ def replace2(regs):
Py_SIZE,
Py_REFCNT,

Py_Is,

PyObject_NEW,
PyMem_MALLOC,
PyObject_MALLOC,
Expand Down