mirror of
https://github.com/python/cpython.git
synced 2025-08-30 21:48:47 +00:00
gh-132825: Enhance unhashable error messages for dict and set (#132828)
This commit is contained in:
parent
b2e666f30a
commit
426449d983
7 changed files with 136 additions and 14 deletions
|
@ -460,7 +460,8 @@ class CAPITest(unittest.TestCase):
|
|||
self.assertFalse(haskey({}, []))
|
||||
self.assertEqual(cm.unraisable.exc_type, TypeError)
|
||||
self.assertEqual(str(cm.unraisable.exc_value),
|
||||
"unhashable type: 'list'")
|
||||
"cannot use 'list' as a dict key "
|
||||
"(unhashable type: 'list')")
|
||||
|
||||
with support.catch_unraisable_exception() as cm:
|
||||
self.assertFalse(haskey([], 1))
|
||||
|
|
|
@ -3,6 +3,7 @@ import collections.abc
|
|||
import gc
|
||||
import pickle
|
||||
import random
|
||||
import re
|
||||
import string
|
||||
import sys
|
||||
import unittest
|
||||
|
@ -1487,6 +1488,47 @@ class DictTest(unittest.TestCase):
|
|||
self.assertEqual(d.get(key3_3), 44)
|
||||
self.assertGreaterEqual(eq_count, 1)
|
||||
|
||||
def test_unhashable_key(self):
|
||||
d = {'a': 1}
|
||||
key = [1, 2, 3]
|
||||
|
||||
def check_unhashable_key():
|
||||
msg = "cannot use 'list' as a dict key (unhashable type: 'list')"
|
||||
return self.assertRaisesRegex(TypeError, re.escape(msg))
|
||||
|
||||
with check_unhashable_key():
|
||||
key in d
|
||||
with check_unhashable_key():
|
||||
d[key]
|
||||
with check_unhashable_key():
|
||||
d[key] = 2
|
||||
with check_unhashable_key():
|
||||
d.setdefault(key, 2)
|
||||
with check_unhashable_key():
|
||||
d.pop(key)
|
||||
with check_unhashable_key():
|
||||
d.get(key)
|
||||
|
||||
# Only TypeError exception is overriden,
|
||||
# other exceptions are left unchanged.
|
||||
class HashError:
|
||||
def __hash__(self):
|
||||
raise KeyError('error')
|
||||
|
||||
key2 = HashError()
|
||||
with self.assertRaises(KeyError):
|
||||
key2 in d
|
||||
with self.assertRaises(KeyError):
|
||||
d[key2]
|
||||
with self.assertRaises(KeyError):
|
||||
d[key2] = 2
|
||||
with self.assertRaises(KeyError):
|
||||
d.setdefault(key2, 2)
|
||||
with self.assertRaises(KeyError):
|
||||
d.pop(key2)
|
||||
with self.assertRaises(KeyError):
|
||||
d.get(key2)
|
||||
|
||||
|
||||
class CAPITest(unittest.TestCase):
|
||||
|
||||
|
|
|
@ -1055,7 +1055,7 @@ except TypeError as e:
|
|||
""")
|
||||
popen = script_helper.spawn_python("main.py", cwd=tmp)
|
||||
stdout, stderr = popen.communicate()
|
||||
self.assertEqual(stdout.rstrip(), b"unhashable type: 'substr'")
|
||||
self.assertIn(b"unhashable type: 'substr'", stdout.rstrip())
|
||||
|
||||
with open(os.path.join(tmp, "main.py"), "w", encoding='utf-8') as f:
|
||||
f.write("""
|
||||
|
@ -1072,7 +1072,7 @@ except TypeError as e:
|
|||
|
||||
popen = script_helper.spawn_python("main.py", cwd=tmp)
|
||||
stdout, stderr = popen.communicate()
|
||||
self.assertEqual(stdout.rstrip(), b"unhashable type: 'substr'")
|
||||
self.assertIn(b"unhashable type: 'substr'", stdout.rstrip())
|
||||
|
||||
# Various issues with sys module
|
||||
with open(os.path.join(tmp, "main.py"), "w", encoding='utf-8') as f:
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
import collections.abc
|
||||
import copy
|
||||
import gc
|
||||
import itertools
|
||||
import operator
|
||||
import pickle
|
||||
import re
|
||||
import unittest
|
||||
import warnings
|
||||
import weakref
|
||||
from random import randrange, shuffle
|
||||
from test import support
|
||||
from test.support import warnings_helper
|
||||
import gc
|
||||
import weakref
|
||||
import operator
|
||||
import copy
|
||||
import pickle
|
||||
from random import randrange, shuffle
|
||||
import warnings
|
||||
import collections
|
||||
import collections.abc
|
||||
import itertools
|
||||
|
||||
|
||||
class PassThru(Exception):
|
||||
pass
|
||||
|
@ -645,6 +646,35 @@ class TestSet(TestJointOps, unittest.TestCase):
|
|||
self.assertRaises(KeyError, myset.remove, set(range(1)))
|
||||
self.assertRaises(KeyError, myset.remove, set(range(3)))
|
||||
|
||||
def test_unhashable_element(self):
|
||||
myset = {'a'}
|
||||
elem = [1, 2, 3]
|
||||
|
||||
def check_unhashable_element():
|
||||
msg = "cannot use 'list' as a set element (unhashable type: 'list')"
|
||||
return self.assertRaisesRegex(TypeError, re.escape(msg))
|
||||
|
||||
with check_unhashable_element():
|
||||
elem in myset
|
||||
with check_unhashable_element():
|
||||
myset.add(elem)
|
||||
with check_unhashable_element():
|
||||
myset.discard(elem)
|
||||
|
||||
# Only TypeError exception is overriden,
|
||||
# other exceptions are left unchanged.
|
||||
class HashError:
|
||||
def __hash__(self):
|
||||
raise KeyError('error')
|
||||
|
||||
elem2 = HashError()
|
||||
with self.assertRaises(KeyError):
|
||||
elem2 in myset
|
||||
with self.assertRaises(KeyError):
|
||||
myset.add(elem2)
|
||||
with self.assertRaises(KeyError):
|
||||
myset.discard(elem2)
|
||||
|
||||
|
||||
class SetSubclass(set):
|
||||
pass
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Enhance unhashable key/element error messages for :class:`dict` and
|
||||
:class:`set`. Patch by Victor Stinner.
|
|
@ -2276,6 +2276,22 @@ PyDict_GetItem(PyObject *op, PyObject *key)
|
|||
"PyDict_GetItemRef() or PyDict_GetItemWithError()");
|
||||
}
|
||||
|
||||
static void
|
||||
dict_unhashtable_type(PyObject *key)
|
||||
{
|
||||
PyObject *exc = PyErr_GetRaisedException();
|
||||
assert(exc != NULL);
|
||||
if (!Py_IS_TYPE(exc, (PyTypeObject*)PyExc_TypeError)) {
|
||||
PyErr_SetRaisedException(exc);
|
||||
return;
|
||||
}
|
||||
|
||||
PyErr_Format(PyExc_TypeError,
|
||||
"cannot use '%T' as a dict key (%S)",
|
||||
key, exc);
|
||||
Py_DECREF(exc);
|
||||
}
|
||||
|
||||
Py_ssize_t
|
||||
_PyDict_LookupIndex(PyDictObject *mp, PyObject *key)
|
||||
{
|
||||
|
@ -2286,6 +2302,7 @@ _PyDict_LookupIndex(PyDictObject *mp, PyObject *key)
|
|||
|
||||
Py_hash_t hash = _PyObject_HashFast(key);
|
||||
if (hash == -1) {
|
||||
dict_unhashtable_type(key);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
@ -2382,6 +2399,7 @@ PyDict_GetItemRef(PyObject *op, PyObject *key, PyObject **result)
|
|||
|
||||
Py_hash_t hash = _PyObject_HashFast(key);
|
||||
if (hash == -1) {
|
||||
dict_unhashtable_type(key);
|
||||
*result = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
@ -2397,6 +2415,7 @@ _PyDict_GetItemRef_Unicode_LockHeld(PyDictObject *op, PyObject *key, PyObject **
|
|||
|
||||
Py_hash_t hash = _PyObject_HashFast(key);
|
||||
if (hash == -1) {
|
||||
dict_unhashtable_type(key);
|
||||
*result = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
@ -2434,6 +2453,7 @@ PyDict_GetItemWithError(PyObject *op, PyObject *key)
|
|||
}
|
||||
hash = _PyObject_HashFast(key);
|
||||
if (hash == -1) {
|
||||
dict_unhashtable_type(key);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -2591,6 +2611,7 @@ setitem_take2_lock_held(PyDictObject *mp, PyObject *key, PyObject *value)
|
|||
assert(PyDict_Check(mp));
|
||||
Py_hash_t hash = _PyObject_HashFast(key);
|
||||
if (hash == -1) {
|
||||
dict_unhashtable_type(key);
|
||||
Py_DECREF(key);
|
||||
Py_DECREF(value);
|
||||
return -1;
|
||||
|
@ -2742,6 +2763,7 @@ PyDict_DelItem(PyObject *op, PyObject *key)
|
|||
assert(key);
|
||||
Py_hash_t hash = _PyObject_HashFast(key);
|
||||
if (hash == -1) {
|
||||
dict_unhashtable_type(key);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
@ -3064,6 +3086,7 @@ pop_lock_held(PyObject *op, PyObject *key, PyObject **result)
|
|||
|
||||
Py_hash_t hash = _PyObject_HashFast(key);
|
||||
if (hash == -1) {
|
||||
dict_unhashtable_type(key);
|
||||
if (result) {
|
||||
*result = NULL;
|
||||
}
|
||||
|
@ -3398,6 +3421,7 @@ dict_subscript(PyObject *self, PyObject *key)
|
|||
|
||||
hash = _PyObject_HashFast(key);
|
||||
if (hash == -1) {
|
||||
dict_unhashtable_type(key);
|
||||
return NULL;
|
||||
}
|
||||
ix = _Py_dict_lookup_threadsafe(mp, key, hash, &value);
|
||||
|
@ -4278,6 +4302,7 @@ dict_get_impl(PyDictObject *self, PyObject *key, PyObject *default_value)
|
|||
|
||||
hash = _PyObject_HashFast(key);
|
||||
if (hash == -1) {
|
||||
dict_unhashtable_type(key);
|
||||
return NULL;
|
||||
}
|
||||
ix = _Py_dict_lookup_threadsafe(self, key, hash, &val);
|
||||
|
@ -4310,6 +4335,7 @@ dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, PyObject *default_valu
|
|||
|
||||
hash = _PyObject_HashFast(key);
|
||||
if (hash == -1) {
|
||||
dict_unhashtable_type(key);
|
||||
if (result) {
|
||||
*result = NULL;
|
||||
}
|
||||
|
@ -4737,8 +4763,8 @@ int
|
|||
PyDict_Contains(PyObject *op, PyObject *key)
|
||||
{
|
||||
Py_hash_t hash = _PyObject_HashFast(key);
|
||||
|
||||
if (hash == -1) {
|
||||
dict_unhashtable_type(key);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
@ -6829,6 +6855,7 @@ _PyDict_SetItem_LockHeld(PyDictObject *dict, PyObject *name, PyObject *value)
|
|||
if (value == NULL) {
|
||||
Py_hash_t hash = _PyObject_HashFast(name);
|
||||
if (hash == -1) {
|
||||
dict_unhashtable_type(name);
|
||||
return -1;
|
||||
}
|
||||
return delitem_knownhash_lock_held((PyObject *)dict, name, hash);
|
||||
|
|
|
@ -211,11 +211,28 @@ set_add_entry(PySetObject *so, PyObject *key, Py_hash_t hash)
|
|||
return set_add_entry_takeref(so, Py_NewRef(key), hash);
|
||||
}
|
||||
|
||||
static void
|
||||
set_unhashtable_type(PyObject *key)
|
||||
{
|
||||
PyObject *exc = PyErr_GetRaisedException();
|
||||
assert(exc != NULL);
|
||||
if (!Py_IS_TYPE(exc, (PyTypeObject*)PyExc_TypeError)) {
|
||||
PyErr_SetRaisedException(exc);
|
||||
return;
|
||||
}
|
||||
|
||||
PyErr_Format(PyExc_TypeError,
|
||||
"cannot use '%T' as a set element (%S)",
|
||||
key, exc);
|
||||
Py_DECREF(exc);
|
||||
}
|
||||
|
||||
int
|
||||
_PySet_AddTakeRef(PySetObject *so, PyObject *key)
|
||||
{
|
||||
Py_hash_t hash = _PyObject_HashFast(key);
|
||||
if (hash == -1) {
|
||||
set_unhashtable_type(key);
|
||||
Py_DECREF(key);
|
||||
return -1;
|
||||
}
|
||||
|
@ -384,6 +401,7 @@ set_add_key(PySetObject *so, PyObject *key)
|
|||
{
|
||||
Py_hash_t hash = _PyObject_HashFast(key);
|
||||
if (hash == -1) {
|
||||
set_unhashtable_type(key);
|
||||
return -1;
|
||||
}
|
||||
return set_add_entry(so, key, hash);
|
||||
|
@ -394,6 +412,7 @@ set_contains_key(PySetObject *so, PyObject *key)
|
|||
{
|
||||
Py_hash_t hash = _PyObject_HashFast(key);
|
||||
if (hash == -1) {
|
||||
set_unhashtable_type(key);
|
||||
return -1;
|
||||
}
|
||||
return set_contains_entry(so, key, hash);
|
||||
|
@ -404,6 +423,7 @@ set_discard_key(PySetObject *so, PyObject *key)
|
|||
{
|
||||
Py_hash_t hash = _PyObject_HashFast(key);
|
||||
if (hash == -1) {
|
||||
set_unhashtable_type(key);
|
||||
return -1;
|
||||
}
|
||||
return set_discard_entry(so, key, hash);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue