bpo-40679: Use the function's qualname in certain TypeErrors (GH-20236)
Patch by Dennis Sweeney.
This commit is contained in:
parent
7c30d12bd5
commit
b5cc2089cc
|
@ -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()
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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.
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue