mirror of https://github.com/python/cpython
gh-98284: better error message for undefined abstractmethod (#97971)
This commit is contained in:
parent
b5f711185b
commit
67ade403a2
|
@ -37,7 +37,6 @@ struct _Py_global_strings {
|
||||||
STRUCT_FOR_STR(anon_string, "<string>")
|
STRUCT_FOR_STR(anon_string, "<string>")
|
||||||
STRUCT_FOR_STR(anon_unknown, "<unknown>")
|
STRUCT_FOR_STR(anon_unknown, "<unknown>")
|
||||||
STRUCT_FOR_STR(close_br, "}")
|
STRUCT_FOR_STR(close_br, "}")
|
||||||
STRUCT_FOR_STR(comma_sep, ", ")
|
|
||||||
STRUCT_FOR_STR(dbl_close_br, "}}")
|
STRUCT_FOR_STR(dbl_close_br, "}}")
|
||||||
STRUCT_FOR_STR(dbl_open_br, "{{")
|
STRUCT_FOR_STR(dbl_open_br, "{{")
|
||||||
STRUCT_FOR_STR(dbl_percent, "%%")
|
STRUCT_FOR_STR(dbl_percent, "%%")
|
||||||
|
|
|
@ -547,7 +547,6 @@ extern "C" {
|
||||||
INIT_STR(anon_string, "<string>"), \
|
INIT_STR(anon_string, "<string>"), \
|
||||||
INIT_STR(anon_unknown, "<unknown>"), \
|
INIT_STR(anon_unknown, "<unknown>"), \
|
||||||
INIT_STR(close_br, "}"), \
|
INIT_STR(close_br, "}"), \
|
||||||
INIT_STR(comma_sep, ", "), \
|
|
||||||
INIT_STR(dbl_close_br, "}}"), \
|
INIT_STR(dbl_close_br, "}}"), \
|
||||||
INIT_STR(dbl_open_br, "{{"), \
|
INIT_STR(dbl_open_br, "{{"), \
|
||||||
INIT_STR(dbl_percent, "%%"), \
|
INIT_STR(dbl_percent, "%%"), \
|
||||||
|
@ -4865,10 +4864,6 @@ _PyStaticObjects_CheckRefcnt(void) {
|
||||||
_PyObject_Dump((PyObject *)&_Py_STR(close_br));
|
_PyObject_Dump((PyObject *)&_Py_STR(close_br));
|
||||||
Py_FatalError("immortal object has less refcnt than expected _PyObject_IMMORTAL_REFCNT");
|
Py_FatalError("immortal object has less refcnt than expected _PyObject_IMMORTAL_REFCNT");
|
||||||
};
|
};
|
||||||
if (Py_REFCNT((PyObject *)&_Py_STR(comma_sep)) < _PyObject_IMMORTAL_REFCNT) {
|
|
||||||
_PyObject_Dump((PyObject *)&_Py_STR(comma_sep));
|
|
||||||
Py_FatalError("immortal object has less refcnt than expected _PyObject_IMMORTAL_REFCNT");
|
|
||||||
};
|
|
||||||
if (Py_REFCNT((PyObject *)&_Py_STR(dbl_close_br)) < _PyObject_IMMORTAL_REFCNT) {
|
if (Py_REFCNT((PyObject *)&_Py_STR(dbl_close_br)) < _PyObject_IMMORTAL_REFCNT) {
|
||||||
_PyObject_Dump((PyObject *)&_Py_STR(dbl_close_br));
|
_PyObject_Dump((PyObject *)&_Py_STR(dbl_close_br));
|
||||||
Py_FatalError("immortal object has less refcnt than expected _PyObject_IMMORTAL_REFCNT");
|
Py_FatalError("immortal object has less refcnt than expected _PyObject_IMMORTAL_REFCNT");
|
||||||
|
|
|
@ -154,7 +154,7 @@ def test_factory(abc_ABCMeta, abc_get_cache_token):
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def method_one(self):
|
def method_one(self):
|
||||||
pass
|
pass
|
||||||
msg = r"class C without an implementation for abstract method method_one"
|
msg = r"class C without an implementation for abstract method 'method_one'"
|
||||||
self.assertRaisesRegex(TypeError, msg, C)
|
self.assertRaisesRegex(TypeError, msg, C)
|
||||||
|
|
||||||
def test_object_new_with_many_abstractmethods(self):
|
def test_object_new_with_many_abstractmethods(self):
|
||||||
|
@ -165,7 +165,7 @@ def test_factory(abc_ABCMeta, abc_get_cache_token):
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def method_two(self):
|
def method_two(self):
|
||||||
pass
|
pass
|
||||||
msg = r"class C without an implementation for abstract methods method_one, method_two"
|
msg = r"class C without an implementation for abstract methods 'method_one', 'method_two'"
|
||||||
self.assertRaisesRegex(TypeError, msg, C)
|
self.assertRaisesRegex(TypeError, msg, C)
|
||||||
|
|
||||||
def test_abstractmethod_integration(self):
|
def test_abstractmethod_integration(self):
|
||||||
|
@ -535,7 +535,7 @@ def test_factory(abc_ABCMeta, abc_get_cache_token):
|
||||||
A.foo = updated_foo
|
A.foo = updated_foo
|
||||||
abc.update_abstractmethods(A)
|
abc.update_abstractmethods(A)
|
||||||
self.assertEqual(A.__abstractmethods__, {'foo', 'bar'})
|
self.assertEqual(A.__abstractmethods__, {'foo', 'bar'})
|
||||||
msg = "class A without an implementation for abstract methods bar, foo"
|
msg = "class A without an implementation for abstract methods 'bar', 'foo'"
|
||||||
self.assertRaisesRegex(TypeError, msg, A)
|
self.assertRaisesRegex(TypeError, msg, A)
|
||||||
|
|
||||||
def test_update_implementation(self):
|
def test_update_implementation(self):
|
||||||
|
@ -547,7 +547,7 @@ def test_factory(abc_ABCMeta, abc_get_cache_token):
|
||||||
class B(A):
|
class B(A):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
msg = "class B without an implementation for abstract method foo"
|
msg = "class B without an implementation for abstract method 'foo'"
|
||||||
self.assertRaisesRegex(TypeError, msg, B)
|
self.assertRaisesRegex(TypeError, msg, B)
|
||||||
self.assertEqual(B.__abstractmethods__, {'foo'})
|
self.assertEqual(B.__abstractmethods__, {'foo'})
|
||||||
|
|
||||||
|
@ -605,7 +605,7 @@ def test_factory(abc_ABCMeta, abc_get_cache_token):
|
||||||
|
|
||||||
abc.update_abstractmethods(B)
|
abc.update_abstractmethods(B)
|
||||||
|
|
||||||
msg = "class B without an implementation for abstract method foo"
|
msg = "class B without an implementation for abstract method 'foo'"
|
||||||
self.assertRaisesRegex(TypeError, msg, B)
|
self.assertRaisesRegex(TypeError, msg, B)
|
||||||
|
|
||||||
def test_update_layered_implementation(self):
|
def test_update_layered_implementation(self):
|
||||||
|
@ -627,7 +627,7 @@ def test_factory(abc_ABCMeta, abc_get_cache_token):
|
||||||
|
|
||||||
abc.update_abstractmethods(C)
|
abc.update_abstractmethods(C)
|
||||||
|
|
||||||
msg = "class C without an implementation for abstract method foo"
|
msg = "class C without an implementation for abstract method 'foo'"
|
||||||
self.assertRaisesRegex(TypeError, msg, C)
|
self.assertRaisesRegex(TypeError, msg, C)
|
||||||
|
|
||||||
def test_update_multi_inheritance(self):
|
def test_update_multi_inheritance(self):
|
||||||
|
|
|
@ -3970,7 +3970,7 @@ class TestAbstract(unittest.TestCase):
|
||||||
day: 'int'
|
day: 'int'
|
||||||
|
|
||||||
self.assertTrue(inspect.isabstract(Date))
|
self.assertTrue(inspect.isabstract(Date))
|
||||||
msg = 'class Date without an implementation for abstract method foo'
|
msg = "class Date without an implementation for abstract method 'foo'"
|
||||||
self.assertRaisesRegex(TypeError, msg, Date)
|
self.assertRaisesRegex(TypeError, msg, Date)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
Improved :class:`TypeError` message for undefined abstract methods of a
|
||||||
|
:class:`abc.ABC` instance. The names of the missing methods are surrounded
|
||||||
|
by single-quotes to highlight them.
|
|
@ -4955,9 +4955,10 @@ object_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
||||||
PyObject *abstract_methods;
|
PyObject *abstract_methods;
|
||||||
PyObject *sorted_methods;
|
PyObject *sorted_methods;
|
||||||
PyObject *joined;
|
PyObject *joined;
|
||||||
|
PyObject* comma_w_quotes_sep;
|
||||||
Py_ssize_t method_count;
|
Py_ssize_t method_count;
|
||||||
|
|
||||||
/* Compute ", ".join(sorted(type.__abstractmethods__))
|
/* Compute "', '".join(sorted(type.__abstractmethods__))
|
||||||
into joined. */
|
into joined. */
|
||||||
abstract_methods = type_abstractmethods(type, NULL);
|
abstract_methods = type_abstractmethods(type, NULL);
|
||||||
if (abstract_methods == NULL)
|
if (abstract_methods == NULL)
|
||||||
|
@ -4970,22 +4971,28 @@ object_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
||||||
Py_DECREF(sorted_methods);
|
Py_DECREF(sorted_methods);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
_Py_DECLARE_STR(comma_sep, ", ");
|
comma_w_quotes_sep = PyUnicode_FromString("', '");
|
||||||
joined = PyUnicode_Join(&_Py_STR(comma_sep), sorted_methods);
|
joined = PyUnicode_Join(comma_w_quotes_sep, sorted_methods);
|
||||||
method_count = PyObject_Length(sorted_methods);
|
method_count = PyObject_Length(sorted_methods);
|
||||||
Py_DECREF(sorted_methods);
|
Py_DECREF(sorted_methods);
|
||||||
if (joined == NULL)
|
if (joined == NULL) {
|
||||||
|
Py_DECREF(comma_w_quotes_sep);
|
||||||
return NULL;
|
return NULL;
|
||||||
if (method_count == -1)
|
}
|
||||||
|
if (method_count == -1) {
|
||||||
|
Py_DECREF(comma_w_quotes_sep);
|
||||||
|
Py_DECREF(joined);
|
||||||
return NULL;
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
PyErr_Format(PyExc_TypeError,
|
PyErr_Format(PyExc_TypeError,
|
||||||
"Can't instantiate abstract class %s "
|
"Can't instantiate abstract class %s "
|
||||||
"without an implementation for abstract method%s %U",
|
"without an implementation for abstract method%s '%U'",
|
||||||
type->tp_name,
|
type->tp_name,
|
||||||
method_count > 1 ? "s" : "",
|
method_count > 1 ? "s" : "",
|
||||||
joined);
|
joined);
|
||||||
Py_DECREF(joined);
|
Py_DECREF(joined);
|
||||||
|
Py_DECREF(comma_w_quotes_sep);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
PyObject *obj = type->tp_alloc(type, 0);
|
PyObject *obj = type->tp_alloc(type, 0);
|
||||||
|
|
Loading…
Reference in New Issue