20 Nov
2018
20 Nov
'18
7:27 p.m.
https://github.com/python/cpython/commit/97f1efb6062188645a470daaa91e3669d739c75f commit: 97f1efb6062188645a470daaa91e3669d739c75f branch: master author: Serhiy Storchaka <storchaka@gmail.com> committer: GitHub <noreply@github.com> date: 2018-11-20T19:27:16+02:00 summary: bpo-35169: Improve error messages for forbidden assignments. (GH-10342) files: A Misc/NEWS.d/next/Core and Builtins/2018-11-05-21-19-05.bpo-35169._FyPI2.rst M Lib/test/test_dictcomps.py M Lib/test/test_generators.py M Lib/test/test_genexps.py M Lib/test/test_syntax.py M Python/ast.c diff --git a/Lib/test/test_dictcomps.py b/Lib/test/test_dictcomps.py index 087307188343..afe68a8de753 100644 --- a/Lib/test/test_dictcomps.py +++ b/Lib/test/test_dictcomps.py @@ -73,11 +73,11 @@ def test_local_visibility(self): self.assertEqual(v, "Local variable") def test_illegal_assignment(self): - with self.assertRaisesRegex(SyntaxError, "can't assign"): + with self.assertRaisesRegex(SyntaxError, "cannot assign"): compile("{x: y for y, x in ((1, 2), (3, 4))} = 5", "<test>", "exec") - with self.assertRaisesRegex(SyntaxError, "can't assign"): + with self.assertRaisesRegex(SyntaxError, "cannot assign"): compile("{x: y for y, x in ((1, 2), (3, 4))} += 5", "<test>", "exec") diff --git a/Lib/test/test_generators.py b/Lib/test/test_generators.py index 7a21cb7e954a..320793c7dab6 100644 --- a/Lib/test/test_generators.py +++ b/Lib/test/test_generators.py @@ -1865,12 +1865,12 @@ def printsolution(self, x): >>> def f(): (yield bar) = y Traceback (most recent call last): ... -SyntaxError: can't assign to yield expression +SyntaxError: cannot assign to yield expression >>> def f(): (yield bar) += y Traceback (most recent call last): ... -SyntaxError: can't assign to yield expression +SyntaxError: cannot assign to yield expression Now check some throw() conditions: diff --git a/Lib/test/test_genexps.py b/Lib/test/test_genexps.py index fb531d6d472b..fd712bb172d5 100644 --- a/Lib/test/test_genexps.py +++ b/Lib/test/test_genexps.py @@ -137,12 +137,12 @@ >>> (y for y in (1,2)) = 10 Traceback (most recent call last): ... - SyntaxError: can't assign to generator expression + SyntaxError: cannot assign to generator expression >>> (y for y in (1,2)) += 10 Traceback (most recent call last): ... - SyntaxError: can't assign to generator expression + SyntaxError: cannot assign to generator expression ########### Tests borrowed from or inspired by test_generators.py ############ diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py index c5b2496e010d..ce1de4b319f2 100644 --- a/Lib/test/test_syntax.py +++ b/Lib/test/test_syntax.py @@ -33,35 +33,55 @@ >>> None = 1 Traceback (most recent call last): -SyntaxError: can't assign to keyword +SyntaxError: cannot assign to None + +>>> obj.True = 1 +Traceback (most recent call last): +SyntaxError: invalid syntax + +>>> True = 1 +Traceback (most recent call last): +SyntaxError: cannot assign to True + +>>> obj.__debug__ = 1 +Traceback (most recent call last): +SyntaxError: cannot assign to __debug__ + +>>> __debug__ = 1 +Traceback (most recent call last): +SyntaxError: cannot assign to __debug__ >>> f() = 1 Traceback (most recent call last): -SyntaxError: can't assign to function call +SyntaxError: cannot assign to function call >>> del f() Traceback (most recent call last): -SyntaxError: can't delete function call +SyntaxError: cannot delete function call >>> a + 1 = 2 Traceback (most recent call last): -SyntaxError: can't assign to operator +SyntaxError: cannot assign to operator >>> (x for x in x) = 1 Traceback (most recent call last): -SyntaxError: can't assign to generator expression +SyntaxError: cannot assign to generator expression >>> 1 = 1 Traceback (most recent call last): -SyntaxError: can't assign to literal +SyntaxError: cannot assign to literal >>> "abc" = 1 Traceback (most recent call last): -SyntaxError: can't assign to literal +SyntaxError: cannot assign to literal >>> b"" = 1 Traceback (most recent call last): -SyntaxError: can't assign to literal +SyntaxError: cannot assign to literal + +>>> ... = 1 +Traceback (most recent call last): +SyntaxError: cannot assign to Ellipsis >>> `1` = 1 Traceback (most recent call last): @@ -74,15 +94,31 @@ >>> (a, "b", c) = (1, 2, 3) Traceback (most recent call last): -SyntaxError: can't assign to literal +SyntaxError: cannot assign to literal + +>>> (a, True, c) = (1, 2, 3) +Traceback (most recent call last): +SyntaxError: cannot assign to True + +>>> (a, __debug__, c) = (1, 2, 3) +Traceback (most recent call last): +SyntaxError: cannot assign to __debug__ + +>>> (a, *True, c) = (1, 2, 3) +Traceback (most recent call last): +SyntaxError: cannot assign to True + +>>> (a, *__debug__, c) = (1, 2, 3) +Traceback (most recent call last): +SyntaxError: cannot assign to __debug__ >>> [a, b, c + 1] = [1, 2, 3] Traceback (most recent call last): -SyntaxError: can't assign to operator +SyntaxError: cannot assign to operator >>> a if 1 else b = 1 Traceback (most recent call last): -SyntaxError: can't assign to conditional expression +SyntaxError: cannot assign to conditional expression From compiler_complex_args(): @@ -255,36 +291,45 @@ >>> f(lambda x: x[0] = 3) Traceback (most recent call last): -SyntaxError: lambda cannot contain assignment +SyntaxError: expression cannot contain assignment, perhaps you meant "=="? The grammar accepts any test (basically, any expression) in the keyword slot of a call site. Test a few different options. >>> f(x()=2) Traceback (most recent call last): -SyntaxError: keyword can't be an expression +SyntaxError: expression cannot contain assignment, perhaps you meant "=="? >>> f(a or b=1) Traceback (most recent call last): -SyntaxError: keyword can't be an expression +SyntaxError: expression cannot contain assignment, perhaps you meant "=="? >>> f(x.y=1) Traceback (most recent call last): -SyntaxError: keyword can't be an expression +SyntaxError: expression cannot contain assignment, perhaps you meant "=="? >>> f((x)=2) Traceback (most recent call last): -SyntaxError: keyword can't be an expression +SyntaxError: expression cannot contain assignment, perhaps you meant "=="? +>>> f(True=2) +Traceback (most recent call last): +SyntaxError: cannot assign to True +>>> f(__debug__=1) +Traceback (most recent call last): +SyntaxError: cannot assign to __debug__ More set_context(): >>> (x for x in x) += 1 Traceback (most recent call last): -SyntaxError: can't assign to generator expression +SyntaxError: cannot assign to generator expression >>> None += 1 Traceback (most recent call last): -SyntaxError: can't assign to keyword +SyntaxError: cannot assign to None +>>> __debug__ += 1 +Traceback (most recent call last): +SyntaxError: cannot assign to __debug__ >>> f() += 1 Traceback (most recent call last): -SyntaxError: can't assign to function call +SyntaxError: cannot assign to function call Test continue in finally in weird combinations. @@ -481,7 +526,7 @@ ... pass Traceback (most recent call last): ... - SyntaxError: can't assign to function call + SyntaxError: cannot assign to function call >>> if 1: ... pass @@ -489,7 +534,7 @@ ... x() = 1 Traceback (most recent call last): ... - SyntaxError: can't assign to function call + SyntaxError: cannot assign to function call >>> if 1: ... x() = 1 @@ -499,7 +544,7 @@ ... pass Traceback (most recent call last): ... - SyntaxError: can't assign to function call + SyntaxError: cannot assign to function call >>> if 1: ... pass @@ -509,7 +554,7 @@ ... pass Traceback (most recent call last): ... - SyntaxError: can't assign to function call + SyntaxError: cannot assign to function call >>> if 1: ... pass @@ -519,7 +564,7 @@ ... x() = 1 Traceback (most recent call last): ... - SyntaxError: can't assign to function call + SyntaxError: cannot assign to function call Make sure that the old "raise X, Y[, Z]" form is gone: >>> raise X, Y @@ -539,21 +584,33 @@ >>> {1, 2, 3} = 42 Traceback (most recent call last): -SyntaxError: can't assign to literal +SyntaxError: cannot assign to set display + +>>> {1: 2, 3: 4} = 42 +Traceback (most recent call last): +SyntaxError: cannot assign to dict display + +>>> f'{x}' = 42 +Traceback (most recent call last): +SyntaxError: cannot assign to f-string expression + +>>> f'{x}-{y}' = 42 +Traceback (most recent call last): +SyntaxError: cannot assign to f-string expression Corner-cases that used to fail to raise the correct error: >>> def f(*, x=lambda __debug__:0): pass Traceback (most recent call last): - SyntaxError: assignment to keyword + SyntaxError: cannot assign to __debug__ >>> def f(*args:(lambda __debug__:0)): pass Traceback (most recent call last): - SyntaxError: assignment to keyword + SyntaxError: cannot assign to __debug__ >>> def f(**kwargs:(lambda __debug__:0)): pass Traceback (most recent call last): - SyntaxError: assignment to keyword + SyntaxError: cannot assign to __debug__ >>> with (lambda *:0): pass Traceback (most recent call last): @@ -563,11 +620,11 @@ >>> def f(**__debug__): pass Traceback (most recent call last): - SyntaxError: assignment to keyword + SyntaxError: cannot assign to __debug__ >>> def f(*xx, __debug__): pass Traceback (most recent call last): - SyntaxError: assignment to keyword + SyntaxError: cannot assign to __debug__ """ diff --git a/Misc/NEWS.d/next/Core and Builtins/2018-11-05-21-19-05.bpo-35169._FyPI2.rst b/Misc/NEWS.d/next/Core and Builtins/2018-11-05-21-19-05.bpo-35169._FyPI2.rst new file mode 100644 index 000000000000..63c28f423ac2 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2018-11-05-21-19-05.bpo-35169._FyPI2.rst @@ -0,0 +1 @@ +Improved error messages for forbidden assignments. diff --git a/Python/ast.c b/Python/ast.c index d5c7ce6982d1..7f72aa2bd373 100644 --- a/Python/ast.c +++ b/Python/ast.c @@ -647,21 +647,25 @@ new_identifier(const char *n, struct compiling *c) #define NEW_IDENTIFIER(n) new_identifier(STR(n), c) static int -ast_error(struct compiling *c, const node *n, const char *errmsg) +ast_error(struct compiling *c, const node *n, const char *errmsg, ...) { PyObject *value, *errstr, *loc, *tmp; + va_list va; + va_start(va, errmsg); + errstr = PyUnicode_FromFormatV(errmsg, va); + va_end(va); + if (!errstr) { + return 0; + } loc = PyErr_ProgramTextObject(c->c_filename, LINENO(n)); if (!loc) { Py_INCREF(Py_None); loc = Py_None; } tmp = Py_BuildValue("(OiiN)", c->c_filename, LINENO(n), n->n_col_offset + 1, loc); - if (!tmp) - return 0; - errstr = PyUnicode_FromString(errmsg); - if (!errstr) { - Py_DECREF(tmp); + if (!tmp) { + Py_DECREF(errstr); return 0; } value = PyTuple_Pack(2, errstr, tmp); @@ -903,6 +907,7 @@ static const char * const FORBIDDEN[] = { "None", "True", "False", + "__debug__", NULL, }; @@ -911,17 +916,16 @@ forbidden_name(struct compiling *c, identifier name, const node *n, int full_checks) { assert(PyUnicode_Check(name)); - if (_PyUnicode_EqualToASCIIString(name, "__debug__")) { - ast_error(c, n, "assignment to keyword"); - return 1; - } - if (full_checks) { - const char * const *p; - for (p = FORBIDDEN; *p; p++) { - if (_PyUnicode_EqualToASCIIString(name, *p)) { - ast_error(c, n, "assignment to keyword"); - return 1; - } + const char * const *p = FORBIDDEN; + if (!full_checks) { + /* In most cases, the parser will protect True, False, and None + from being assign to. */ + p += 3; + } + for (; *p; p++) { + if (_PyUnicode_EqualToASCIIString(name, *p)) { + ast_error(c, n, "cannot assign to %U", name); + return 1; } } return 0; @@ -1012,22 +1016,25 @@ set_context(struct compiling *c, expr_ty e, expr_context_ty ctx, const node *n) expr_name = "dict comprehension"; break; case Dict_kind: + expr_name = "dict display"; + break; case Set_kind: + expr_name = "set display"; + break; case JoinedStr_kind: case FormattedValue_kind: - expr_name = "literal"; + expr_name = "f-string expression"; break; case Constant_kind: { PyObject *value = e->v.Constant.value; - if (value == Py_None || value == Py_False || value == Py_True) { - expr_name = "keyword"; - } - else if (value == Py_Ellipsis) { - expr_name = "Ellipsis"; - } - else { - expr_name = "literal"; + if (value == Py_None || value == Py_False || value == Py_True + || value == Py_Ellipsis) + { + return ast_error(c, n, "cannot %s %R", + ctx == Store ? "assign to" : "delete", + value); } + expr_name = "literal"; break; } case Compare_kind: @@ -1044,12 +1051,9 @@ set_context(struct compiling *c, expr_ty e, expr_context_ty ctx, const node *n) } /* Check for error string set by switch */ if (expr_name) { - char buf[300]; - PyOS_snprintf(buf, sizeof(buf), - "can't %s %s", - ctx == Store ? "assign to" : "delete", - expr_name); - return ast_error(c, n, buf); + return ast_error(c, n, "cannot %s %s", + ctx == Store ? "assign to" : "delete", + expr_name); } /* If the LHS is a list or tuple, we need to set the assignment @@ -2083,21 +2087,17 @@ ast_for_atom(struct compiling *c, const node *n) else if (PyErr_ExceptionMatches(PyExc_ValueError)) errtype = "value error"; if (errtype) { - char buf[128]; - const char *s = NULL; PyObject *type, *value, *tback, *errstr; PyErr_Fetch(&type, &value, &tback); errstr = PyObject_Str(value); - if (errstr) - s = PyUnicode_AsUTF8(errstr); - if (s) { - PyOS_snprintf(buf, sizeof(buf), "(%s) %s", errtype, s); - } else { + if (errstr) { + ast_error(c, n, "(%s) %U", errtype, errstr); + Py_DECREF(errstr); + } + else { PyErr_Clear(); - PyOS_snprintf(buf, sizeof(buf), "(%s) unknown error", errtype); + ast_error(c, n, "(%s) unknown error", errtype); } - Py_XDECREF(errstr); - ast_error(c, n, buf); Py_DECREF(type); Py_XDECREF(value); Py_XDECREF(tback); @@ -2815,18 +2815,10 @@ ast_for_call(struct compiling *c, const node *n, expr_ty func, bool allowgen) break; expr_node = CHILD(expr_node, 0); } - if (TYPE(expr_node) == lambdef) { - // f(lambda x: x[0] = 3) ends up getting parsed with LHS - // test = lambda x: x[0], and RHS test = 3. Issue #132313 - // points out that complaining about a keyword then is very - // confusing. + if (TYPE(expr_node) != NAME) { ast_error(c, chch, - "lambda cannot contain assignment"); - return NULL; - } - else if (TYPE(expr_node) != NAME) { - ast_error(c, chch, - "keyword can't be an expression"); + "expression cannot contain assignment, " + "perhaps you meant \"==\"?"); return NULL; } key = new_identifier(STR(expr_node), c); @@ -4127,16 +4119,10 @@ warn_invalid_escape_sequence(struct compiling *c, const node *n, NULL, NULL) < 0) { if (PyErr_ExceptionMatches(PyExc_SyntaxWarning)) { - const char *s; - /* Replace the SyntaxWarning exception with a SyntaxError to get a more accurate error report */ PyErr_Clear(); - - s = PyUnicode_AsUTF8(msg); - if (s != NULL) { - ast_error(c, n, s); - } + ast_error(c, n, "%U", msg); } Py_DECREF(msg); return -1;