diff --git a/Lib/test/test_hash.py b/Lib/test/test_hash.py index 59e43dc8d07..807c2c46b36 100644 --- a/Lib/test/test_hash.py +++ b/Lib/test/test_hash.py @@ -103,9 +103,30 @@ class HashInheritanceTestCase(unittest.TestCase): self.assertFalse(isinstance(obj, Hashable), repr(obj)) +# Issue #4701: Check that some builtin types are correctly hashable +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 = [range(10), + enumerate(range(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(): support.run_unittest(HashEqualityTestCase, - HashInheritanceTestCase) + HashInheritanceTestCase, + HashBuiltinsTestCase) if __name__ == "__main__": diff --git a/Misc/NEWS b/Misc/NEWS index 49656a65881..bd43a002df8 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -12,6 +12,9 @@ What's New in Python 3.1 alpha 0 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 #4759: None is now allowed as the first argument of bytearray.translate(). It was always allowed for bytes.translate(). diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index c7c63930886..a1b367a6d98 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -175,6 +175,105 @@ 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 = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) + "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(). @@ -1036,6 +1135,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 b14e52aadee..bf0e6d174dc 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -846,6 +846,17 @@ PyObject_Hash(PyObject *v) PyTypeObject *tp = Py_TYPE(v); 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); + } /* Otherwise, the object can't be hashed */ return PyObject_HashNotImplemented(v); }