8000 gh-98284: better error message for undefined abstractmethod by kaushikcfd · Pull Request #97971 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Nov 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Include/internal/pycore_global_strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ struct _Py_global_strings {
STRUCT_FOR_STR(anon_string, "<string>")
STRUCT_FOR_STR(anon_unknown, "<unknown>")
STRUCT_FOR_STR(close_br, "}")
STRUCT_FOR_STR(comma_sep, ", ")
STRUCT_FOR_STR(dbl_close_br, "}}")
STRUCT_FOR_STR(dbl_open_br, "{{")
STRUCT_FOR_STR(dbl_percent, "%%")
Expand Down
5 changes: 0 additions & 5 deletions Include/internal/pycore_runtime_init_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions Lib/test/test_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ class C(metaclass=abc_ABCMeta):
@abc.abstractmethod
def method_one(self):
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)

def test_object_new_with_many_abstractmethods(self):
Expand All @@ -165,7 +165,7 @@ def method_one(self):
@abc.abstractmethod
def method_two(self):
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)

def test_abstractmethod_integration(self):
Expand Down Expand Up @@ -535,7 +535,7 @@ def updated_foo(self):
A.foo = updated_foo
abc.update_abstractmethods(A)
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)

def test_update_implementation(self):
Expand All @@ -547,7 +547,7 @@ def foo(self):
class B(A):
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.assertEqual(B.__abstractmethods__, {'foo'})

Expand Down Expand Up @@ -605,7 +605,7 @@ def foo(self):

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)

def test_update_layered_implementation(self):
Expand All @@ -627,7 +627,7 @@ def foo(self):

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)

def test_update_multi_inheritance(self):
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -3962,7 +3962,7 @@ class Date(A):
day: 'int'

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)


Expand Down
Original file line number Diff line number Diff line change
@@ -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.
19 changes: 13 additions & 6 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -4861,9 +4861,10 @@ object_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
PyObject *abstract_methods;
PyObject *sorted_methods;
PyObject *joined;
PyObject* comma_w_quotes_sep;
Py_ssize_t method_count;

/* Compute ", ".join(sorted(type.__abstractmethods__))
/* Compute "', '".join(sorted(type.__abstractmethods__))
into joined. */
abstract_methods = type_abstractmethods(type, NULL);
if (abstract_methods == NULL)
Expand All @@ -4876,22 +4877,28 @@ object_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
Py_DECREF(sorted_methods);
return NULL;
}
_Py_DECLARE_STR(comma_sep, ", ");
joined = PyUnicode_Join(&_Py_STR(comma_sep), sorted_methods);
comma_w_quotes_sep = PyUnicode_FromString("', '");
joined = PyUnicode_Join(comma_w_quotes_sep, sorted_methods);
method_count = PyObject_Length(sorted_methods);
Py_DECREF(sorted_methods);
if (joined == NULL)
if (joined == NULL) {
Py_DECREF(comma_w_quotes_sep);
return NULL;
if (method_count == -1)
}
if (method_count == -1) {
Py_DECREF(comma_w_quotes_sep);
Py_DECREF(joined);
return NULL;
}

PyErr_Format(PyExc_TypeError,
"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,
method_count > 1 ? "s" : "",
joined);
Py_DECREF(joined);
Py_DECREF(comma_w_quotes_sep);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You also need to DECREF this in the two return NULL branches above.

Come to think of it, the second of those already leaks joined. It needs a Py_DECREF(joined).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aah, yep! Fixed in 6ae052e.

return NULL;
}
PyObject *obj = type->tp_alloc(type, 0);
Expand Down
0