diff --git a/Include/eval.h b/Include/eval.h --- a/Include/eval.h +++ b/Include/eval.h @@ -10,12 +10,21 @@ PyAPI_FUNC(PyObject *) PyEval_EvalCode(PyObject *, PyObject *, PyObject *); PyAPI_FUNC(PyObject *) PyEval_EvalCodeEx(PyObject *co, - PyObject *globals, - PyObject *locals, - PyObject **args, int argc, - PyObject **kwds, int kwdc, - PyObject **defs, int defc, - PyObject *kwdefs, PyObject *closure); + PyObject *globals, + PyObject *locals, + PyObject **args, int argc, + PyObject **kwds, int kwdc, + PyObject **defs, int defc, + PyObject *kwdefs, PyObject *closure); + +PyAPI_FUNC(PyObject *) PyEval_EvalCodeEx2(PyObject *co, + PyObject *globals, + PyObject *locals, + PyObject **args, int argc, + PyObject **kwds, int kwdc, + PyObject **defs, int defc, + PyObject *kwdefs, PyObject *closure, + PyObject *qualname); #ifndef Py_LIMITED_API PyAPI_FUNC(PyObject *) _PyEval_CallTracing(PyObject *func, PyObject *args); diff --git a/Lib/test/test_extcall.py b/Lib/test/test_extcall.py --- a/Lib/test/test_extcall.py +++ b/Lib/test/test_extcall.py @@ -317,6 +317,42 @@ Traceback (most recent call last): ... TypeError: f() takes from 1 to 2 positional arguments but 3 positional arguments (and 1 keyword-only argument) were given + >>> class A: + ... def __init__(): pass + >>> A() + Traceback (most recent call last): + ... + TypeError: A.__init__() takes 0 positional arguments but 1 was given + >>> class A: + ... def __init__(a): pass + >>> A(1) + Traceback (most recent call last): + ... + TypeError: A.__init__() takes 1 positional argument but 2 were given + >>> class A: + ... def __init__(a, b=1): pass + >>> A(1, 2) + Traceback (most recent call last): + ... + TypeError: A.__init__() takes from 1 to 2 positional arguments but 3 were given + >>> class A: + ... def __init__(*, kw): pass + >>> A(kw=3) + Traceback (most recent call last): + ... + TypeError: A.__init__() takes 0 positional arguments but 1 positional argument (and 1 keyword-only argument) were given + >>> class A: + ... def __init__(*, kw, b): pass + >>> A(1, 2, b=3, kw=3) + Traceback (most recent call last): + ... + TypeError: A.__init__() takes 0 positional arguments but 3 positional arguments (and 2 keyword-only arguments) were given + >>> class A: + ... def __init__(a, b=2, *, kw): pass + >>> A(2, 3, kw=4) + Traceback (most recent call last): + ... + TypeError: A.__init__() takes from 1 to 2 positional arguments but 3 positional arguments (and 1 keyword-only argument) were given Too few and missing arguments: @@ -345,6 +381,41 @@ Traceback (most recent call last): ... TypeError: f() missing 1 required positional argument: 'a' + >>> class A: + ... @staticmethod + ... def f(a): pass + >>> A.f() + Traceback (most recent call last): + ... + TypeError: A.f() missing 1 required positional argument: 'a' + >>> class A: + ... @staticmethod + ... def f(a, b): pass + >>> A.f() + Traceback (most recent call last): + ... + TypeError: A.f() missing 2 required positional arguments: 'a' and 'b' + >>> class A: + ... @staticmethod + ... def f(a, b, c): pass + >>> A.f() + Traceback (most recent call last): + ... + TypeError: A.f() missing 3 required positional arguments: 'a', 'b', and 'c' + >>> class A: + ... @staticmethod + ... def f(a, b, c, d, e): pass + >>> A.f() + Traceback (most recent call last): + ... + TypeError: A.f() missing 5 required positional arguments: 'a', 'b', 'c', 'd', and 'e' + >>> class A: + ... @staticmethod + ... def f(a, b=4, c=5, d=5): pass + >>> A.f(c=12, b=9) + Traceback (most recent call last): + ... + TypeError: A.f() missing 1 required positional argument: 'a' Same with keyword only args: @@ -358,7 +429,37 @@ Traceback (most recent call last): ... TypeError: f() missing 5 required keyword-only arguments: 'a', 'b', 'c', 'd', and 'e' + >>> class A: + ... @staticmethod + ... def f(*, w): pass + >>> A.f() + Traceback (most recent call last): + ... + TypeError: A.f() missing 1 required keyword-only argument: 'w' + >>> class A: + ... @staticmethod + ... def f(*, a, b, c, d, e): pass + >>> A.f() + Traceback (most recent call last): + ... + TypeError: A.f() missing 5 required keyword-only arguments: 'a', 'b', 'c', 'd', and 'e' +Test errors in inner functions: + + >>> def f(): + ... def g(a): pass + ... g() + >>> f() + Traceback (most recent call last): + ... + TypeError: f..g() missing 1 required positional argument: 'a' + >>> def f(): + ... def g(): pass + ... g(1) + >>> f() + Traceback (most recent call last): + ... + TypeError: f..g() takes 0 positional arguments but 1 was given """ import sys diff --git a/Lib/test/test_keywordonlyarg.py b/Lib/test/test_keywordonlyarg.py --- a/Lib/test/test_keywordonlyarg.py +++ b/Lib/test/test_keywordonlyarg.py @@ -75,7 +75,9 @@ 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 = ("KeywordOnlyArgTestCase.testTooManyPositionalErrorMessage." + ".f() takes from 1 to 2 positional arguments but 3 were " + "given") self.assertEqual(str(exc.exception), expected) def testSyntaxErrorForFunctionCall(self): diff --git a/Misc/ACKS b/Misc/ACKS --- a/Misc/ACKS +++ b/Misc/ACKS @@ -155,6 +155,7 @@ Gawain Bolton Forest Bond Gregory Bond +Daniil Bondarev Médéric Boquien Matias Bordese Jonas Borgström diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -10,6 +10,9 @@ Core and Builtins ----------------- +- Issue #2786: Names in tracebacks have class names. + Patch from Daniil Bondarev. + - Issue #21167: NAN operations are now handled correctly when python is compiled with ICC even if -fp-model strict is not specified. diff --git a/Objects/funcobject.c b/Objects/funcobject.c --- a/Objects/funcobject.c +++ b/Objects/funcobject.c @@ -629,13 +629,14 @@ nk = 0; } - result = PyEval_EvalCodeEx( + result = PyEval_EvalCodeEx2( PyFunction_GET_CODE(func), PyFunction_GET_GLOBALS(func), (PyObject *)NULL, &PyTuple_GET_ITEM(arg, 0), PyTuple_GET_SIZE(arg), k, nk, d, nd, PyFunction_GET_KW_DEFAULTS(func), - PyFunction_GET_CLOSURE(func)); + PyFunction_GET_CLOSURE(func), + ((PyFunctionObject *)func)->func_qualname); Py_XDECREF(kwtuple); diff --git a/PC/python3.def b/PC/python3.def --- a/PC/python3.def +++ b/PC/python3.def @@ -151,6 +151,7 @@ PyEval_CallObjectWithKeywords=python36.PyEval_CallObjectWithKeywords PyEval_EvalCode=python36.PyEval_EvalCode PyEval_EvalCodeEx=python36.PyEval_EvalCodeEx + PyEval_EvalCodeEx2=python36.PyEval_EvalCodeEx2 PyEval_EvalFrame=python36.PyEval_EvalFrame PyEval_EvalFrameEx=python36.PyEval_EvalFrameEx PyEval_GetBuiltins=python36.PyEval_GetBuiltins diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -173,9 +173,10 @@ Py_DECREF(bases); return NULL; } - cell = PyEval_EvalCodeEx(PyFunction_GET_CODE(func), PyFunction_GET_GLOBALS(func), ns, - NULL, 0, NULL, 0, NULL, 0, NULL, - PyFunction_GET_CLOSURE(func)); + cell = PyEval_EvalCodeEx2(PyFunction_GET_CODE(func), PyFunction_GET_GLOBALS(func), ns, + NULL, 0, NULL, 0, NULL, 0, NULL, + PyFunction_GET_CLOSURE(func), + ((PyFunctionObject *)func)->func_qualname); if (cell != NULL) { PyObject *margs; margs = PyTuple_Pack(3, name, bases, ns); diff --git a/Python/ceval.c b/Python/ceval.c --- a/Python/ceval.c +++ b/Python/ceval.c @@ -774,12 +774,12 @@ PyObject * PyEval_EvalCode(PyObject *co, PyObject *globals, PyObject *locals) { - return PyEval_EvalCodeEx(co, + return PyEval_EvalCodeEx2(co, globals, locals, (PyObject **)NULL, 0, (PyObject **)NULL, 0, (PyObject **)NULL, 0, - NULL, NULL); + NULL, NULL, NULL); } @@ -3577,7 +3577,8 @@ } static void -format_missing(const char *kind, PyCodeObject *co, PyObject *names) +format_missing(const char *kind, PyCodeObject *co, PyObject *names, + PyObject *qualname) { int err; Py_ssize_t len = PyList_GET_SIZE(names); @@ -3630,7 +3631,7 @@ return; PyErr_Format(PyExc_TypeError, "%U() missing %i required %s argument%s: %U", - co->co_name, + qualname ? qualname : co->co_name, len, kind, len == 1 ? "" : "s", @@ -3640,7 +3641,7 @@ static void missing_arguments(PyCodeObject *co, int missing, int defcount, - PyObject **fastlocals) + PyObject **fastlocals, PyObject *qualname) { int i, j = 0; int start, end; @@ -3672,12 +3673,13 @@ } } assert(j == missing); - format_missing(kind, co, missing_names); + format_missing(kind, co, missing_names, qualname); Py_DECREF(missing_names); } static void -too_many_positional(PyCodeObject *co, int given, int defcount, PyObject **fastlocals) +too_many_positional(PyCodeObject *co, int given, int defcount, PyObject **fastlocals, + PyObject *qualname) { int plural; int kwonly_given = 0; @@ -3716,7 +3718,7 @@ } PyErr_Format(PyExc_TypeError, "%U() takes %U positional argument%s but %d%U %s given", - co->co_name, + qualname ? qualname : co->co_name, sig, plural ? "s" : "", given, @@ -3798,7 +3800,7 @@ if (keyword == NULL || !PyUnicode_Check(keyword)) { PyErr_Format(PyExc_TypeError, "%U() keywords must be strings", - co->co_name); + qualname ? qualname : co->co_name); goto fail; } /* Speed hack: do raw pointer compares. As names are @@ -3823,7 +3825,7 @@ PyErr_Format(PyExc_TypeError, "%U() got an unexpected " "keyword argument '%S'", - co->co_name, + qualname ? qualname : co->co_name, keyword); goto fail; } @@ -3836,7 +3838,7 @@ PyErr_Format(PyExc_TypeError, "%U() got multiple " "values for argument '%S'", - co->co_name, + qualname ? qualname : co->co_name, keyword); goto fail; } @@ -3844,7 +3846,7 @@ SETLOCAL(j, value); } if (argcount > co->co_argcount && !(co->co_flags & CO_VARARGS)) { - too_many_positional(co, argcount, defcount, fastlocals); + too_many_positional(co, argcount, defcount, fastlocals, qualname); goto fail; } if (argcount < co->co_argcount) { @@ -3854,7 +3856,7 @@ if (GETLOCAL(i) == NULL) missing++; if (missing) { - missing_arguments(co, missing, defcount, fastlocals); + missing_arguments(co, missing, defcount, fastlocals, qualname); goto fail; } if (n > m) @@ -3887,7 +3889,7 @@ missing++; } if (missing) { - missing_arguments(co, missing, -1, fastlocals); + missing_arguments(co, missing, -1, fastlocals, qualname); goto fail; } } @@ -3986,6 +3988,18 @@ NULL, NULL); } +PyObject * +PyEval_EvalCodeEx2(PyObject *_co, PyObject *globals, PyObject *locals, + PyObject **args, int argcount, PyObject **kws, int kwcount, + PyObject **defs, int defcount, PyObject *kwdefs, PyObject *closure, + PyObject *qualname) +{ + return _PyEval_EvalCodeWithName(_co, globals, locals, + args, argcount, kws, kwcount, + defs, defcount, kwdefs, closure, + NULL, qualname); +} + static PyObject * special_lookup(PyObject *o, _Py_Identifier *id) {