Skip to content

Commit b5cc208

Browse files
authored
bpo-40679: Use the function's qualname in certain TypeErrors (GH-20236)
Patch by Dennis Sweeney.
1 parent 7c30d12 commit b5cc208

File tree

4 files changed

+68
-16
lines changed

4 files changed

+68
-16
lines changed

Lib/test/test_call.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import collections
99
import itertools
1010
import gc
11+
import contextlib
1112

1213

1314
class FunctionCalls(unittest.TestCase):
@@ -665,5 +666,52 @@ def __call__(self, *args):
665666
self.assertEqual(expected, wrapped(*args, **kwargs))
666667

667668

669+
class A:
670+
def method_two_args(self, x, y):
671+
pass
672+
673+
@staticmethod
674+
def static_no_args():
675+
pass
676+
677+
@staticmethod
678+
def positional_only(arg, /):
679+
pass
680+
681+
@cpython_only
682+
class TestErrorMessagesUseQualifiedName(unittest.TestCase):
683+
684+
@contextlib.contextmanager
685+
def check_raises_type_error(self, message):
686+
with self.assertRaises(TypeError) as cm:
687+
yield
688+
self.assertEqual(str(cm.exception), message)
689+
690+
def test_missing_arguments(self):
691+
msg = "A.method_two_args() missing 1 required positional argument: 'y'"
692+
with self.check_raises_type_error(msg):
693+
A().method_two_args("x")
694+
695+
def test_too_many_positional(self):
696+
msg = "A.static_no_args() takes 0 positional arguments but 1 was given"
697+
with self.check_raises_type_error(msg):
698+
A.static_no_args("oops it's an arg")
699+
700+
def test_positional_only_passed_as_keyword(self):
701+
msg = "A.positional_only() got some positional-only arguments passed as keyword arguments: 'arg'"
702+
with self.check_raises_type_error(msg):
703+
A.positional_only(arg="x")
704+
705+
def test_unexpected_keyword(self):
706+
msg = "A.method_two_args() got an unexpected keyword argument 'bad'"
707+
with self.check_raises_type_error(msg):
708+
A().method_two_args(bad="x")
709+
710+
def test_multiple_values(self):
711+
msg = "A.method_two_args() got multiple values for argument 'x'"
712+
with self.check_raises_type_error(msg):
713+
A().method_two_args("x", "y", x="oops")
714+
715+
668716
if __name__ == "__main__":
669717
unittest.main()

Lib/test/test_keywordonlyarg.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ def f(a, b=None, *, c=None):
6363
pass
6464
with self.assertRaises(TypeError) as exc:
6565
f(1, 2, 3)
66-
expected = "f() takes from 1 to 2 positional arguments but 3 were given"
66+
expected = (f"{f.__qualname__}() takes from 1 to 2 "
67+
"positional arguments but 3 were given")
6768
self.assertEqual(str(exc.exception), expected)
6869

6970
def testSyntaxErrorForFunctionCall(self):
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Certain :exc:`TypeError` messages about missing or extra arguments now include the function's
2+
:term:`qualified name`. Patch by Dennis Sweeney.

Python/ceval.c

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3875,7 +3875,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
38753875

38763876
static void
38773877
format_missing(PyThreadState *tstate, const char *kind,
3878-
PyCodeObject *co, PyObject *names)
3878+
PyCodeObject *co, PyObject *names, PyObject *qualname)
38793879
{
38803880
int err;
38813881
Py_ssize_t len = PyList_GET_SIZE(names);
@@ -3928,7 +3928,7 @@ format_missing(PyThreadState *tstate, const char *kind,
39283928
return;
39293929
_PyErr_Format(tstate, PyExc_TypeError,
39303930
"%U() missing %i required %s argument%s: %U",
3931-
co->co_name,
3931+
qualname,
39323932
len,
39333933
kind,
39343934
len == 1 ? "" : "s",
@@ -3939,7 +3939,7 @@ format_missing(PyThreadState *tstate, const char *kind,
39393939
static void
39403940
missing_arguments(PyThreadState *tstate, PyCodeObject *co,
39413941
Py_ssize_t missing, Py_ssize_t defcount,
3942-
PyObject **fastlocals)
3942+
PyObject **fastlocals, PyObject *qualname)
39433943
{
39443944
Py_ssize_t i, j = 0;
39453945
Py_ssize_t start, end;
@@ -3971,14 +3971,14 @@ missing_arguments(PyThreadState *tstate, PyCodeObject *co,
39713971
}
39723972
}
39733973
assert(j == missing);
3974-
format_missing(tstate, kind, co, missing_names);
3974+
format_missing(tstate, kind, co, missing_names, qualname);
39753975
Py_DECREF(missing_names);
39763976
}
39773977

39783978
static void
39793979
too_many_positional(PyThreadState *tstate, PyCodeObject *co,
39803980
Py_ssize_t given, Py_ssize_t defcount,
3981-
PyObject **fastlocals)
3981+
PyObject **fastlocals, PyObject *qualname)
39823982
{
39833983
int plural;
39843984
Py_ssize_t kwonly_given = 0;
@@ -4022,7 +4022,7 @@ too_many_positional(PyThreadState *tstate, PyCodeObject *co,
40224022
}
40234023
_PyErr_Format(tstate, PyExc_TypeError,
40244024
"%U() takes %U positional argument%s but %zd%U %s given",
4025-
co->co_name,
4025+
qualname,
40264026
sig,
40274027
plural ? "s" : "",
40284028
given,
@@ -4034,7 +4034,8 @@ too_many_positional(PyThreadState *tstate, PyCodeObject *co,
40344034

40354035
static int
40364036
positional_only_passed_as_keyword(PyThreadState *tstate, PyCodeObject *co,
4037-
Py_ssize_t kwcount, PyObject* const* kwnames)
4037+
Py_ssize_t kwcount, PyObject* const* kwnames,
4038+
PyObject *qualname)
40384039
{
40394040
int posonly_conflicts = 0;
40404041
PyObject* posonly_names = PyList_New(0);
@@ -4079,7 +4080,7 @@ positional_only_passed_as_keyword(PyThreadState *tstate, PyCodeObject *co,
40794080
_PyErr_Format(tstate, PyExc_TypeError,
40804081
"%U() got some positional-only arguments passed"
40814082
" as keyword arguments: '%U'",
4082-
co->co_name, error_names);
4083+
qualname, error_names);
40834084
Py_DECREF(error_names);
40844085
goto fail;
40854086
}
@@ -4180,7 +4181,7 @@ _PyEval_EvalCode(PyThreadState *tstate,
41804181
if (keyword == NULL || !PyUnicode_Check(keyword)) {
41814182
_PyErr_Format(tstate, PyExc_TypeError,
41824183
"%U() keywords must be strings",
4183-
co->co_name);
4184+
qualname);
41844185
goto fail;
41854186
}
41864187

@@ -4211,14 +4212,14 @@ _PyEval_EvalCode(PyThreadState *tstate,
42114212

42124213
if (co->co_posonlyargcount
42134214
&& positional_only_passed_as_keyword(tstate, co,
4214-
kwcount, kwnames))
4215+
kwcount, kwnames, qualname))
42154216
{
42164217
goto fail;
42174218
}
42184219

42194220
_PyErr_Format(tstate, PyExc_TypeError,
42204221
"%U() got an unexpected keyword argument '%S'",
4221-
co->co_name, keyword);
4222+
qualname, keyword);
42224223
goto fail;
42234224
}
42244225

@@ -4231,7 +4232,7 @@ _PyEval_EvalCode(PyThreadState *tstate,
42314232
if (GETLOCAL(j) != NULL) {
42324233
_PyErr_Format(tstate, PyExc_TypeError,
42334234
"%U() got multiple values for argument '%S'",
4234-
co->co_name, keyword);
4235+
qualname, keyword);
42354236
goto fail;
42364237
}
42374238
Py_INCREF(value);
@@ -4240,7 +4241,7 @@ _PyEval_EvalCode(PyThreadState *tstate,
42404241

42414242
/* Check the number of positional arguments */
42424243
if ((argcount > co->co_argcount) && !(co->co_flags & CO_VARARGS)) {
4243-
too_many_positional(tstate, co, argcount, defcount, fastlocals);
4244+
too_many_positional(tstate, co, argcount, defcount, fastlocals, qualname);
42444245
goto fail;
42454246
}
42464247

@@ -4254,7 +4255,7 @@ _PyEval_EvalCode(PyThreadState *tstate,
42544255
}
42554256
}
42564257
if (missing) {
4257-
missing_arguments(tstate, co, missing, defcount, fastlocals);
4258+
missing_arguments(tstate, co, missing, defcount, fastlocals, qualname);
42584259
goto fail;
42594260
}
42604261
if (n > m)
@@ -4292,7 +4293,7 @@ _PyEval_EvalCode(PyThreadState *tstate,
42924293
missing++;
42934294
}
42944295
if (missing) {
4295-
missing_arguments(tstate, co, missing, -1, fastlocals);
4296+
missing_arguments(tstate, co, missing, -1, fastlocals, qualname);
42964297
goto fail;
42974298
}
42984299
}

0 commit comments

Comments
 (0)