bpo-23699: Use a macro to reduce boilerplate code in rich comparison functions (GH-793)

This commit is contained in:
stratakis 2017-11-02 11:32:54 +01:00 committed by Nick Coghlan
parent 4f469c0966
commit e8b1965639
16 changed files with 75 additions and 316 deletions

View File

@ -623,6 +623,22 @@ type objects) *must* have the :attr:`ob_size` field.
| :const:`Py_GE` | ``>=`` | | :const:`Py_GE` | ``>=`` |
+----------------+------------+ +----------------+------------+
The following macro is defined to ease writing rich comparison functions:
.. c:function:: PyObject *Py_RETURN_RICHCOMPARE(VAL_A, VAL_B, int op)
Return ``Py_True`` or ``Py_False`` from the function, depending on the
result of a comparison.
VAL_A and VAL_B must be orderable by C comparison operators (for example,
they may be C ints or floats). The third argument specifies the requested
operation, as for :c:func:`PyObject_RichCompare`.
The return value's reference count is properly incremented.
On error, sets an exception and returns NULL from the function.
.. versionadded:: 3.7
.. c:member:: Py_ssize_t PyTypeObject.tp_weaklistoffset .. c:member:: Py_ssize_t PyTypeObject.tp_weaklistoffset

View File

@ -931,6 +931,25 @@ PyAPI_DATA(PyObject) _Py_NotImplementedStruct; /* Don't use this directly */
#define Py_GT 4 #define Py_GT 4
#define Py_GE 5 #define Py_GE 5
/*
* Macro for implementing rich comparisons
*
* Needs to be a macro because any C-comparable type can be used.
*/
#define Py_RETURN_RICHCOMPARE(val1, val2, op) \
do { \
switch (op) { \
case Py_EQ: if ((val1) == (val2)) Py_RETURN_TRUE; Py_RETURN_FALSE; \
case Py_NE: if ((val1) != (val2)) Py_RETURN_TRUE; Py_RETURN_FALSE; \
case Py_LT: if ((val1) < (val2)) Py_RETURN_TRUE; Py_RETURN_FALSE; \
case Py_GT: if ((val1) > (val2)) Py_RETURN_TRUE; Py_RETURN_FALSE; \
case Py_LE: if ((val1) <= (val2)) Py_RETURN_TRUE; Py_RETURN_FALSE; \
case Py_GE: if ((val1) >= (val2)) Py_RETURN_TRUE; Py_RETURN_FALSE; \
default: \
Py_UNREACHABLE(); \
} \
} while (0)
#ifndef Py_LIMITED_API #ifndef Py_LIMITED_API
/* Maps Py_LT to Py_GT, ..., Py_GE to Py_LE. /* Maps Py_LT to Py_GT, ..., Py_GE to Py_LE.
* Defined in object.c. * Defined in object.c.

View File

@ -1646,7 +1646,7 @@ Mike Verdone
Jaap Vermeulen Jaap Vermeulen
Nikita Vetoshkin Nikita Vetoshkin
Al Vezza Al Vezza
Petr Victorin Petr Viktorin
Jacques A. Vidrine Jacques A. Vidrine
John Viega John Viega
Dino Viehland Dino Viehland

View File

@ -0,0 +1,2 @@
Add Py_RETURN_RICHCOMPARE macro to reduce boilerplate code in rich
comparison functions.

View File

@ -1442,22 +1442,7 @@ build_struct_time(int y, int m, int d, int hh, int mm, int ss, int dstflag)
static PyObject * static PyObject *
diff_to_bool(int diff, int op) diff_to_bool(int diff, int op)
{ {
PyObject *result; Py_RETURN_RICHCOMPARE(diff, 0, op);
int istrue;
switch (op) {
case Py_EQ: istrue = diff == 0; break;
case Py_NE: istrue = diff != 0; break;
case Py_LE: istrue = diff <= 0; break;
case Py_GE: istrue = diff >= 0; break;
case Py_LT: istrue = diff < 0; break;
case Py_GT: istrue = diff > 0; break;
default:
Py_UNREACHABLE();
}
result = istrue ? Py_True : Py_False;
Py_INCREF(result);
return result;
} }
/* Raises a "can't compare" TypeError and returns NULL. */ /* Raises a "can't compare" TypeError and returns NULL. */

View File

@ -864,13 +864,10 @@ PyTclObject_repr(PyTclObject *self)
return repr; return repr;
} }
#define TEST_COND(cond) ((cond) ? Py_True : Py_False)
static PyObject * static PyObject *
PyTclObject_richcompare(PyObject *self, PyObject *other, int op) PyTclObject_richcompare(PyObject *self, PyObject *other, int op)
{ {
int result; int result;
PyObject *v;
/* neither argument should be NULL, unless something's gone wrong */ /* neither argument should be NULL, unless something's gone wrong */
if (self == NULL || other == NULL) { if (self == NULL || other == NULL) {
@ -880,8 +877,7 @@ PyTclObject_richcompare(PyObject *self, PyObject *other, int op)
/* both arguments should be instances of PyTclObject */ /* both arguments should be instances of PyTclObject */
if (!PyTclObject_Check(self) || !PyTclObject_Check(other)) { if (!PyTclObject_Check(self) || !PyTclObject_Check(other)) {
v = Py_NotImplemented; Py_RETURN_NOTIMPLEMENTED;
goto finished;
} }
if (self == other) if (self == other)
@ -890,33 +886,7 @@ PyTclObject_richcompare(PyObject *self, PyObject *other, int op)
else else
result = strcmp(Tcl_GetString(((PyTclObject *)self)->value), result = strcmp(Tcl_GetString(((PyTclObject *)self)->value),
Tcl_GetString(((PyTclObject *)other)->value)); Tcl_GetString(((PyTclObject *)other)->value));
/* Convert return value to a Boolean */ Py_RETURN_RICHCOMPARE(result, 0, op);
switch (op) {
case Py_EQ:
v = TEST_COND(result == 0);
break;
case Py_NE:
v = TEST_COND(result != 0);
break;
case Py_LE:
v = TEST_COND(result <= 0);
break;
case Py_GE:
v = TEST_COND(result >= 0);
break;
case Py_LT:
v = TEST_COND(result < 0);
break;
case Py_GT:
v = TEST_COND(result > 0);
break;
default:
PyErr_BadArgument();
return NULL;
}
finished:
Py_INCREF(v);
return v;
} }
PyDoc_STRVAR(get_typename__doc__, "name of the Tcl type"); PyDoc_STRVAR(get_typename__doc__, "name of the Tcl type");

View File

@ -299,13 +299,10 @@ parser_compare_nodes(node *left, node *right)
* *
*/ */
#define TEST_COND(cond) ((cond) ? Py_True : Py_False)
static PyObject * static PyObject *
parser_richcompare(PyObject *left, PyObject *right, int op) parser_richcompare(PyObject *left, PyObject *right, int op)
{ {
int result; int result;
PyObject *v;
/* neither argument should be NULL, unless something's gone wrong */ /* neither argument should be NULL, unless something's gone wrong */
if (left == NULL || right == NULL) { if (left == NULL || right == NULL) {
@ -315,8 +312,7 @@ parser_richcompare(PyObject *left, PyObject *right, int op)
/* both arguments should be instances of PyST_Object */ /* both arguments should be instances of PyST_Object */
if (!PyST_Object_Check(left) || !PyST_Object_Check(right)) { if (!PyST_Object_Check(left) || !PyST_Object_Check(right)) {
v = Py_NotImplemented; Py_RETURN_NOTIMPLEMENTED;
goto finished;
} }
if (left == right) if (left == right)
@ -326,33 +322,7 @@ parser_richcompare(PyObject *left, PyObject *right, int op)
result = parser_compare_nodes(((PyST_Object *)left)->st_node, result = parser_compare_nodes(((PyST_Object *)left)->st_node,
((PyST_Object *)right)->st_node); ((PyST_Object *)right)->st_node);
/* Convert return value to a Boolean */ Py_RETURN_RICHCOMPARE(result, 0, op);
switch (op) {
case Py_EQ:
v = TEST_COND(result == 0);
break;
case Py_NE:
v = TEST_COND(result != 0);
break;
case Py_LE:
v = TEST_COND(result <= 0);
break;
case Py_GE:
v = TEST_COND(result >= 0);
break;
case Py_LT:
v = TEST_COND(result < 0);
break;
case Py_GT:
v = TEST_COND(result > 0);
break;
default:
PyErr_BadArgument();
return NULL;
}
finished:
Py_INCREF(v);
return v;
} }
/* parser_newstobject(node* st) /* parser_newstobject(node* st)

View File

@ -1929,27 +1929,7 @@ kqueue_event_richcompare(kqueue_event_Object *s, kqueue_event_Object *o,
: 0; : 0;
#undef CMP #undef CMP
switch (op) { Py_RETURN_RICHCOMPARE(result, 0, op);
case Py_EQ:
result = (result == 0);
break;
case Py_NE:
result = (result != 0);
break;
case Py_LE:
result = (result <= 0);
break;
case Py_GE:
result = (result >= 0);
break;
case Py_LT:
result = (result < 0);
break;
case Py_GT:
result = (result > 0);
break;
}
return PyBool_FromLong((long)result);
} }
static PyTypeObject kqueue_event_Type = { static PyTypeObject kqueue_event_Type = {

View File

@ -1012,8 +1012,6 @@ bytearray_richcompare(PyObject *self, PyObject *other, int op)
{ {
Py_ssize_t self_size, other_size; Py_ssize_t self_size, other_size;
Py_buffer self_bytes, other_bytes; Py_buffer self_bytes, other_bytes;
PyObject *res;
Py_ssize_t minsize;
int cmp, rc; int cmp, rc;
/* Bytes can be compared to anything that supports the (binary) /* Bytes can be compared to anything that supports the (binary)
@ -1049,38 +1047,25 @@ bytearray_richcompare(PyObject *self, PyObject *other, int op)
if (self_size != other_size && (op == Py_EQ || op == Py_NE)) { if (self_size != other_size && (op == Py_EQ || op == Py_NE)) {
/* Shortcut: if the lengths differ, the objects differ */ /* Shortcut: if the lengths differ, the objects differ */
cmp = (op == Py_NE); PyBuffer_Release(&self_bytes);
PyBuffer_Release(&other_bytes);
return PyBool_FromLong((op == Py_NE));
} }
else { else {
minsize = self_size; cmp = memcmp(self_bytes.buf, other_bytes.buf,
if (other_size < minsize) Py_MIN(self_size, other_size));
minsize = other_size;
cmp = memcmp(self_bytes.buf, other_bytes.buf, minsize);
/* In ISO C, memcmp() guarantees to use unsigned bytes! */ /* In ISO C, memcmp() guarantees to use unsigned bytes! */
if (cmp == 0) { PyBuffer_Release(&self_bytes);
if (self_size < other_size) PyBuffer_Release(&other_bytes);
cmp = -1;
else if (self_size > other_size) if (cmp != 0) {
cmp = 1; Py_RETURN_RICHCOMPARE(cmp, 0, op);
} }
switch (op) { Py_RETURN_RICHCOMPARE(self_size, other_size, op);
case Py_LT: cmp = cmp < 0; break;
case Py_LE: cmp = cmp <= 0; break;
case Py_EQ: cmp = cmp == 0; break;
case Py_NE: cmp = cmp != 0; break;
case Py_GT: cmp = cmp > 0; break;
case Py_GE: cmp = cmp >= 0; break;
}
} }
res = cmp ? Py_True : Py_False;
PyBuffer_Release(&self_bytes);
PyBuffer_Release(&other_bytes);
Py_INCREF(res);
return res;
} }
static void static void

View File

@ -1566,7 +1566,6 @@ bytes_richcompare(PyBytesObject *a, PyBytesObject *b, int op)
int c; int c;
Py_ssize_t len_a, len_b; Py_ssize_t len_a, len_b;
Py_ssize_t min_len; Py_ssize_t min_len;
PyObject *result;
int rc; int rc;
/* Make sure both arguments are strings. */ /* Make sure both arguments are strings. */
@ -1599,7 +1598,7 @@ bytes_richcompare(PyBytesObject *a, PyBytesObject *b, int op)
} }
} }
} }
result = Py_NotImplemented; Py_RETURN_NOTIMPLEMENTED;
} }
else if (a == b) { else if (a == b) {
switch (op) { switch (op) {
@ -1607,12 +1606,12 @@ bytes_richcompare(PyBytesObject *a, PyBytesObject *b, int op)
case Py_LE: case Py_LE:
case Py_GE: case Py_GE:
/* a string is equal to itself */ /* a string is equal to itself */
result = Py_True; Py_RETURN_TRUE;
break; break;
case Py_NE: case Py_NE:
case Py_LT: case Py_LT:
case Py_GT: case Py_GT:
result = Py_False; Py_RETURN_FALSE;
break; break;
default: default:
PyErr_BadArgument(); PyErr_BadArgument();
@ -1622,7 +1621,7 @@ bytes_richcompare(PyBytesObject *a, PyBytesObject *b, int op)
else if (op == Py_EQ || op == Py_NE) { else if (op == Py_EQ || op == Py_NE) {
int eq = bytes_compare_eq(a, b); int eq = bytes_compare_eq(a, b);
eq ^= (op == Py_NE); eq ^= (op == Py_NE);
result = eq ? Py_True : Py_False; return PyBool_FromLong(eq);
} }
else { else {
len_a = Py_SIZE(a); len_a = Py_SIZE(a);
@ -1635,22 +1634,10 @@ bytes_richcompare(PyBytesObject *a, PyBytesObject *b, int op)
} }
else else
c = 0; c = 0;
if (c == 0) if (c != 0)
c = (len_a < len_b) ? -1 : (len_a > len_b) ? 1 : 0; Py_RETURN_RICHCOMPARE(c, 0, op);
switch (op) { Py_RETURN_RICHCOMPARE(len_a, len_b, op);
case Py_LT: c = c < 0; break;
case Py_LE: c = c <= 0; break;
case Py_GT: c = c > 0; break;
case Py_GE: c = c >= 0; break;
default:
PyErr_BadArgument();
return NULL;
}
result = c ? Py_True : Py_False;
} }
Py_INCREF(result);
return result;
} }
static Py_hash_t static Py_hash_t

View File

@ -53,22 +53,15 @@ cell_dealloc(PyCellObject *op)
PyObject_GC_Del(op); PyObject_GC_Del(op);
} }
#define TEST_COND(cond) ((cond) ? Py_True : Py_False)
static PyObject * static PyObject *
cell_richcompare(PyObject *a, PyObject *b, int op) cell_richcompare(PyObject *a, PyObject *b, int op)
{ {
int result;
PyObject *v;
/* neither argument should be NULL, unless something's gone wrong */ /* neither argument should be NULL, unless something's gone wrong */
assert(a != NULL && b != NULL); assert(a != NULL && b != NULL);
/* both arguments should be instances of PyCellObject */ /* both arguments should be instances of PyCellObject */
if (!PyCell_Check(a) || !PyCell_Check(b)) { if (!PyCell_Check(a) || !PyCell_Check(b)) {
v = Py_NotImplemented; Py_RETURN_NOTIMPLEMENTED;
Py_INCREF(v);
return v;
} }
/* compare cells by contents; empty cells come before anything else */ /* compare cells by contents; empty cells come before anything else */
@ -77,32 +70,7 @@ cell_richcompare(PyObject *a, PyObject *b, int op)
if (a != NULL && b != NULL) if (a != NULL && b != NULL)
return PyObject_RichCompare(a, b, op); return PyObject_RichCompare(a, b, op);
result = (b == NULL) - (a == NULL); Py_RETURN_RICHCOMPARE(b == NULL, a == NULL, op);
switch (op) {
case Py_EQ:
v = TEST_COND(result == 0);
break;
case Py_NE:
v = TEST_COND(result != 0);
break;
case Py_LE:
v = TEST_COND(result <= 0);
break;
case Py_GE:
v = TEST_COND(result >= 0);
break;
case Py_LT:
v = TEST_COND(result < 0);
break;
case Py_GT:
v = TEST_COND(result > 0);
break;
default:
PyErr_BadArgument();
return NULL;
}
Py_INCREF(v);
return v;
} }
static PyObject * static PyObject *

View File

@ -1035,22 +1035,16 @@ wrapper_dealloc(wrapperobject *wp)
Py_TRASHCAN_SAFE_END(wp) Py_TRASHCAN_SAFE_END(wp)
} }
#define TEST_COND(cond) ((cond) ? Py_True : Py_False)
static PyObject * static PyObject *
wrapper_richcompare(PyObject *a, PyObject *b, int op) wrapper_richcompare(PyObject *a, PyObject *b, int op)
{ {
intptr_t result;
PyObject *v;
PyWrapperDescrObject *a_descr, *b_descr; PyWrapperDescrObject *a_descr, *b_descr;
assert(a != NULL && b != NULL); assert(a != NULL && b != NULL);
/* both arguments should be wrapperobjects */ /* both arguments should be wrapperobjects */
if (!Wrapper_Check(a) || !Wrapper_Check(b)) { if (!Wrapper_Check(a) || !Wrapper_Check(b)) {
v = Py_NotImplemented; Py_RETURN_NOTIMPLEMENTED;
Py_INCREF(v);
return v;
} }
/* compare by descriptor address; if the descriptors are the same, /* compare by descriptor address; if the descriptors are the same,
@ -1063,32 +1057,7 @@ wrapper_richcompare(PyObject *a, PyObject *b, int op)
return PyObject_RichCompare(a, b, op); return PyObject_RichCompare(a, b, op);
} }
result = a_descr - b_descr; Py_RETURN_RICHCOMPARE(a_descr, b_descr, op);
switch (op) {
case Py_EQ:
v = TEST_COND(result == 0);
break;
case Py_NE:
v = TEST_COND(result != 0);
break;
case Py_LE:
v = TEST_COND(result <= 0);
break;
case Py_GE:
v = TEST_COND(result >= 0);
break;
case Py_LT:
v = TEST_COND(result < 0);
break;
case Py_GT:
v = TEST_COND(result > 0);
break;
default:
PyErr_BadArgument();
return NULL;
}
Py_INCREF(v);
return v;
} }
static Py_hash_t static Py_hash_t

View File

@ -2330,13 +2330,10 @@ list_richcompare(PyObject *v, PyObject *w, int op)
if (Py_SIZE(vl) != Py_SIZE(wl) && (op == Py_EQ || op == Py_NE)) { if (Py_SIZE(vl) != Py_SIZE(wl) && (op == Py_EQ || op == Py_NE)) {
/* Shortcut: if the lengths differ, the lists differ */ /* Shortcut: if the lengths differ, the lists differ */
PyObject *res;
if (op == Py_EQ) if (op == Py_EQ)
res = Py_False; Py_RETURN_FALSE;
else else
res = Py_True; Py_RETURN_TRUE;
Py_INCREF(res);
return res;
} }
/* Search for the first index where items are different */ /* Search for the first index where items are different */
@ -2351,25 +2348,7 @@ list_richcompare(PyObject *v, PyObject *w, int op)
if (i >= Py_SIZE(vl) || i >= Py_SIZE(wl)) { if (i >= Py_SIZE(vl) || i >= Py_SIZE(wl)) {
/* No more items to compare -- compare sizes */ /* No more items to compare -- compare sizes */
Py_ssize_t vs = Py_SIZE(vl); Py_RETURN_RICHCOMPARE(Py_SIZE(vl), Py_SIZE(wl), op);
Py_ssize_t ws = Py_SIZE(wl);
int cmp;
PyObject *res;
switch (op) {
case Py_LT: cmp = vs < ws; break;
case Py_LE: cmp = vs <= ws; break;
case Py_EQ: cmp = vs == ws; break;
case Py_NE: cmp = vs != ws; break;
case Py_GT: cmp = vs > ws; break;
case Py_GE: cmp = vs >= ws; break;
default: return NULL; /* cannot happen */
}
if (cmp)
res = Py_True;
else
res = Py_False;
Py_INCREF(res);
return res;
} }
/* We have an item that differs -- shortcuts for EQ/NE */ /* We have an item that differs -- shortcuts for EQ/NE */

View File

@ -2915,45 +2915,16 @@ long_compare(PyLongObject *a, PyLongObject *b)
return sign < 0 ? -1 : sign > 0 ? 1 : 0; return sign < 0 ? -1 : sign > 0 ? 1 : 0;
} }
#define TEST_COND(cond) \
((cond) ? Py_True : Py_False)
static PyObject * static PyObject *
long_richcompare(PyObject *self, PyObject *other, int op) long_richcompare(PyObject *self, PyObject *other, int op)
{ {
int result; int result;
PyObject *v;
CHECK_BINOP(self, other); CHECK_BINOP(self, other);
if (self == other) if (self == other)
result = 0; result = 0;
else else
result = long_compare((PyLongObject*)self, (PyLongObject*)other); result = long_compare((PyLongObject*)self, (PyLongObject*)other);
/* Convert the return value to a Boolean */ Py_RETURN_RICHCOMPARE(result, 0, op);
switch (op) {
case Py_EQ:
v = TEST_COND(result == 0);
break;
case Py_NE:
v = TEST_COND(result != 0);
break;
case Py_LE:
v = TEST_COND(result <= 0);
break;
case Py_GE:
v = TEST_COND(result >= 0);
break;
case Py_LT:
v = TEST_COND(result == -1);
break;
case Py_GT:
v = TEST_COND(result == 1);
break;
default:
PyErr_BadArgument();
return NULL;
}
Py_INCREF(v);
return v;
} }
static Py_hash_t static Py_hash_t

View File

@ -647,23 +647,7 @@ tuplerichcompare(PyObject *v, PyObject *w, int op)
if (i >= vlen || i >= wlen) { if (i >= vlen || i >= wlen) {
/* No more items to compare -- compare sizes */ /* No more items to compare -- compare sizes */
int cmp; Py_RETURN_RICHCOMPARE(vlen, wlen, op);
PyObject *res;
switch (op) {
case Py_LT: cmp = vlen < wlen; break;
case Py_LE: cmp = vlen <= wlen; break;
case Py_EQ: cmp = vlen == wlen; break;
case Py_NE: cmp = vlen != wlen; break;
case Py_GT: cmp = vlen > wlen; break;
case Py_GE: cmp = vlen >= wlen; break;
default: return NULL; /* cannot happen */
}
if (cmp)
res = Py_True;
else
res = Py_False;
Py_INCREF(res);
return res;
} }
/* We have an item that differs -- shortcuts for EQ/NE */ /* We have an item that differs -- shortcuts for EQ/NE */

View File

@ -11152,14 +11152,10 @@ _PyUnicode_EqualToASCIIId(PyObject *left, _Py_Identifier *right)
return unicode_compare_eq(left, right_uni); return unicode_compare_eq(left, right_uni);
} }
#define TEST_COND(cond) \
((cond) ? Py_True : Py_False)
PyObject * PyObject *
PyUnicode_RichCompare(PyObject *left, PyObject *right, int op) PyUnicode_RichCompare(PyObject *left, PyObject *right, int op)
{ {
int result; int result;
PyObject *v;
if (!PyUnicode_Check(left) || !PyUnicode_Check(right)) if (!PyUnicode_Check(left) || !PyUnicode_Check(right))
Py_RETURN_NOTIMPLEMENTED; Py_RETURN_NOTIMPLEMENTED;
@ -11174,13 +11170,11 @@ PyUnicode_RichCompare(PyObject *left, PyObject *right, int op)
case Py_LE: case Py_LE:
case Py_GE: case Py_GE:
/* a string is equal to itself */ /* a string is equal to itself */
v = Py_True; Py_RETURN_TRUE;
break;
case Py_NE: case Py_NE:
case Py_LT: case Py_LT:
case Py_GT: case Py_GT:
v = Py_False; Py_RETURN_FALSE;
break;
default: default:
PyErr_BadArgument(); PyErr_BadArgument();
return NULL; return NULL;
@ -11189,32 +11183,12 @@ PyUnicode_RichCompare(PyObject *left, PyObject *right, int op)
else if (op == Py_EQ || op == Py_NE) { else if (op == Py_EQ || op == Py_NE) {
result = unicode_compare_eq(left, right); result = unicode_compare_eq(left, right);
result ^= (op == Py_NE); result ^= (op == Py_NE);
v = TEST_COND(result); return PyBool_FromLong(result);
} }
else { else {
result = unicode_compare(left, right); result = unicode_compare(left, right);
Py_RETURN_RICHCOMPARE(result, 0, op);
/* Convert the return value to a Boolean */
switch (op) {
case Py_LE:
v = TEST_COND(result <= 0);
break;
case Py_GE:
v = TEST_COND(result >= 0);
break;
case Py_LT:
v = TEST_COND(result == -1);
break;
case Py_GT:
v = TEST_COND(result == 1);
break;
default:
PyErr_BadArgument();
return NULL;
}
} }
Py_INCREF(v);
return v;
} }
int int