summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNick Coghlan <ncoghlan@gmail.com>2008-12-30 01:18:48 (GMT)
committerNick Coghlan <ncoghlan@gmail.com>2008-12-30 01:18:48 (GMT)
commit180e4007661f4480c3be50289748526c2647d28e (patch)
tree9c1da39d98c91ebb08054ddb19d608596b19b498
parentc13acb18bc2db9d8824ba94b86d8e4c8909d6b6c (diff)
downloadcpython-180e4007661f4480c3be50289748526c2647d28e.zip
cpython-180e4007661f4480c3be50289748526c2647d28e.tar.gz
cpython-180e4007661f4480c3be50289748526c2647d28e.tar.bz2
Issue #4701: implicitly call PyType_Ready from PyObject_Hash
-rw-r--r--Lib/test/test_hash.py25
-rw-r--r--Misc/NEWS3
-rw-r--r--Modules/_testcapimodule.c101
-rw-r--r--Objects/object.c11
4 files changed, 139 insertions, 1 deletions
diff --git a/Lib/test/test_hash.py b/Lib/test/test_hash.py
index 47c66d1..7ce40b9 100644
--- a/Lib/test/test_hash.py
+++ b/Lib/test/test_hash.py
@@ -111,9 +111,32 @@ class HashInheritanceTestCase(unittest.TestCase):
self.assertFalse(isinstance(obj, Hashable), repr(obj))
+# Issue #4701: Check that some builtin types are correctly hashable
+# (This test only used to fail in Python 3.0, but has been included
+# in 2.x along with the lazy call to PyType_Ready in PyObject_Hash)
+class DefaultIterSeq(object):
+ seq = range(10)
+ def __len__(self):
+ return len(self.seq)
+ def __getitem__(self, index):
+ return self.seq[index]
+
+class HashBuiltinsTestCase(unittest.TestCase):
+ hashes_to_check = [xrange(10),
+ enumerate(xrange(10)),
+ iter(DefaultIterSeq()),
+ iter(lambda: 0, 0),
+ ]
+
+ def test_hashes(self):
+ _default_hash = object.__hash__
+ for obj in self.hashes_to_check:
+ self.assertEqual(hash(obj), _default_hash(obj))
+
def test_main():
test_support.run_unittest(HashEqualityTestCase,
- HashInheritanceTestCase)
+ HashInheritanceTestCase,
+ HashBuiltinsTestCase)
if __name__ == "__main__":
diff --git a/Misc/NEWS b/Misc/NEWS
index 539a41a..d5dec85 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -12,6 +12,9 @@ What's New in Python 2.7 alpha 1
Core and Builtins
-----------------
+- Issue #4701: PyObject_Hash now implicitly calls PyType_Ready on types
+ where the tp_hash and tp_dict slots are both NULL.
+
- Issue #4764: With io.open, IOError.filename is set when trying to open a
directory on POSIX systems.
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index d640c39..bd2f211 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -173,6 +173,106 @@ test_dict_iteration(PyObject* self)
}
+/* Issue #4701: Check that PyObject_Hash implicitly calls
+ * PyType_Ready if it hasn't already been called
+ */
+static PyTypeObject _HashInheritanceTester_Type = {
+ PyObject_HEAD_INIT(&PyType_Type)
+ 0, /* Number of items for varobject */
+ "hashinheritancetester", /* Name of this type */
+ sizeof(PyObject), /* Basic object size */
+ 0, /* Item size for varobject */
+ (destructor)PyObject_Del, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_compare */
+ 0, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ PyObject_GenericGetAttr, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT, /* tp_flags */
+ 0, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ 0, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ 0, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+ PyType_GenericNew, /* tp_new */
+};
+
+static PyObject*
+test_lazy_hash_inheritance(PyObject* self)
+{
+ PyTypeObject *type;
+ PyObject *obj;
+ long hash;
+
+ type = &_HashInheritanceTester_Type;
+ obj = PyObject_New(PyObject, type);
+ if (obj == NULL) {
+ PyErr_Clear();
+ PyErr_SetString(
+ TestError,
+ "test_lazy_hash_inheritance: failed to create object");
+ return NULL;
+ }
+
+ if (type->tp_dict != NULL) {
+ PyErr_SetString(
+ TestError,
+ "test_lazy_hash_inheritance: type initialised too soon");
+ Py_DECREF(obj);
+ return NULL;
+ }
+
+ hash = PyObject_Hash(obj);
+ if ((hash == -1) && PyErr_Occurred()) {
+ PyErr_Clear();
+ PyErr_SetString(
+ TestError,
+ "test_lazy_hash_inheritance: could not hash object");
+ Py_DECREF(obj);
+ return NULL;
+ }
+
+ if (type->tp_dict == NULL) {
+ PyErr_SetString(
+ TestError,
+ "test_lazy_hash_inheritance: type not initialised by hash()");
+ Py_DECREF(obj);
+ return NULL;
+ }
+
+ if (type->tp_hash != PyType_Type.tp_hash) {
+ PyErr_SetString(
+ TestError,
+ "test_lazy_hash_inheritance: unexpected hash function");
+ Py_DECREF(obj);
+ return NULL;
+ }
+
+ Py_RETURN_NONE;
+}
+
+
/* Tests of PyLong_{As, From}{Unsigned,}Long(), and (#ifdef HAVE_LONG_LONG)
PyLong_{As, From}{Unsigned,}LongLong().
@@ -805,6 +905,7 @@ static PyMethodDef TestMethods[] = {
{"test_config", (PyCFunction)test_config, METH_NOARGS},
{"test_list_api", (PyCFunction)test_list_api, METH_NOARGS},
{"test_dict_iteration", (PyCFunction)test_dict_iteration,METH_NOARGS},
+ {"test_lazy_hash_inheritance", (PyCFunction)test_lazy_hash_inheritance,METH_NOARGS},
{"test_long_api", (PyCFunction)test_long_api, METH_NOARGS},
{"test_long_numbits", (PyCFunction)test_long_numbits, METH_NOARGS},
{"test_k_code", (PyCFunction)test_k_code, METH_NOARGS},
diff --git a/Objects/object.c b/Objects/object.c
index c882cf2..7b82db9 100644
--- a/Objects/object.c
+++ b/Objects/object.c
@@ -1100,6 +1100,17 @@ PyObject_Hash(PyObject *v)
PyTypeObject *tp = v->ob_type;
if (tp->tp_hash != NULL)
return (*tp->tp_hash)(v);
+ /* To keep to the general practice that inheriting
+ * solely from object in C code should work without
+ * an explicit call to PyType_Ready, we implicitly call
+ * PyType_Ready here and then check the tp_hash slot again
+ */
+ if (tp->tp_dict == NULL) {
+ if (PyType_Ready(tp) < 0)
+ return -1;
+ if (tp->tp_hash != NULL)
+ return (*tp->tp_hash)(v);
+ }
if (tp->tp_compare == NULL && RICHCOMPARE(tp) == NULL) {
return _Py_HashPointer(v); /* Use address as hash value */
}