]> granicus.if.org Git - python/commitdiff
Issue 2235: Py3k warnings are now emitted for classes that will no longer inherit...
authorNick Coghlan <ncoghlan@gmail.com>
Mon, 11 Aug 2008 15:45:58 +0000 (15:45 +0000)
committerNick Coghlan <ncoghlan@gmail.com>
Mon, 11 Aug 2008 15:45:58 +0000 (15:45 +0000)
18 files changed:
Lib/UserList.py
Lib/_abcoll.py
Lib/ctypes/test/test_simplesubclasses.py
Lib/numbers.py
Lib/test/test_builtin.py
Lib/test/test_coercion.py
Lib/test/test_collections.py
Lib/test/test_copy.py
Lib/test/test_datetime.py
Lib/test/test_descr.py
Lib/test/test_hash.py
Lib/test/test_operator.py
Lib/test/test_py3kwarn.py
Lib/test/test_slice.py
Lib/test/test_sort.py
Lib/unittest.py
Lib/xml/dom/minidom.py
Objects/typeobject.c

index 556a3273a8a8462cc0901634e27788cc26f55f5b..b44598570790f42374623b357780bd6b131b4089 100644 (file)
@@ -25,6 +25,7 @@ class UserList(collections.MutableSequence):
         else: return other
     def __cmp__(self, other):
         return cmp(self.data, self.__cast(other))
+    __hash__ = None # Mutable sequence, so not hashable
     def __contains__(self, item): return item in self.data
     def __len__(self): return len(self.data)
     def __getitem__(self, i): return self.data[i]
index 85d733f39c5f416c22aefd98eed5982876f93340..a5fee081df5650d4c6b939c8612cb0e52adc51a4 100644 (file)
@@ -207,6 +207,9 @@ class Set(Sized, Iterable, Container):
             other = self._from_iterable(other)
         return (self - other) | (other - self)
 
+    # Sets are not hashable by default, but subclasses can change this
+    __hash__ = None
+
     def _hash(self):
         """Compute the hash value of a set.
 
@@ -350,6 +353,9 @@ class Mapping(Sized, Iterable, Container):
     def values(self):
         return [self[key] for key in self]
 
+    # Mappings are not hashable by default, but subclasses can change this
+    __hash__ = None
+
     def __eq__(self, other):
         return isinstance(other, Mapping) and \
                dict(self.items()) == dict(other.items())
index 71551707421ec496d4af294c8e778420bbb71929..5671cce3320443bb4f93f79488df1f8ab6829c18 100644 (file)
@@ -6,6 +6,8 @@ class MyInt(c_int):
         if type(other) != MyInt:
             return -1
         return cmp(self.value, other.value)
+    def __hash__(self): # Silence Py3k warning
+        return hash(self.value)
 
 class Test(unittest.TestCase):
 
index 38240d62502c41d36c6d2910895f5287c8ce2dd3..fa59fd8e7de93d68a37021269165d27c51649b01 100644 (file)
@@ -18,6 +18,9 @@ class Number(object):
     """
     __metaclass__ = ABCMeta
 
+    # Concrete numeric types must provide their own hash implementation
+    __hash__ = None
+
 
 ## Notes on Decimal
 ## ----------------
index 70980f8924f5e4eef5f02c3ac60134bdaa54c8a9..6671f2c02ebb2611314fcae3c0e5494205e0c0f0 100644 (file)
@@ -1064,6 +1064,7 @@ class BuiltinTest(unittest.TestCase):
         class badzero(int):
             def __cmp__(self, other):
                 raise RuntimeError
+            __hash__ = None # Invalid cmp makes this unhashable
         self.assertRaises(RuntimeError, range, a, a + 1, badzero(1))
 
         # Reject floats when it would require PyLongs to represent.
index e3a7e43737b97baeb4cd2449e2647199416dafac..a70f82d84c46da6ce0a15f3a56aca61c888715d6 100644 (file)
@@ -309,6 +309,7 @@ class CoercionTest(unittest.TestCase):
             def __cmp__(slf, other):
                 self.assert_(other == 42, 'expected evil_coercer, got %r' % other)
                 return 0
+            __hash__ = None # Invalid cmp makes this unhashable
         self.assertEquals(cmp(WackyComparer(), evil_coercer), 0)
         # ...and classic classes too, since that code path is a little different
         class ClassicWackyComparer:
index 99eb8cf9addb623fe33140d665f6167f0d8cd3d0..d689add41d20266b256d92c5f531daad461d42cf 100644 (file)
@@ -172,6 +172,7 @@ class TestOneTrickPonyABCs(unittest.TestCase):
         class H(Hashable):
             def __hash__(self):
                 return super(H, self).__hash__()
+            __eq__ = Hashable.__eq__ # Silence Py3k warning
         self.assertEqual(hash(H()), 0)
         self.failIf(issubclass(int, H))
 
index d2899bd4eab9f1184aeebdd53a94a9805770878c..be334ccf0597fbe54555bbbe44424542943ba0ae 100644 (file)
@@ -435,6 +435,7 @@ class TestCopy(unittest.TestCase):
                 return (C, (), self.__dict__)
             def __cmp__(self, other):
                 return cmp(self.__dict__, other.__dict__)
+            __hash__ = None # Silence Py3k warning
         x = C()
         x.foo = [42]
         y = copy.copy(x)
@@ -451,6 +452,7 @@ class TestCopy(unittest.TestCase):
                 self.__dict__.update(state)
             def __cmp__(self, other):
                 return cmp(self.__dict__, other.__dict__)
+            __hash__ = None # Silence Py3k warning
         x = C()
         x.foo = [42]
         y = copy.copy(x)
@@ -477,6 +479,7 @@ class TestCopy(unittest.TestCase):
             def __cmp__(self, other):
                 return (cmp(list(self), list(other)) or
                         cmp(self.__dict__, other.__dict__))
+            __hash__ = None # Silence Py3k warning
         x = C([[1, 2], 3])
         y = copy.copy(x)
         self.assertEqual(x, y)
@@ -494,6 +497,7 @@ class TestCopy(unittest.TestCase):
             def __cmp__(self, other):
                 return (cmp(dict(self), list(dict)) or
                         cmp(self.__dict__, other.__dict__))
+            __hash__ = None # Silence Py3k warning
         x = C([("foo", [1, 2]), ("bar", 3)])
         y = copy.copy(x)
         self.assertEqual(x, y)
index cdc9eed6b9ce64b81f4cdcebad73ea97b1d7efeb..16749610140bc22d475e63be0d44299346315e7e 100644 (file)
@@ -986,6 +986,7 @@ class TestDate(HarmlessMixedComparison, unittest.TestCase):
                 # compare-by-address (which never says "equal" for distinct
                 # objects).
                 return 0
+            __hash__ = None # Silence Py3k warning
 
         # This still errors, because date and datetime comparison raise
         # TypeError instead of NotImplemented when they don't know what to
index 53b7611fb054c69e455201cc517c4dbb3b00e9b9..f170d591a60556c810196519ff4771dd3975bcf3 100644 (file)
@@ -1121,6 +1121,7 @@ order (MRO) for bases """
         class G(object):
             def __cmp__(self, other):
                 return 0
+            __hash__ = None # Silence Py3k warning
         g = G()
         orig_objects = len(gc.get_objects())
         for i in xrange(10):
@@ -2727,6 +2728,7 @@ order (MRO) for bases """
                     if isinstance(other, int) or isinstance(other, long):
                         return cmp(self.value, other)
                     return NotImplemented
+                __hash__ = None # Silence Py3k warning
 
             c1 = C(1)
             c2 = C(2)
@@ -2755,6 +2757,7 @@ order (MRO) for bases """
                     return abs(self - other) <= 1e-6
                 except:
                     return NotImplemented
+            __hash__ = None # Silence Py3k warning
         zz = ZZ(1.0000003)
         self.assertEqual(zz, 1+0j)
         self.assertEqual(1+0j, zz)
@@ -2767,6 +2770,7 @@ order (MRO) for bases """
                     self.value = int(value)
                 def __cmp__(self_, other):
                     self.fail("shouldn't call __cmp__")
+                __hash__ = None # Silence Py3k warning
                 def __eq__(self, other):
                     if isinstance(other, C):
                         return self.value == other.value
@@ -3262,6 +3266,7 @@ order (MRO) for bases """
         class S(str):
             def __eq__(self, other):
                 return self.lower() == other.lower()
+            __hash__ = None # Silence Py3k warning
 
     def test_subclass_propagation(self):
         # Testing propagation of slot functions to subclasses...
index f3954c2280b22432f50ac759ac0dcb612feb2b38..47c66d1d89dca6f68bfe356224d888c2e156a764 100644 (file)
@@ -52,6 +52,9 @@ class FixedHash(object):
 class OnlyEquality(object):
     def __eq__(self, other):
         return self is other
+    # Trick to suppress Py3k warning in 2.x
+    __hash__ = None
+del OnlyEquality.__hash__
 
 class OnlyInequality(object):
     def __ne__(self, other):
@@ -60,6 +63,9 @@ class OnlyInequality(object):
 class OnlyCmp(object):
     def __cmp__(self, other):
         return cmp(id(self), id(other))
+    # Trick to suppress Py3k warning in 2.x
+    __hash__ = None
+del OnlyCmp.__hash__
 
 class InheritedHashWithEquality(FixedHash, OnlyEquality): pass
 class InheritedHashWithInequality(FixedHash, OnlyInequality): pass
@@ -71,18 +77,15 @@ class NoHash(object):
 class HashInheritanceTestCase(unittest.TestCase):
     default_expected = [object(),
                         DefaultHash(),
+                        OnlyEquality(),
+                        OnlyInequality(),
+                        OnlyCmp(),
                        ]
     fixed_expected = [FixedHash(),
                       InheritedHashWithEquality(),
                       InheritedHashWithInequality(),
                       InheritedHashWithCmp(),
                       ]
-    # TODO: Change these to expecting an exception
-    # when forward porting to Py3k
-    warning_expected = [OnlyEquality(),
-                        OnlyInequality(),
-                        OnlyCmp(),
-                       ]
     error_expected = [NoHash()]
 
     def test_default_hash(self):
@@ -93,20 +96,13 @@ class HashInheritanceTestCase(unittest.TestCase):
         for obj in self.fixed_expected:
             self.assertEqual(hash(obj), _FIXED_HASH_VALUE)
 
-    def test_warning_hash(self):
-        for obj in self.warning_expected:
-            # TODO: Check for the expected Py3k warning here
-            obj_hash = hash(obj)
-            self.assertEqual(obj_hash, _default_hash(obj))
-
     def test_error_hash(self):
         for obj in self.error_expected:
             self.assertRaises(TypeError, hash, obj)
 
     def test_hashable(self):
         objects = (self.default_expected +
-                   self.fixed_expected +
-                   self.warning_expected)
+                   self.fixed_expected)
         for obj in objects:
             self.assert_(isinstance(obj, Hashable), repr(obj))
 
index 1c3fda326fa7a8bbf106e08c1dd0e76e7973803d..9bc0a4ef55e6adca9c0c20129b4fa8b13d0802bb 100644 (file)
@@ -57,6 +57,7 @@ class OperatorTestCase(unittest.TestCase):
         class C(object):
             def __eq__(self, other):
                 raise SyntaxError
+            __hash__ = None # Silence Py3k warning
         self.failUnlessRaises(TypeError, operator.eq)
         self.failUnlessRaises(SyntaxError, operator.eq, C(), C())
         self.failIf(operator.eq(1, 0))
index 67b2538f01a502d50b2f9bc78a6ae2da3cfd27a7..340e86f42dc7d2d0257582737ce4de2976d83ba9 100644 (file)
@@ -12,6 +12,9 @@ if not sys.py3kwarning:
 
 class TestPy3KWarnings(unittest.TestCase):
 
+    def assertWarning(self, _, warning, expected_message):
+        self.assertEqual(str(warning.message), expected_message)
+
     def test_backquote(self):
         expected = 'backquote not supported in 3.x; use repr()'
         with catch_warning() as w:
@@ -28,30 +31,41 @@ class TestPy3KWarnings(unittest.TestCase):
         with catch_warning() as w:
             safe_exec("True = False")
             self.assertWarning(None, w, expected)
+            w.reset()
             safe_exec("False = True")
             self.assertWarning(None, w, expected)
+            w.reset()
             try:
                 safe_exec("obj.False = True")
             except NameError: pass
             self.assertWarning(None, w, expected)
+            w.reset()
             try:
                 safe_exec("obj.True = False")
             except NameError: pass
             self.assertWarning(None, w, expected)
+            w.reset()
             safe_exec("def False(): pass")
             self.assertWarning(None, w, expected)
+            w.reset()
             safe_exec("def True(): pass")
             self.assertWarning(None, w, expected)
+            w.reset()
             safe_exec("class False: pass")
             self.assertWarning(None, w, expected)
+            w.reset()
             safe_exec("class True: pass")
             self.assertWarning(None, w, expected)
+            w.reset()
             safe_exec("def f(True=43): pass")
             self.assertWarning(None, w, expected)
+            w.reset()
             safe_exec("def f(False=None): pass")
             self.assertWarning(None, w, expected)
+            w.reset()
             safe_exec("f(False=True)")
             self.assertWarning(None, w, expected)
+            w.reset()
             safe_exec("f(True=1)")
             self.assertWarning(None, w, expected)
 
@@ -60,20 +74,25 @@ class TestPy3KWarnings(unittest.TestCase):
         expected = 'type inequality comparisons not supported in 3.x'
         with catch_warning() as w:
             self.assertWarning(int < str, w, expected)
+            w.reset()
             self.assertWarning(type < object, w, expected)
 
     def test_object_inequality_comparisons(self):
         expected = 'comparing unequal types not supported in 3.x'
         with catch_warning() as w:
             self.assertWarning(str < [], w, expected)
+            w.reset()
             self.assertWarning(object() < (1, 2), w, expected)
 
     def test_dict_inequality_comparisons(self):
         expected = 'dict inequality comparisons not supported in 3.x'
         with catch_warning() as w:
             self.assertWarning({} < {2:3}, w, expected)
+            w.reset()
             self.assertWarning({} <= {}, w, expected)
+            w.reset()
             self.assertWarning({} > {2:3}, w, expected)
+            w.reset()
             self.assertWarning({2:3} >= {}, w, expected)
 
     def test_cell_inequality_comparisons(self):
@@ -86,6 +105,7 @@ class TestPy3KWarnings(unittest.TestCase):
         cell1, = f(1).func_closure
         with catch_warning() as w:
             self.assertWarning(cell0 == cell1, w, expected)
+            w.reset()
             self.assertWarning(cell0 < cell1, w, expected)
 
     def test_code_inequality_comparisons(self):
@@ -96,8 +116,11 @@ class TestPy3KWarnings(unittest.TestCase):
             pass
         with catch_warning() as w:
             self.assertWarning(f.func_code < g.func_code, w, expected)
+            w.reset()
             self.assertWarning(f.func_code <= g.func_code, w, expected)
+            w.reset()
             self.assertWarning(f.func_code >= g.func_code, w, expected)
+            w.reset()
             self.assertWarning(f.func_code > g.func_code, w, expected)
 
     def test_builtin_function_or_method_comparisons(self):
@@ -107,13 +130,13 @@ class TestPy3KWarnings(unittest.TestCase):
         meth = {}.get
         with catch_warning() as w:
             self.assertWarning(func < meth, w, expected)
+            w.reset()
             self.assertWarning(func > meth, w, expected)
+            w.reset()
             self.assertWarning(meth <= func, w, expected)
+            w.reset()
             self.assertWarning(meth >= func, w, expected)
 
-    def assertWarning(self, _, warning, expected_message):
-        self.assertEqual(str(warning.message), expected_message)
-
     def test_sort_cmp_arg(self):
         expected = "the cmp argument is not supported in 3.x"
         lst = range(5)
@@ -121,8 +144,11 @@ class TestPy3KWarnings(unittest.TestCase):
 
         with catch_warning() as w:
             self.assertWarning(lst.sort(cmp=cmp), w, expected)
+            w.reset()
             self.assertWarning(sorted(lst, cmp=cmp), w, expected)
+            w.reset()
             self.assertWarning(lst.sort(cmp), w, expected)
+            w.reset()
             self.assertWarning(sorted(lst, cmp), w, expected)
 
     def test_sys_exc_clear(self):
@@ -156,7 +182,7 @@ class TestPy3KWarnings(unittest.TestCase):
             self.assertWarning(None, w, expected)
 
     def test_buffer(self):
-        expected = 'buffer() not supported in 3.x; use memoryview()'
+        expected = 'buffer() not supported in 3.x'
         with catch_warning() as w:
             self.assertWarning(buffer('a'), w, expected)
 
@@ -167,6 +193,64 @@ class TestPy3KWarnings(unittest.TestCase):
             with catch_warning() as w:
                 self.assertWarning(f.xreadlines(), w, expected)
 
+    def test_hash_inheritance(self):
+        with catch_warning() as w:
+            # With object as the base class
+            class WarnOnlyCmp(object):
+                def __cmp__(self, other): pass
+            self.assertEqual(len(w.warnings), 1)
+            self.assertWarning(None, w,
+                 "Overriding __cmp__ blocks inheritance of __hash__ in 3.x")
+            w.reset()
+            class WarnOnlyEq(object):
+                def __eq__(self, other): pass
+            self.assertEqual(len(w.warnings), 1)
+            self.assertWarning(None, w,
+                 "Overriding __eq__ blocks inheritance of __hash__ in 3.x")
+            w.reset()
+            class WarnCmpAndEq(object):
+                def __cmp__(self, other): pass
+                def __eq__(self, other): pass
+            self.assertEqual(len(w.warnings), 2)
+            self.assertWarning(None, w.warnings[-2],
+                 "Overriding __cmp__ blocks inheritance of __hash__ in 3.x")
+            self.assertWarning(None, w,
+                 "Overriding __eq__ blocks inheritance of __hash__ in 3.x")
+            w.reset()
+            class NoWarningOnlyHash(object):
+                def __hash__(self): pass
+            self.assertEqual(len(w.warnings), 0)
+            # With an intermediate class in the heirarchy
+            class DefinesAllThree(object):
+                def __cmp__(self, other): pass
+                def __eq__(self, other): pass
+                def __hash__(self): pass
+            class WarnOnlyCmp(DefinesAllThree):
+                def __cmp__(self, other): pass
+            self.assertEqual(len(w.warnings), 1)
+            self.assertWarning(None, w,
+                 "Overriding __cmp__ blocks inheritance of __hash__ in 3.x")
+            w.reset()
+            class WarnOnlyEq(DefinesAllThree):
+                def __eq__(self, other): pass
+            self.assertEqual(len(w.warnings), 1)
+            self.assertWarning(None, w,
+                 "Overriding __eq__ blocks inheritance of __hash__ in 3.x")
+            w.reset()
+            class WarnCmpAndEq(DefinesAllThree):
+                def __cmp__(self, other): pass
+                def __eq__(self, other): pass
+            self.assertEqual(len(w.warnings), 2)
+            self.assertWarning(None, w.warnings[-2],
+                 "Overriding __cmp__ blocks inheritance of __hash__ in 3.x")
+            self.assertWarning(None, w,
+                 "Overriding __eq__ blocks inheritance of __hash__ in 3.x")
+            w.reset()
+            class NoWarningOnlyHash(DefinesAllThree):
+                def __hash__(self): pass
+            self.assertEqual(len(w.warnings), 0)
+
+
 
 class TestStdlibRemovals(unittest.TestCase):
 
index 8c90c10e846180bca45a7c2f780373c0c13249f6..854805b91551000386f4063732ea239d0b8f4d23 100644 (file)
@@ -33,6 +33,7 @@ class SliceTest(unittest.TestCase):
         class BadCmp(object):
             def __eq__(self, other):
                 raise Exc
+            __hash__ = None # Silence Py3k warning
 
         s1 = slice(BadCmp())
         s2 = slice(BadCmp())
index 84c92cc97045458b8014a355588e2f26f5e9d1a6..a61fb96b794f7ef249faf29d08748778e05f0dcb 100644 (file)
@@ -70,6 +70,7 @@ class TestBase(unittest.TestCase):
 
             def __cmp__(self, other):
                 return cmp(self.key, other.key)
+            __hash__ = None # Silence Py3k warning
 
             def __repr__(self):
                 return "Stable(%d, %d)" % (self.key, self.index)
index b5a1a4b8b50f00465206040dee0f65400a4f8c46..09c6ca97c8dc1e4f1a7d8d96af04a6fdd4bd697e 100644 (file)
@@ -425,6 +425,9 @@ class TestSuite:
     def __ne__(self, other):
         return not self == other
 
+    # Can't guarantee hash invariant, so flag as unhashable
+    __hash__ = None
+
     def __iter__(self):
         return iter(self._tests)
 
index 9f7f62c7321b9cead68d2e9d26f64fdb7bd900c5..ad42947238d16d7bc92a4ddf1a4d1d4a219b3739 100644 (file)
@@ -516,6 +516,7 @@ class NamedNodeMap(object):
 
     __len__ = _get_length
 
+    __hash__ = None # Mutable type can't be correctly hashed
     def __cmp__(self, other):
         if self._attrs is getattr(other, "_attrs", None):
             return 0
index 0af3f30de5efe399f527cb30e1f43a6e8aff6d82..42974f8fccaf715626fe359e58c61f2c724a5c65 100644 (file)
@@ -3648,6 +3648,22 @@ inherit_special(PyTypeObject *type, PyTypeObject *base)
                type->tp_flags |= Py_TPFLAGS_DICT_SUBCLASS;
 }
 
+static int
+overrides_name(PyTypeObject *type, char *name)
+{
+       PyObject *dict = type->tp_dict;
+
+       assert(dict != NULL);
+       if (PyDict_GetItemString(dict, name) != NULL) {
+               return 1;
+       }
+       return 0;
+}
+
+#define OVERRIDES_HASH(x)       overrides_name(x, "__hash__")
+#define OVERRIDES_CMP(x)        overrides_name(x, "__cmp__")
+#define OVERRIDES_EQ(x)         overrides_name(x, "__eq__")
+
 static void
 inherit_slots(PyTypeObject *type, PyTypeObject *base)
 {
@@ -3786,6 +3802,25 @@ inherit_slots(PyTypeObject *type, PyTypeObject *base)
                        type->tp_compare = base->tp_compare;
                        type->tp_richcompare = base->tp_richcompare;
                        type->tp_hash = base->tp_hash;
+                       /* Check for changes to inherited methods in Py3k*/
+                       if (Py_Py3kWarningFlag) {
+                               if (base->tp_hash &&
+                                               (base->tp_hash != PyObject_HashNotImplemented) &&
+                                               !OVERRIDES_HASH(type)) {
+                                       if (OVERRIDES_CMP(type)) {
+                                               PyErr_WarnPy3k("Overriding "
+                                                 "__cmp__ blocks inheritance "
+                                                 "of __hash__ in 3.x",
+                                                 1);
+                                       }
+                                       if (OVERRIDES_EQ(type)) {
+                                               PyErr_WarnPy3k("Overriding "
+                                                 "__eq__ blocks inheritance "
+                                                 "of __hash__ in 3.x",
+                                                 1);
+                                       }
+                               }
+                       }
                }
        }
        else {