summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Lib/test/test_coercion.py36
-rw-r--r--Objects/object.c27
2 files changed, 47 insertions, 16 deletions
diff --git a/Lib/test/test_coercion.py b/Lib/test/test_coercion.py
index 331771f..37d735b 100644
--- a/Lib/test/test_coercion.py
+++ b/Lib/test/test_coercion.py
@@ -125,9 +125,45 @@ def do_prefix_binops():
else:
print '=', format_result(x)
+# New-style class version of CoerceNumber
+class CoerceTo(object):
+ def __init__(self, arg):
+ self.arg = arg
+ def __coerce__(self, other):
+ if isinstance(other, CoerceTo):
+ return self.arg, other.arg
+ else:
+ return self.arg, other
+
+def assert_(expr, msg=None):
+ if not expr:
+ raise AssertionError, msg
+
+def do_cmptypes():
+ # Built-in tp_compare slots expect their arguments to have the
+ # same type, but a user-defined __coerce__ doesn't have to obey.
+ # SF #980352
+ evil_coercer = CoerceTo(42)
+ # Make sure these don't crash any more
+ assert_(cmp(u'fish', evil_coercer) != 0)
+ assert_(cmp(slice(1), evil_coercer) != 0)
+ # ...but that this still works
+ class WackyComparer(object):
+ def __cmp__(self, other):
+ assert_(other == 42, 'expected evil_coercer, got %r' % other)
+ return 0
+ assert_(cmp(WackyComparer(), evil_coercer) == 0)
+ # ...and classic classes too, since that code path is a little different
+ class ClassicWackyComparer:
+ def __cmp__(self, other):
+ assert_(other == 42, 'expected evil_coercer, got %r' % other)
+ return 0
+ assert_(cmp(ClassicWackyComparer(), evil_coercer) == 0)
+
warnings.filterwarnings("ignore",
r'complex divmod\(\), // and % are deprecated',
DeprecationWarning,
r'test.test_coercion$')
do_infix_binops()
do_prefix_binops()
+do_cmptypes()
diff --git a/Objects/object.c b/Objects/object.c
index 3f70009..d86d74f 100644
--- a/Objects/object.c
+++ b/Objects/object.c
@@ -606,33 +606,28 @@ try_3way_compare(PyObject *v, PyObject *w)
w->ob_type->tp_compare == _PyObject_SlotCompare)
return _PyObject_SlotCompare(v, w);
- /* Try coercion; if it fails, give up */
+ /* 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;
-
- /* Try v's comparison, if defined */
- if ((f = v->ob_type->tp_compare) != NULL) {
+ 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);
}
- /* Try w's comparison, if defined */
- if ((f = w->ob_type->tp_compare) != NULL) {
- c = (*f)(w, v); /* swapped! */
- Py_DECREF(v);
- Py_DECREF(w);
- c = adjust_tp_compare(c);
- if (c >= -1)
- return -c; /* Swapped! */
- else
- return c;
- }
-
/* No comparison defined */
Py_DECREF(v);
Py_DECREF(w);