Thanks to visit codestin.com
Credit goes to github.com

Skip to content

bpo-40679: use a function's qualname in error messages #20236

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
May 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions Lib/test/test_call.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import collections
import itertools
import gc
import contextlib


class FunctionCalls(unittest.TestCase):
Expand Down Expand Up @@ -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

@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()
3 changes: 2 additions & 1 deletion Lib/test/test_keywordonlyarg.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
31 changes: 16 additions & 15 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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",
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand All @@ -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);
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there any suggestions for how to test this from Python? I couldn't figure out how without getting a SyntaxError.

Copy link
Contributor

@SylvainDe SylvainDe May 20, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the ** should do the trick.
For example:

>>> func = lambda *args, **kwargs: None
>>> kwargs = {None: None}
>>> func(kwargs)
>>> func(*kwargs)
>>> func(**kwargs)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: <lambda>() keywords must be strings

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately it looks like this behavior changed a bit in the newest releases so that that code isn't actually reached:

PS > py -3.7 -c "f = lambda x: None; f(**{1:1})"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
TypeError: <lambda>() keywords must be strings
PS > py -3.8 -c "f = lambda x: None; f(**{1:1})"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
TypeError: <lambda>() keywords must be strings
PS > py -3.9 -c "f = lambda x: None; f(**{1:1})"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
TypeError: keywords must be strings

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I see... Probably related to 0567786 .

goto fail;
}

Expand Down Expand Up @@ -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;
}

Expand All @@ -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);
Expand All @@ -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;
}

Expand All @@ -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)
Expand Down Expand Up @@ -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;
}
}
Expand Down