gh-98284: better error message for undefined abstractmethod (#97971)

This commit is contained in:
Kaushik Kulkarni 2022-11-05 11:31:57 -05:00 committed by GitHub
parent b5f711185b
commit 67ade403a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 23 additions and 19 deletions

View File

@ -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, "%%")

View File

@ -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");

View File

@ -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):

View File

@ -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)

View File

@ -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.

View File

@ -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);