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

Dennis Sweeney webhook-mailer at python.org
Fri May 22 16:40:26 EDT 2020


https://github.com/python/cpython/commit/b5cc2089cc354469f12eabc7ba54280e85fdd6dc
commit: b5cc2089cc354469f12eabc7ba54280e85fdd6dc
branch: master
author: Dennis Sweeney <36520290+sweeneyde at users.noreply.github.com>
committer: GitHub <noreply at github.com>
date: 2020-05-22T13:40:17-07:00
summary:

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

Patch by Dennis Sweeney.

files:
A Misc/NEWS.d/next/Core and Builtins/2020-05-19-19-39-49.bpo-40679.SVzz9p.rst
M Lib/test/test_call.py
M Lib/test/test_keywordonlyarg.py
M Python/ceval.c

diff --git a/Lib/test/test_call.py b/Lib/test/test_call.py
index 451a7170c304d..3f459222748b3 100644
--- a/Lib/test/test_call.py
+++ b/Lib/test/test_call.py
@@ -8,6 +8,7 @@
 import collections
 import itertools
 import gc
+import contextlib
 
 
 class FunctionCalls(unittest.TestCase):
@@ -665,5 +666,52 @@ def __call__(self, *args):
                 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
+
+ at 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 2cf8a89a078e8..df82f677a00a4 100644
--- a/Lib/test/test_keywordonlyarg.py
+++ b/Lib/test/test_keywordonlyarg.py
@@ -63,7 +63,8 @@ def f(a, b=None, *, c=None):
             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 0000000000000..2d0a432b6fa69
--- /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 43ea1c760b17e..a79773f85118a 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -3875,7 +3875,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
 
 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;
         }
     }



More information about the Python-checkins mailing list