summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDennis Sweeney <36520290+sweeneyde@users.noreply.github.com>2020-05-22 20:40:17 (GMT)
committerGitHub <noreply@github.com>2020-05-22 20:40:17 (GMT)
commitb5cc2089cc354469f12eabc7ba54280e85fdd6dc (patch)
tree288c14c85592ce4672fbf50d47a94c7daf47f0b0
parent7c30d12bd5359b0f66c4fbc98aa055398bcc8a7e (diff)
downloadcpython-b5cc2089cc354469f12eabc7ba54280e85fdd6dc.zip
cpython-b5cc2089cc354469f12eabc7ba54280e85fdd6dc.tar.gz
cpython-b5cc2089cc354469f12eabc7ba54280e85fdd6dc.tar.bz2
bpo-40679: Use the function's qualname in certain TypeErrors (GH-20236)
Patch by Dennis Sweeney.
-rw-r--r--Lib/test/test_call.py48
-rw-r--r--Lib/test/test_keywordonlyarg.py3
-rw-r--r--Misc/NEWS.d/next/Core and Builtins/2020-05-19-19-39-49.bpo-40679.SVzz9p.rst2
-rw-r--r--Python/ceval.c31
4 files changed, 68 insertions, 16 deletions
diff --git a/Lib/test/test_call.py b/Lib/test/test_call.py
index 451a717..3f45922 100644
--- a/Lib/test/test_call.py
+++ b/Lib/test/test_call.py
@@ -8,6 +8,7 @@ import struct
import collections
import itertools
import gc
+import contextlib
class FunctionCalls(unittest.TestCase):
@@ -665,5 +666,52 @@ class TestPEP590(unittest.TestCase):
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__":
unittest.main()
diff --git a/Lib/test/test_keywordonlyarg.py b/Lib/test/test_keywordonlyarg.py
index 2cf8a89..df82f67 100644
--- a/Lib/test/test_keywordonlyarg.py
+++ b/Lib/test/test_keywordonlyarg.py
@@ -63,7 +63,8 @@ class KeywordOnlyArgTestCase(unittest.TestCase):
pass
with self.assertRaises(TypeError) as exc:
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)
def testSyntaxErrorForFunctionCall(self):
diff --git a/Misc/NEWS.d/next/Core and Builtins/2020-05-19-19-39-49.bpo-40679.SVzz9p.rst b/Misc/NEWS.d/next/Core and Builtins/2020-05-19-19-39-49.bpo-40679.SVzz9p.rst
new file mode 100644
index 0000000..2d0a432
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2020-05-19-19-39-49.bpo-40679.SVzz9p.rst
@@ -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.
diff --git a/Python/ceval.c b/Python/ceval.c
index 43ea1c7..a79773f 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -3875,7 +3875,7 @@ exit_eval_frame:
static void
format_missing(PyThreadState *tstate, const char *kind,
- PyCodeObject *co, PyObject *names)
+ PyCodeObject *co, PyObject *names, PyObject *qualname)
{
int err;
Py_ssize_t len = PyList_GET_SIZE(names);
@@ -3928,7 +3928,7 @@ format_missing(PyThreadState *tstate, const char *kind,
return;
_PyErr_Format(tstate, PyExc_TypeError,
"%U() missing %i required %s argument%s: %U",
- co->co_name,
+ qualname,
len,
kind,
len == 1 ? "" : "s",
@@ -3939,7 +3939,7 @@ format_missing(PyThreadState *tstate, const char *kind,
static void
missing_arguments(PyThreadState *tstate, PyCodeObject *co,
Py_ssize_t missing, Py_ssize_t defcount,
- PyObject **fastlocals)
+ PyObject **fastlocals, PyObject *qualname)
{
Py_ssize_t i, j = 0;
Py_ssize_t start, end;
@@ -3971,14 +3971,14 @@ missing_arguments(PyThreadState *tstate, PyCodeObject *co,
}
}
assert(j == missing);
- format_missing(tstate, kind, co, missing_names);
+ format_missing(tstate, kind, co, missing_names, qualname);
Py_DECREF(missing_names);
}
static void
too_many_positional(PyThreadState *tstate, PyCodeObject *co,
Py_ssize_t given, Py_ssize_t defcount,
- PyObject **fastlocals)
+ PyObject **fastlocals, PyObject *qualname)
{
int plural;
Py_ssize_t kwonly_given = 0;
@@ -4022,7 +4022,7 @@ too_many_positional(PyThreadState *tstate, PyCodeObject *co,
}
_PyErr_Format(tstate, PyExc_TypeError,
"%U() takes %U positional argument%s but %zd%U %s given",
- co->co_name,
+ qualname,
sig,
plural ? "s" : "",
given,
@@ -4034,7 +4034,8 @@ too_many_positional(PyThreadState *tstate, PyCodeObject *co,
static int
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;
PyObject* posonly_names = PyList_New(0);
@@ -4079,7 +4080,7 @@ positional_only_passed_as_keyword(PyThreadState *tstate, PyCodeObject *co,
_PyErr_Format(tstate, PyExc_TypeError,
"%U() got some positional-only arguments passed"
" as keyword arguments: '%U'",
- co->co_name, error_names);
+ qualname, error_names);
Py_DECREF(error_names);
goto fail;
}
@@ -4180,7 +4181,7 @@ _PyEval_EvalCode(PyThreadState *tstate,
if (keyword == NULL || !PyUnicode_Check(keyword)) {
_PyErr_Format(tstate, PyExc_TypeError,
"%U() keywords must be strings",
- co->co_name);
+ qualname);
goto fail;
}
@@ -4211,14 +4212,14 @@ _PyEval_EvalCode(PyThreadState *tstate,
if (co->co_posonlyargcount
&& positional_only_passed_as_keyword(tstate, co,
- kwcount, kwnames))
+ kwcount, kwnames, qualname))
{
goto fail;
}
_PyErr_Format(tstate, PyExc_TypeError,
"%U() got an unexpected keyword argument '%S'",
- co->co_name, keyword);
+ qualname, keyword);
goto fail;
}
@@ -4231,7 +4232,7 @@ _PyEval_EvalCode(PyThreadState *tstate,
if (GETLOCAL(j) != NULL) {
_PyErr_Format(tstate, PyExc_TypeError,
"%U() got multiple values for argument '%S'",
- co->co_name, keyword);
+ qualname, keyword);
goto fail;
}
Py_INCREF(value);
@@ -4240,7 +4241,7 @@ _PyEval_EvalCode(PyThreadState *tstate,
/* Check the number of positional arguments */
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;
}
@@ -4254,7 +4255,7 @@ _PyEval_EvalCode(PyThreadState *tstate,
}
}
if (missing) {
- missing_arguments(tstate, co, missing, defcount, fastlocals);
+ missing_arguments(tstate, co, missing, defcount, fastlocals, qualname);
goto fail;
}
if (n > m)
@@ -4292,7 +4293,7 @@ _PyEval_EvalCode(PyThreadState *tstate,
missing++;
}
if (missing) {
- missing_arguments(tstate, co, missing, -1, fastlocals);
+ missing_arguments(tstate, co, missing, -1, fastlocals, qualname);
goto fail;
}
}