Skip to content

Instantly share code, notes, and snippets.

@MSeifert04
Created January 12, 2017 03:49
Show Gist options
  • Save MSeifert04/acec8515090a4217d1b87d4ff092f459 to your computer and use it in GitHub Desktop.
Save MSeifert04/acec8515090a4217d1b87d4ff092f459 to your computer and use it in GitHub Desktop.
/* Helper to warn about deprecated tp_compare return values. Return:
-2 for an exception;
-1 if v < w;
0 if v == w;
1 if v > w.
(This function cannot return 2.)
*/
static int
adjust_tp_compare(int c)
{
printf("adjust_tp_compare\n");
if (PyErr_Occurred()) {
if (c != -1 && c != -2) {
PyObject *t, *v, *tb;
PyErr_Fetch(&t, &v, &tb);
if (PyErr_Warn(PyExc_RuntimeWarning,
"tp_compare didn't return -1 or -2 "
"for exception") < 0) {
Py_XDECREF(t);
Py_XDECREF(v);
Py_XDECREF(tb);
}
else
PyErr_Restore(t, v, tb);
}
return -2;
}
else if (c < -1 || c > 1) {
if (PyErr_Warn(PyExc_RuntimeWarning,
"tp_compare didn't return -1, 0 or 1") < 0)
return -2;
else
return c < -1 ? -1 : 1;
}
else {
assert(c >= -1 && c <= 1);
return c;
}
}
/* Macro to get the tp_richcompare field of a type if defined */
#define RICHCOMPARE(t) (PyType_HasFeature((t), Py_TPFLAGS_HAVE_RICHCOMPARE) \
? (t)->tp_richcompare : NULL)
/* Try a genuine rich comparison, returning an object. Return:
NULL for exception;
NotImplemented if this particular rich comparison is not implemented or
undefined;
some object not equal to NotImplemented if it is implemented
(this latter object may not be a Boolean).
*/
static PyObject *
try_rich_compare(PyObject *v, PyObject *w, int op)
{
printf("try_rich_compare\n");
richcmpfunc f;
PyObject *res;
if (v->ob_type != w->ob_type &&
PyType_IsSubtype(w->ob_type, v->ob_type) &&
(f = RICHCOMPARE(w->ob_type)) != NULL) {
printf("try_rich_compare - 1\n");
res = (*f)(w, v, _Py_SwappedOp[op]);
if (res != Py_NotImplemented)
return res;
Py_DECREF(res);
}
if ((f = RICHCOMPARE(v->ob_type)) != NULL) {
printf("try_rich_compare - 2\n");
res = (*f)(v, w, op);
if (res != Py_NotImplemented)
return res;
Py_DECREF(res);
}
if ((f = RICHCOMPARE(w->ob_type)) != NULL) {
printf("try_rich_compare - 3\n");
return (*f)(w, v, _Py_SwappedOp[op]);
}
res = Py_NotImplemented;
Py_INCREF(res);
return res;
}
/* Try a genuine rich comparison, returning an int. Return:
-1 for exception (including the case where try_rich_compare() returns an
object that's not a Boolean);
0 if the outcome is false;
1 if the outcome is true;
2 if this particular rich comparison is not implemented or undefined.
*/
static int
try_rich_compare_bool(PyObject *v, PyObject *w, int op)
{
printf("try_rich_compare_bool\n");
PyObject *res;
int ok;
if (RICHCOMPARE(v->ob_type) == NULL && RICHCOMPARE(w->ob_type) == NULL)
return 2; /* Shortcut, avoid INCREF+DECREF */
res = try_rich_compare(v, w, op);
if (res == NULL)
return -1;
if (res == Py_NotImplemented) {
Py_DECREF(res);
return 2;
}
ok = PyObject_IsTrue(res);
Py_DECREF(res);
return ok;
}
/* Try rich comparisons to determine a 3-way comparison. Return:
-2 for an exception;
-1 if v < w;
0 if v == w;
1 if v > w;
2 if this particular rich comparison is not implemented or undefined.
*/
static int
try_rich_to_3way_compare(PyObject *v, PyObject *w)
{
printf("try_rich_to_3way_compare\n");
static struct { int op; int outcome; } tries[3] = {
/* Try this operator, and if it is true, use this outcome: */
{Py_EQ, 0},
{Py_LT, -1},
{Py_GT, 1},
};
int i;
if (RICHCOMPARE(v->ob_type) == NULL && RICHCOMPARE(w->ob_type) == NULL)
return 2; /* Shortcut */
for (i = 0; i < 3; i++) {
switch (try_rich_compare_bool(v, w, tries[i].op)) {
case -1:
return -2;
case 1:
return tries[i].outcome;
}
}
return 2;
}
/* Try a 3-way comparison, returning an int. Return:
-2 for an exception;
-1 if v < w;
0 if v == w;
1 if v > w;
2 if this particular 3-way comparison is not implemented or undefined.
*/
static int
try_3way_compare(PyObject *v, PyObject *w)
{
printf("try_3way_compare\n");
int c;
cmpfunc f;
/* Comparisons involving instances are given to instance_compare,
which has the same return conventions as this function. */
f = v->ob_type->tp_compare;
if (PyInstance_Check(v))
return (*f)(v, w);
if (PyInstance_Check(w))
return (*w->ob_type->tp_compare)(v, w);
/* If both have the same (non-NULL) tp_compare, use it. */
if (f != NULL && f == w->ob_type->tp_compare) {
c = (*f)(v, w);
return adjust_tp_compare(c);
}
/* If we're here, v and w,
a) are not instances;
b) have different types or a type without tp_compare; and
c) don't have a user-defined tp_compare.
tp_compare implementations in C assume that both arguments
have their type, so we give up if the coercion fails or if
it yields types which are still incompatible (which can
happen with a user-defined nb_coerce).
*/
c = PyNumber_CoerceEx(&v, &w);
if (c < 0)
return -2;
if (c > 0)
return 2;
f = v->ob_type->tp_compare;
if (f != NULL && f == w->ob_type->tp_compare) {
c = (*f)(v, w);
Py_DECREF(v);
Py_DECREF(w);
return adjust_tp_compare(c);
}
/* No comparison defined */
Py_DECREF(v);
Py_DECREF(w);
return 2;
}
/* Final fallback 3-way comparison, returning an int. Return:
-2 if an error occurred;
-1 if v < w;
0 if v == w;
1 if v > w.
*/
static int
default_3way_compare(PyObject *v, PyObject *w)
{
printf("default_3way_compare\n");
int c;
const char *vname, *wname;
if (v->ob_type == w->ob_type) {
/* When comparing these pointers, they must be cast to
* integer types (i.e. Py_uintptr_t, our spelling of C9X's
* uintptr_t). ANSI specifies that pointer compares other
* than == and != to non-related structures are undefined.
*/
Py_uintptr_t vv = (Py_uintptr_t)v;
Py_uintptr_t ww = (Py_uintptr_t)w;
return (vv < ww) ? -1 : (vv > ww) ? 1 : 0;
}
/* None is smaller than anything */
if (v == Py_None)
return -1;
if (w == Py_None)
return 1;
/* different type: compare type names; numbers are smaller */
if (PyNumber_Check(v))
vname = "";
else
vname = v->ob_type->tp_name;
if (PyNumber_Check(w))
wname = "";
else
wname = w->ob_type->tp_name;
c = strcmp(vname, wname);
if (c < 0)
return -1;
if (c > 0)
return 1;
/* Same type name, or (more likely) incomparable numeric types */
return ((Py_uintptr_t)(v->ob_type) < (
Py_uintptr_t)(w->ob_type)) ? -1 : 1;
}
/* Do a 3-way comparison, by hook or by crook. Return:
-2 for an exception (but see below);
-1 if v < w;
0 if v == w;
1 if v > w;
BUT: if the object implements a tp_compare function, it returns
whatever this function returns (whether with an exception or not).
*/
static int
do_cmp(PyObject *v, PyObject *w)
{
printf("do_cmp\n");
int c;
cmpfunc f;
if (v->ob_type == w->ob_type
&& (f = v->ob_type->tp_compare) != NULL) {
c = (*f)(v, w);
if (PyInstance_Check(v)) {
/* Instance tp_compare has a different signature.
But if it returns undefined we fall through. */
if (c != 2)
return c;
/* Else fall through to try_rich_to_3way_compare() */
}
else
return adjust_tp_compare(c);
}
/* We only get here if one of the following is true:
a) v and w have different types
b) v and w have the same type, which doesn't have tp_compare
c) v and w are instances, and either __cmp__ is not defined or
__cmp__ returns NotImplemented
*/
c = try_rich_to_3way_compare(v, w);
if (c < 2)
return c;
c = try_3way_compare(v, w);
if (c < 2)
return c;
return default_3way_compare(v, w);
}
/* Return (new reference to) Py_True or Py_False. */
static PyObject *
convert_3way_to_object(int op, int c)
{
printf("convert_3way_to_object\n");
PyObject *result;
switch (op) {
case Py_LT: c = c < 0; break;
case Py_LE: c = c <= 0; break;
case Py_EQ: c = c == 0; break;
case Py_NE: c = c != 0; break;
case Py_GT: c = c > 0; break;
case Py_GE: c = c >= 0; break;
}
result = c ? Py_True : Py_False;
Py_INCREF(result);
return result;
}
/* We want a rich comparison but don't have one. Try a 3-way cmp instead.
Return
NULL if error
Py_True if v op w
Py_False if not (v op w)
*/
static PyObject *
try_3way_to_rich_compare(PyObject *v, PyObject *w, int op)
{
printf("try_3way_to_rich_compare\n");
int c;
c = try_3way_compare(v, w);
if (c >= 2) {
/* Py3K warning if types are not equal and comparison isn't == or != */
if (Py_Py3kWarningFlag &&
v->ob_type != w->ob_type && op != Py_EQ && op != Py_NE &&
PyErr_WarnEx(PyExc_DeprecationWarning,
"comparing unequal types not supported "
"in 3.x", 1) < 0) {
return NULL;
}
c = default_3way_compare(v, w);
}
if (c <= -2)
return NULL;
return convert_3way_to_object(op, c);
}
/* Do rich comparison on v and w. Return
NULL if error
Else a new reference to an object other than Py_NotImplemented, usually(?):
Py_True if v op w
Py_False if not (v op w)
*/
static PyObject *
do_richcmp(PyObject *v, PyObject *w, int op)
{
printf("do_richcmp\n");
PyObject *res;
res = try_rich_compare(v, w, op);
if (res != Py_NotImplemented)
return res;
Py_DECREF(res);
return try_3way_to_rich_compare(v, w, op);
}
/* Return:
NULL for exception;
some object not equal to NotImplemented if it is implemented
(this latter object may not be a Boolean).
*/
PyObject *
otherrichcompare(PyObject *v, PyObject *w, int op)
{
printf("otherrichcompare\n");
PyObject *res;
assert(Py_LT <= op && op <= Py_GE);
if (Py_EnterRecursiveCall(" in cmp"))
return NULL;
/* If the types are equal, and not old-style instances, try to
get out cheap (don't bother with coercions etc.). */
if (v->ob_type == w->ob_type && !PyInstance_Check(v)) {
cmpfunc fcmp;
richcmpfunc frich = RICHCOMPARE(v->ob_type);
/* If the type has richcmp, try it first. try_rich_compare
tries it two-sided, which is not needed since we've a
single type only. */
if (frich != NULL) {
printf("otherrichcompare - 1\n");
res = (*frich)(v, w, op);
if (res != Py_NotImplemented)
goto Done;
Py_DECREF(res);
}
/* No richcmp, or this particular richmp not implemented.
Try 3-way cmp. */
fcmp = v->ob_type->tp_compare;
if (fcmp != NULL) {
printf("otherrichcompare - 2\n");
int c = (*fcmp)(v, w);
c = adjust_tp_compare(c);
if (c == -2) {
res = NULL;
goto Done;
}
res = convert_3way_to_object(op, c);
goto Done;
}
}
/* Fast path not taken, or couldn't deliver a useful result. */
res = do_richcmp(v, w, op);
Done:
Py_LeaveRecursiveCall();
return res;
}
static PyObject *
myrichcmp(PyObject *m, PyObject *args)
{
PyObject *op1, *op2;
if (PyArg_UnpackTuple(args, "richcomp\n", 2, 2, &op1, &op2)) {
return otherrichcompare(op1, op2, Py_EQ);
} else {
return NULL;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment