bpo-40679: Use the function's qualname in certain TypeErrors (GH-20236)

Patch by Dennis Sweeney.
This commit is contained in:
Dennis Sweeney 2020-05-22 16:40:17 -04:00 committed by GitHub
parent 7c30d12bd5
commit b5cc2089cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 68 additions and 16 deletions

View File

@ -8,6 +8,7 @@ import struct
import collections import collections
import itertools import itertools
import gc import gc
import contextlib
class FunctionCalls(unittest.TestCase): class FunctionCalls(unittest.TestCase):
@ -665,5 +666,52 @@ class TestPEP590(unittest.TestCase):
self.assertEqual(expected, wrapped(*args, **kwargs)) self.assertEqual(expected, wrapped(*args, **kwargs))
class A:
def method_two_args(self, x, y):
pass
@staticmethod
def static_no_args():
pass
@staticmethod
def positional_only(arg, /):
pass
@cpython_only
class TestErrorMessagesUseQualifiedName(unittest.TestCase):
@contextlib.contextmanager
def check_raises_type_error(self, message):
with self.assertRaises(TypeError) as cm:
yield
self.assertEqual(str(cm.exception), message)
def test_missing_arguments(self):
msg = "A.method_two_args() missing 1 required positional argument: 'y'"
with self.check_raises_type_error(msg):
A().method_two_args("x")
def test_too_many_positional(self):
msg = "A.static_no_args() takes 0 positional arguments but 1 was given"
with self.check_raises_type_error(msg):
A.static_no_args("oops it's an arg")
def test_positional_only_passed_as_keyword(self):
msg = "A.positional_only() got some positional-only arguments passed as keyword arguments: 'arg'"
with self.check_raises_type_error(msg):
A.positional_only(arg="x")
def test_unexpected_keyword(self):
msg = "A.method_two_args() got an unexpected keyword argument 'bad'"
with self.check_raises_type_error(msg):
A().method_two_args(bad="x")
def test_multiple_values(self):
msg = "A.method_two_args() got multiple values for argument 'x'"
with self.check_raises_type_error(msg):
A().method_two_args("x", "y", x="oops")
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

View File

@ -63,7 +63,8 @@ class KeywordOnlyArgTestCase(unittest.TestCase):
pass pass
with self.assertRaises(TypeError) as exc: with self.assertRaises(TypeError) as exc:
f(1, 2, 3) f(1, 2, 3)
expected = "f() takes from 1 to 2 positional arguments but 3 were given" expected = (f"{f.__qualname__}() takes from 1 to 2 "
"positional arguments but 3 were given")
self.assertEqual(str(exc.exception), expected) self.assertEqual(str(exc.exception), expected)
def testSyntaxErrorForFunctionCall(self): def testSyntaxErrorForFunctionCall(self):

View File

@ -0,0 +1,2 @@
Certain :exc:`TypeError` messages about missing or extra arguments now include the function's
:term:`qualified name`. Patch by Dennis Sweeney.

View File

@ -3875,7 +3875,7 @@ exit_eval_frame:
static void static void
format_missing(PyThreadState *tstate, const char *kind, format_missing(PyThreadState *tstate, const char *kind,
PyCodeObject *co, PyObject *names) PyCodeObject *co, PyObject *names, PyObject *qualname)
{ {
int err; int err;
Py_ssize_t len = PyList_GET_SIZE(names); Py_ssize_t len = PyList_GET_SIZE(names);
@ -3928,7 +3928,7 @@ format_missing(PyThreadState *tstate, const char *kind,
return; return;
_PyErr_Format(tstate, PyExc_TypeError, _PyErr_Format(tstate, PyExc_TypeError,
"%U() missing %i required %s argument%s: %U", "%U() missing %i required %s argument%s: %U",
co->co_name, qualname,
len, len,
kind, kind,
len == 1 ? "" : "s", len == 1 ? "" : "s",
@ -3939,7 +3939,7 @@ format_missing(PyThreadState *tstate, const char *kind,
static void static void
missing_arguments(PyThreadState *tstate, PyCodeObject *co, missing_arguments(PyThreadState *tstate, PyCodeObject *co,
Py_ssize_t missing, Py_ssize_t defcount, Py_ssize_t missing, Py_ssize_t defcount,
PyObject **fastlocals) PyObject **fastlocals, PyObject *qualname)
{ {
Py_ssize_t i, j = 0; Py_ssize_t i, j = 0;
Py_ssize_t start, end; Py_ssize_t start, end;
@ -3971,14 +3971,14 @@ missing_arguments(PyThreadState *tstate, PyCodeObject *co,
} }
} }
assert(j == missing); assert(j == missing);
format_missing(tstate, kind, co, missing_names); format_missing(tstate, kind, co, missing_names, qualname);
Py_DECREF(missing_names); Py_DECREF(missing_names);
} }
static void static void
too_many_positional(PyThreadState *tstate, PyCodeObject *co, too_many_positional(PyThreadState *tstate, PyCodeObject *co,
Py_ssize_t given, Py_ssize_t defcount, Py_ssize_t given, Py_ssize_t defcount,
PyObject **fastlocals) PyObject **fastlocals, PyObject *qualname)
{ {
int plural; int plural;
Py_ssize_t kwonly_given = 0; Py_ssize_t kwonly_given = 0;
@ -4022,7 +4022,7 @@ too_many_positional(PyThreadState *tstate, PyCodeObject *co,
} }
_PyErr_Format(tstate, PyExc_TypeError, _PyErr_Format(tstate, PyExc_TypeError,
"%U() takes %U positional argument%s but %zd%U %s given", "%U() takes %U positional argument%s but %zd%U %s given",
co->co_name, qualname,
sig, sig,
plural ? "s" : "", plural ? "s" : "",
given, given,
@ -4034,7 +4034,8 @@ too_many_positional(PyThreadState *tstate, PyCodeObject *co,
static int static int
positional_only_passed_as_keyword(PyThreadState *tstate, PyCodeObject *co, positional_only_passed_as_keyword(PyThreadState *tstate, PyCodeObject *co,
Py_ssize_t kwcount, PyObject* const* kwnames) Py_ssize_t kwcount, PyObject* const* kwnames,
PyObject *qualname)
{ {
int posonly_conflicts = 0; int posonly_conflicts = 0;
PyObject* posonly_names = PyList_New(0); PyObject* posonly_names = PyList_New(0);
@ -4079,7 +4080,7 @@ positional_only_passed_as_keyword(PyThreadState *tstate, PyCodeObject *co,
_PyErr_Format(tstate, PyExc_TypeError, _PyErr_Format(tstate, PyExc_TypeError,
"%U() got some positional-only arguments passed" "%U() got some positional-only arguments passed"
" as keyword arguments: '%U'", " as keyword arguments: '%U'",
co->co_name, error_names); qualname, error_names);
Py_DECREF(error_names); Py_DECREF(error_names);
goto fail; goto fail;
} }
@ -4180,7 +4181,7 @@ _PyEval_EvalCode(PyThreadState *tstate,
if (keyword == NULL || !PyUnicode_Check(keyword)) { if (keyword == NULL || !PyUnicode_Check(keyword)) {
_PyErr_Format(tstate, PyExc_TypeError, _PyErr_Format(tstate, PyExc_TypeError,
"%U() keywords must be strings", "%U() keywords must be strings",
co->co_name); qualname);
goto fail; goto fail;
} }
@ -4211,14 +4212,14 @@ _PyEval_EvalCode(PyThreadState *tstate,
if (co->co_posonlyargcount if (co->co_posonlyargcount
&& positional_only_passed_as_keyword(tstate, co, && positional_only_passed_as_keyword(tstate, co,
kwcount, kwnames)) kwcount, kwnames, qualname))
{ {
goto fail; goto fail;
} }
_PyErr_Format(tstate, PyExc_TypeError, _PyErr_Format(tstate, PyExc_TypeError,
"%U() got an unexpected keyword argument '%S'", "%U() got an unexpected keyword argument '%S'",
co->co_name, keyword); qualname, keyword);
goto fail; goto fail;
} }
@ -4231,7 +4232,7 @@ _PyEval_EvalCode(PyThreadState *tstate,
if (GETLOCAL(j) != NULL) { if (GETLOCAL(j) != NULL) {
_PyErr_Format(tstate, PyExc_TypeError, _PyErr_Format(tstate, PyExc_TypeError,
"%U() got multiple values for argument '%S'", "%U() got multiple values for argument '%S'",
co->co_name, keyword); qualname, keyword);
goto fail; goto fail;
} }
Py_INCREF(value); Py_INCREF(value);
@ -4240,7 +4241,7 @@ _PyEval_EvalCode(PyThreadState *tstate,
/* Check the number of positional arguments */ /* Check the number of positional arguments */
if ((argcount > co->co_argcount) && !(co->co_flags & CO_VARARGS)) { if ((argcount > co->co_argcount) && !(co->co_flags & CO_VARARGS)) {
too_many_positional(tstate, co, argcount, defcount, fastlocals); too_many_positional(tstate, co, argcount, defcount, fastlocals, qualname);
goto fail; goto fail;
} }
@ -4254,7 +4255,7 @@ _PyEval_EvalCode(PyThreadState *tstate,
} }
} }
if (missing) { if (missing) {
missing_arguments(tstate, co, missing, defcount, fastlocals); missing_arguments(tstate, co, missing, defcount, fastlocals, qualname);
goto fail; goto fail;
} }
if (n > m) if (n > m)
@ -4292,7 +4293,7 @@ _PyEval_EvalCode(PyThreadState *tstate,
missing++; missing++;
} }
if (missing) { if (missing) {
missing_arguments(tstate, co, missing, -1, fastlocals); missing_arguments(tstate, co, missing, -1, fastlocals, qualname);
goto fail; goto fail;
} }
} }