Commit b7097770 authored by Kevin Modzelewski's avatar Kevin Modzelewski

cmp() builtin

parent 9ebfc95f
......@@ -553,7 +553,7 @@ PyAPI_FUNC(int) PyNumber_CoerceEx(PyObject **, PyObject **) PYSTON_NOEXCEPT;
PyAPI_FUNC(void) PyObject_ClearWeakRefs(PyObject *) PYSTON_NOEXCEPT;
/* A slot function whose address we need to compare */
extern int _PyObject_SlotCompare(PyObject *, PyObject *);
extern int _PyObject_SlotCompare(PyObject *, PyObject *) PYSTON_NOEXCEPT;
/* Same as PyObject_Generic{Get,Set}Attr, but passing the attributes
dict as the last parameter. */
PyAPI_FUNC(PyObject *)
......
......@@ -27,6 +27,31 @@
namespace pyston {
static PyObject* type_error(const char* msg, PyObject* obj) noexcept {
PyErr_Format(PyExc_TypeError, msg, Py_TYPE(obj)->tp_name);
return NULL;
}
static PyObject* null_error(void) noexcept {
if (!PyErr_Occurred())
PyErr_SetString(PyExc_SystemError, "null argument to internal routine");
return NULL;
}
extern "C" int PyObject_Cmp(PyObject* o1, PyObject* o2, int* result) noexcept {
int r;
if (o1 == NULL || o2 == NULL) {
null_error();
return -1;
}
r = PyObject_Compare(o1, o2);
if (PyErr_Occurred())
return -1;
*result = r;
return 0;
}
extern "C" Py_ssize_t _PyObject_LengthHint(PyObject* o, Py_ssize_t defaultvalue) noexcept {
static PyObject* hintstrobj = NULL;
PyObject* ro, *hintmeth;
......@@ -227,17 +252,6 @@ extern "C" void PyBuffer_Release(Py_buffer* view) noexcept {
view->obj = NULL;
}
static PyObject* type_error(const char* msg, PyObject* obj) noexcept {
PyErr_Format(PyExc_TypeError, msg, Py_TYPE(obj)->tp_name);
return NULL;
}
static PyObject* null_error(void) noexcept {
if (!PyErr_Occurred())
PyErr_SetString(PyExc_SystemError, "null argument to internal routine");
return NULL;
}
static PyObject* objargs_mktuple(va_list va) noexcept {
int i, n = 0;
va_list countva;
......@@ -1516,9 +1530,28 @@ extern "C" int PyNumber_Coerce(PyObject**, PyObject**) noexcept {
return -1;
}
extern "C" int PyNumber_CoerceEx(PyObject**, PyObject**) noexcept {
fatalOrError(PyExc_NotImplementedError, "unimplemented");
return -1;
extern "C" int PyNumber_CoerceEx(PyObject** pv, PyObject** pw) noexcept {
PyObject* v = *pv;
PyObject* w = *pw;
int res;
/* Shortcut only for old-style types */
if (v->cls == w->cls && !PyType_HasFeature(v->cls, Py_TPFLAGS_CHECKTYPES)) {
Py_INCREF(v);
Py_INCREF(w);
return 0;
}
if (v->cls->tp_as_number && v->cls->tp_as_number->nb_coerce) {
res = (*v->cls->tp_as_number->nb_coerce)(pv, pw);
if (res <= 0)
return res;
}
if (w->cls->tp_as_number && w->cls->tp_as_number->nb_coerce) {
res = (*w->cls->tp_as_number->nb_coerce)(pw, pv);
if (res <= 0)
return res;
}
return 1;
}
extern "C" PyObject* _PyNumber_ConvertIntegralToInt(PyObject* integral, const char* error_format) noexcept {
......
......@@ -472,31 +472,6 @@ extern "C" int PyObject_AsWriteBuffer(PyObject* obj, void** buffer, Py_ssize_t*
Py_FatalError("unimplemented");
}
/* Return -1 if error; 1 if v op w; 0 if not (v op w). */
extern "C" int PyObject_RichCompareBool(PyObject* v, PyObject* w, int op) noexcept {
PyObject* res;
int ok;
/* Quick result when objects are the same.
Guarantees that identity implies equality. */
if (v == w) {
if (op == Py_EQ)
return 1;
else if (op == Py_NE)
return 0;
}
res = PyObject_RichCompare(v, w, op);
if (res == NULL)
return -1;
if (PyBool_Check(res))
ok = (res == Py_True);
else
ok = PyObject_IsTrue(res);
Py_DECREF(res);
return ok;
}
// I'm not sure how we can support this one:
extern "C" PyObject** _PyObject_GetDictPtr(PyObject* obj) noexcept {
fatalOrError(PyExc_NotImplementedError, "unimplemented");
......@@ -564,10 +539,443 @@ extern "C" void Py_ReprLeave(PyObject* obj) noexcept {
}
}
extern "C" int PyObject_Compare(PyObject* o1, PyObject* o2) noexcept {
fatalOrError(PyExc_NotImplementedError, "unimplemented");
// 'On error, the value returned is undefined; use PyErr_Occurred() to detect an error.'
// - https://docs.python.org/2/c-api/object.html
return 0xdeadbeef;
/* 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) {
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)
/* Map rich comparison operators to their swapped version, e.g. LT --> GT */
extern "C" {
int _Py_SwappedOp[] = { Py_GT, Py_GE, Py_EQ, Py_NE, Py_LT, Py_LE };
}
/* 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) {
richcmpfunc f;
PyObject* res;
if (v->cls != w->cls && PyType_IsSubtype(w->cls, v->cls) && (f = RICHCOMPARE(w->cls)) != NULL) {
res = (*f)(w, v, _Py_SwappedOp[op]);
if (res != Py_NotImplemented)
return res;
Py_DECREF(res);
}
if ((f = RICHCOMPARE(v->cls)) != NULL) {
res = (*f)(v, w, op);
if (res != Py_NotImplemented)
return res;
Py_DECREF(res);
}
if ((f = RICHCOMPARE(w->cls)) != NULL) {
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) {
PyObject* res;
int ok;
if (RICHCOMPARE(v->cls) == NULL && RICHCOMPARE(w->cls) == 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) {
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->cls) == NULL && RICHCOMPARE(w->cls) == 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) {
int c;
cmpfunc f;
/* Comparisons involving instances are given to instance_compare,
which has the same return conventions as this function. */
f = v->cls->tp_compare;
if (PyInstance_Check(v))
return (*f)(v, w);
if (PyInstance_Check(w))
return (*w->cls->tp_compare)(v, w);
/* If both have the same (non-NULL) tp_compare, use it. */
if (f != NULL && f == w->cls->tp_compare) {
c = (*f)(v, w);
return adjust_tp_compare(c);
}
/* If either tp_compare is _PyObject_SlotCompare, that's safe. */
if (f == _PyObject_SlotCompare || w->cls->tp_compare == _PyObject_SlotCompare)
return _PyObject_SlotCompare(v, w);
/* 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->cls->tp_compare;
if (f != NULL && f == w->cls->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) {
int c;
const char* vname, *wname;
if (v->cls == w->cls) {
/* 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->cls->tp_name;
if (PyNumber_Check(w))
wname = "";
else
wname = w->cls->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->cls) < (Py_uintptr_t)(w->cls)) ? -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) {
int c;
cmpfunc f;
if (v->cls == w->cls && (f = v->cls->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);
}
/* Compare v to w. Return
-1 if v < w or exception (PyErr_Occurred() true in latter case).
0 if v == w.
1 if v > w.
XXX The docs (C API manual) say the return value is undefined in case
XXX of error.
*/
extern "C" int PyObject_Compare(PyObject* v, PyObject* w) noexcept {
int result;
if (v == NULL || w == NULL) {
PyErr_BadInternalCall();
return -1;
}
if (v == w)
return 0;
if (Py_EnterRecursiveCall(" in cmp"))
return -1;
result = do_cmp(v, w);
Py_LeaveRecursiveCall();
return result < 0 ? -1 : result;
}
/* Return (new reference to) Py_True or Py_False. */
static PyObject* convert_3way_to_object(int op, int c) noexcept {
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) noexcept {
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->cls != w->cls && 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) noexcept {
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).
*/
extern "C" PyObject* PyObject_RichCompare(PyObject* v, PyObject* w, int op) noexcept {
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->cls == w->cls && !PyInstance_Check(v)) {
cmpfunc fcmp;
richcmpfunc frich = RICHCOMPARE(v->cls);
/* 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) {
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->cls->tp_compare;
if (fcmp != NULL) {
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;
}
/* Return -1 if error; 1 if v op w; 0 if not (v op w). */
extern "C" int PyObject_RichCompareBool(PyObject* v, PyObject* w, int op) noexcept {
PyObject* res;
int ok;
/* Quick result when objects are the same.
Guarantees that identity implies equality. */
if (v == w) {
if (op == Py_EQ)
return 1;
else if (op == Py_NE)
return 0;
}
res = PyObject_RichCompare(v, w, op);
if (res == NULL)
return -1;
if (PyBool_Check(res))
ok = (res == Py_True);
else
ok = PyObject_IsTrue(res);
Py_DECREF(res);
return ok;
}
}
......@@ -461,6 +461,26 @@ static PyObject* wrap_delitem(PyObject* self, PyObject* args, void* wrapped) noe
return Py_None;
}
static PyObject* wrap_cmpfunc(PyObject* self, PyObject* args, void* wrapped) noexcept {
cmpfunc func = (cmpfunc)wrapped;
int res;
PyObject* other;
if (!check_num_args(args, 1))
return NULL;
other = PyTuple_GET_ITEM(args, 0);
if (Py_TYPE(other)->tp_compare != func && !PyType_IsSubtype(Py_TYPE(other), Py_TYPE(self))) {
PyErr_Format(PyExc_TypeError, "%s.__cmp__(x,y) requires y to be a '%s', not a '%s'", Py_TYPE(self)->tp_name,
Py_TYPE(self)->tp_name, Py_TYPE(other)->tp_name);
return NULL;
}
res = (*func)(self, other);
if (PyErr_Occurred())
return NULL;
return PyInt_FromLong((long)res);
}
static PyObject* wrap_init(PyObject* self, PyObject* args, void* wrapped, PyObject* kwds) noexcept {
initproc func = (initproc)wrapped;
......@@ -565,7 +585,57 @@ static PyObject* call_maybe(PyObject* o, const char* name, PyObject** nameobj, c
return retval;
}
PyObject* slot_tp_repr(PyObject* self) noexcept {
static int half_compare(PyObject* self, PyObject* other) noexcept {
PyObject* func, *args, *res;
static PyObject* cmp_str;
Py_ssize_t c;
func = lookup_method(self, "__cmp__", &cmp_str);
if (func == NULL) {
PyErr_Clear();
} else {
args = PyTuple_Pack(1, other);
if (args == NULL)
res = NULL;
else {
res = PyObject_Call(func, args, NULL);
Py_DECREF(args);
}
Py_DECREF(func);
if (res != Py_NotImplemented) {
if (res == NULL)
return -2;
c = PyInt_AsLong(res);
Py_DECREF(res);
if (c == -1 && PyErr_Occurred())
return -2;
return (c < 0) ? -1 : (c > 0) ? 1 : 0;
}
Py_DECREF(res);
}
return 2;
}
/* This slot is published for the benefit of try_3way_compare in object.c */
extern "C" int _PyObject_SlotCompare(PyObject* self, PyObject* other) noexcept {
int c;
if (Py_TYPE(self)->tp_compare == _PyObject_SlotCompare) {
c = half_compare(self, other);
if (c <= 1)
return c;
}
if (Py_TYPE(other)->tp_compare == _PyObject_SlotCompare) {
c = half_compare(other, self);
if (c < -1)
return -2;
if (c <= 1)
return -c;
}
return (void*)self < (void*)other ? -1 : (void*)self > (void*)other ? 1 : 0;
}
static PyObject* slot_tp_repr(PyObject* self) noexcept {
try {
return repr(self);
} catch (ExcInfo e) {
......@@ -574,7 +644,7 @@ PyObject* slot_tp_repr(PyObject* self) noexcept {
}
}
PyObject* slot_tp_str(PyObject* self) noexcept {
static PyObject* slot_tp_str(PyObject* self) noexcept {
try {
return str(self);
} catch (ExcInfo e) {
......@@ -1264,6 +1334,7 @@ static slotdef slotdefs[]
TPSLOT("__getattr__", tp_getattr, NULL, NULL, ""),
TPSLOT("__setattr__", tp_setattr, NULL, NULL, ""),
TPSLOT("__delattr__", tp_setattr, NULL, NULL, ""),
TPSLOT("__cmp__", tp_compare, _PyObject_SlotCompare, wrap_cmpfunc, "x.__cmp__(y) <==> cmp(x,y)"),
TPSLOT("__repr__", tp_repr, slot_tp_repr, wrap_unaryfunc, "x.__repr__() <==> repr(x)"),
TPSLOT("__hash__", tp_hash, slot_tp_hash, wrap_hashfunc, "x.__hash__() <==> hash(x)"),
......
......@@ -969,9 +969,11 @@ Box* builtinRound(Box* _number, Box* _ndigits) {
throwCAPIException();
}
Box* builtinCmp(Box* lhs, Box* rhs) {
fatalOrError(PyExc_NotImplementedError, "unimplemented");
throwCAPIException();
Box* builtinCmp(Box* a, Box* b) {
int c;
if (PyObject_Cmp(a, b, &c) < 0)
throwCAPIException();
return PyInt_FromLong((long)c);
}
Box* builtinApply(Box* func, Box* args, Box* keywords) {
......
......@@ -306,44 +306,6 @@ extern "C" int PyObject_DelItem(PyObject* o, PyObject* key) noexcept {
return -1;
}
extern "C" PyObject* PyObject_RichCompare(PyObject* o1, PyObject* o2, int opid) noexcept {
int translated_op;
switch (opid) {
case Py_LT:
translated_op = AST_TYPE::Lt;
break;
case Py_LE:
translated_op = AST_TYPE::LtE;
break;
case Py_EQ:
translated_op = AST_TYPE::Eq;
break;
case Py_NE:
translated_op = AST_TYPE::NotEq;
break;
case Py_GT:
translated_op = AST_TYPE::Gt;
break;
case Py_GE:
translated_op = AST_TYPE::GtE;
break;
default:
fatalOrError(PyExc_NotImplementedError, "unimplemented");
return nullptr;
};
try {
return compare(o1, o2, translated_op);
} catch (ExcInfo e) {
setCAPIException(e);
return NULL;
}
}
extern "C" {
int _Py_SwappedOp[] = { Py_GT, Py_GE, Py_EQ, Py_NE, Py_LT, Py_LE };
}
extern "C" long PyObject_Hash(PyObject* o) noexcept {
try {
return hash(o)->n;
......
......@@ -325,6 +325,7 @@ BoxedClass::BoxedClass(BoxedClass* base, gcvisit_func gc_visit, int attrs_offset
tp_flags |= Py_TPFLAGS_HAVE_CLASS;
tp_flags |= Py_TPFLAGS_HAVE_GC;
tp_flags |= Py_TPFLAGS_HAVE_WEAKREFS;
tp_flags |= Py_TPFLAGS_HAVE_RICHCOMPARE;
if (base && (base->tp_flags & Py_TPFLAGS_HAVE_NEWBUFFER))
tp_flags |= Py_TPFLAGS_HAVE_NEWBUFFER;
......
class C(object):
def __eq__(self, rhs):
print "eq", type(self), type(rhs)
return False
def __lt__(self, rhs):
print "lt", type(self), type(rhs)
return False
def __gt__(self, rhs):
print "gt", type(self), type(rhs)
return True
class D(C):
def __cmp__(self, rhs):
print "cmp", type(self), type(rhs)
return 0
l = [C(), D()]
for lhs in l:
for rhs in l:
r = cmp(lhs, rhs)
print type(lhs), type(rhs), r
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment