Commit 97f1efb6 authored by Serhiy Storchaka's avatar Serhiy Storchaka Committed by GitHub

bpo-35169: Improve error messages for forbidden assignments. (GH-10342)

parent 6c48bf2d
...@@ -73,11 +73,11 @@ class DictComprehensionTest(unittest.TestCase): ...@@ -73,11 +73,11 @@ class DictComprehensionTest(unittest.TestCase):
self.assertEqual(v, "Local variable") self.assertEqual(v, "Local variable")
def test_illegal_assignment(self): 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>", compile("{x: y for y, x in ((1, 2), (3, 4))} = 5", "<test>",
"exec") "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>", compile("{x: y for y, x in ((1, 2), (3, 4))} += 5", "<test>",
"exec") "exec")
......
...@@ -1865,12 +1865,12 @@ SyntaxError: assignment to yield expression not possible ...@@ -1865,12 +1865,12 @@ SyntaxError: assignment to yield expression not possible
>>> def f(): (yield bar) = y >>> def f(): (yield bar) = y
Traceback (most recent call last): Traceback (most recent call last):
... ...
SyntaxError: can't assign to yield expression SyntaxError: cannot assign to yield expression
>>> def f(): (yield bar) += y >>> def f(): (yield bar) += y
Traceback (most recent call last): Traceback (most recent call last):
... ...
SyntaxError: can't assign to yield expression SyntaxError: cannot assign to yield expression
Now check some throw() conditions: Now check some throw() conditions:
......
...@@ -137,12 +137,12 @@ Verify that syntax error's are raised for genexps used as lvalues ...@@ -137,12 +137,12 @@ Verify that syntax error's are raised for genexps used as lvalues
>>> (y for y in (1,2)) = 10 >>> (y for y in (1,2)) = 10
Traceback (most recent call last): 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 >>> (y for y in (1,2)) += 10
Traceback (most recent call last): 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 ############ ########### Tests borrowed from or inspired by test_generators.py ############
......
...@@ -33,35 +33,55 @@ SyntaxError: invalid syntax ...@@ -33,35 +33,55 @@ SyntaxError: invalid syntax
>>> None = 1 >>> None = 1
Traceback (most recent call last): 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 >>> f() = 1
Traceback (most recent call last): Traceback (most recent call last):
SyntaxError: can't assign to function call SyntaxError: cannot assign to function call
>>> del f() >>> del f()
Traceback (most recent call last): Traceback (most recent call last):
SyntaxError: can't delete function call SyntaxError: cannot delete function call
>>> a + 1 = 2 >>> a + 1 = 2
Traceback (most recent call last): Traceback (most recent call last):
SyntaxError: can't assign to operator SyntaxError: cannot assign to operator
>>> (x for x in x) = 1 >>> (x for x in x) = 1
Traceback (most recent call last): Traceback (most recent call last):
SyntaxError: can't assign to generator expression SyntaxError: cannot assign to generator expression
>>> 1 = 1 >>> 1 = 1
Traceback (most recent call last): Traceback (most recent call last):
SyntaxError: can't assign to literal SyntaxError: cannot assign to literal
>>> "abc" = 1 >>> "abc" = 1
Traceback (most recent call last): Traceback (most recent call last):
SyntaxError: can't assign to literal SyntaxError: cannot assign to literal
>>> b"" = 1 >>> b"" = 1
Traceback (most recent call last): 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 >>> `1` = 1
Traceback (most recent call last): Traceback (most recent call last):
...@@ -74,15 +94,31 @@ them. ...@@ -74,15 +94,31 @@ them.
>>> (a, "b", c) = (1, 2, 3) >>> (a, "b", c) = (1, 2, 3)
Traceback (most recent call last): 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] >>> [a, b, c + 1] = [1, 2, 3]
Traceback (most recent call last): Traceback (most recent call last):
SyntaxError: can't assign to operator SyntaxError: cannot assign to operator
>>> a if 1 else b = 1 >>> a if 1 else b = 1
Traceback (most recent call last): Traceback (most recent call last):
SyntaxError: can't assign to conditional expression SyntaxError: cannot assign to conditional expression
From compiler_complex_args(): From compiler_complex_args():
...@@ -255,36 +291,45 @@ SyntaxError: invalid syntax ...@@ -255,36 +291,45 @@ SyntaxError: invalid syntax
>>> f(lambda x: x[0] = 3) >>> f(lambda x: x[0] = 3)
Traceback (most recent call last): 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 The grammar accepts any test (basically, any expression) in the
keyword slot of a call site. Test a few different options. keyword slot of a call site. Test a few different options.
>>> f(x()=2) >>> f(x()=2)
Traceback (most recent call last): 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) >>> f(a or b=1)
Traceback (most recent call last): Traceback (most recent call last):
SyntaxError: keyword can't be an expression SyntaxError: expression cannot contain assignment, perhaps you meant "=="?
>>> f(x.y=1) >>> f(x.y=1)
Traceback (most recent call last): Traceback (most recent call last):
SyntaxError: keyword can't be an expression SyntaxError: expression cannot contain assignment, perhaps you meant "=="?
>>> f((x)=2) >>> f((x)=2)
Traceback (most recent call last): 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(): More set_context():
>>> (x for x in x) += 1 >>> (x for x in x) += 1
Traceback (most recent call last): Traceback (most recent call last):
SyntaxError: can't assign to generator expression SyntaxError: cannot assign to generator expression
>>> None += 1 >>> None += 1
Traceback (most recent call last): 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 >>> f() += 1
Traceback (most recent call last): 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. Test continue in finally in weird combinations.
...@@ -481,7 +526,7 @@ leading to spurious errors. ...@@ -481,7 +526,7 @@ leading to spurious errors.
... pass ... pass
Traceback (most recent call last): Traceback (most recent call last):
... ...
SyntaxError: can't assign to function call SyntaxError: cannot assign to function call
>>> if 1: >>> if 1:
... pass ... pass
...@@ -489,7 +534,7 @@ leading to spurious errors. ...@@ -489,7 +534,7 @@ leading to spurious errors.
... x() = 1 ... x() = 1
Traceback (most recent call last): Traceback (most recent call last):
... ...
SyntaxError: can't assign to function call SyntaxError: cannot assign to function call
>>> if 1: >>> if 1:
... x() = 1 ... x() = 1
...@@ -499,7 +544,7 @@ leading to spurious errors. ...@@ -499,7 +544,7 @@ leading to spurious errors.
... pass ... pass
Traceback (most recent call last): Traceback (most recent call last):
... ...
SyntaxError: can't assign to function call SyntaxError: cannot assign to function call
>>> if 1: >>> if 1:
... pass ... pass
...@@ -509,7 +554,7 @@ leading to spurious errors. ...@@ -509,7 +554,7 @@ leading to spurious errors.
... pass ... pass
Traceback (most recent call last): Traceback (most recent call last):
... ...
SyntaxError: can't assign to function call SyntaxError: cannot assign to function call
>>> if 1: >>> if 1:
... pass ... pass
...@@ -519,7 +564,7 @@ leading to spurious errors. ...@@ -519,7 +564,7 @@ leading to spurious errors.
... x() = 1 ... x() = 1
Traceback (most recent call last): 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: Make sure that the old "raise X, Y[, Z]" form is gone:
>>> raise X, Y >>> raise X, Y
...@@ -539,21 +584,33 @@ SyntaxError: keyword argument repeated ...@@ -539,21 +584,33 @@ SyntaxError: keyword argument repeated
>>> {1, 2, 3} = 42 >>> {1, 2, 3} = 42
Traceback (most recent call last): 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: Corner-cases that used to fail to raise the correct error:
>>> def f(*, x=lambda __debug__:0): pass >>> def f(*, x=lambda __debug__:0): pass
Traceback (most recent call last): Traceback (most recent call last):
SyntaxError: assignment to keyword SyntaxError: cannot assign to __debug__
>>> def f(*args:(lambda __debug__:0)): pass >>> def f(*args:(lambda __debug__:0)): pass
Traceback (most recent call last): Traceback (most recent call last):
SyntaxError: assignment to keyword SyntaxError: cannot assign to __debug__
>>> def f(**kwargs:(lambda __debug__:0)): pass >>> def f(**kwargs:(lambda __debug__:0)): pass
Traceback (most recent call last): Traceback (most recent call last):
SyntaxError: assignment to keyword SyntaxError: cannot assign to __debug__
>>> with (lambda *:0): pass >>> with (lambda *:0): pass
Traceback (most recent call last): Traceback (most recent call last):
...@@ -563,11 +620,11 @@ Corner-cases that used to crash: ...@@ -563,11 +620,11 @@ Corner-cases that used to crash:
>>> def f(**__debug__): pass >>> def f(**__debug__): pass
Traceback (most recent call last): Traceback (most recent call last):
SyntaxError: assignment to keyword SyntaxError: cannot assign to __debug__
>>> def f(*xx, __debug__): pass >>> def f(*xx, __debug__): pass
Traceback (most recent call last): Traceback (most recent call last):
SyntaxError: assignment to keyword SyntaxError: cannot assign to __debug__
""" """
......
...@@ -647,21 +647,25 @@ new_identifier(const char *n, struct compiling *c) ...@@ -647,21 +647,25 @@ new_identifier(const char *n, struct compiling *c)
#define NEW_IDENTIFIER(n) new_identifier(STR(n), c) #define NEW_IDENTIFIER(n) new_identifier(STR(n), c)
static int 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; 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)); loc = PyErr_ProgramTextObject(c->c_filename, LINENO(n));
if (!loc) { if (!loc) {
Py_INCREF(Py_None); Py_INCREF(Py_None);
loc = Py_None; loc = Py_None;
} }
tmp = Py_BuildValue("(OiiN)", c->c_filename, LINENO(n), n->n_col_offset + 1, loc); tmp = Py_BuildValue("(OiiN)", c->c_filename, LINENO(n), n->n_col_offset + 1, loc);
if (!tmp) if (!tmp) {
return 0; Py_DECREF(errstr);
errstr = PyUnicode_FromString(errmsg);
if (!errstr) {
Py_DECREF(tmp);
return 0; return 0;
} }
value = PyTuple_Pack(2, errstr, tmp); value = PyTuple_Pack(2, errstr, tmp);
...@@ -903,6 +907,7 @@ static const char * const FORBIDDEN[] = { ...@@ -903,6 +907,7 @@ static const char * const FORBIDDEN[] = {
"None", "None",
"True", "True",
"False", "False",
"__debug__",
NULL, NULL,
}; };
...@@ -911,17 +916,16 @@ forbidden_name(struct compiling *c, identifier name, const node *n, ...@@ -911,17 +916,16 @@ forbidden_name(struct compiling *c, identifier name, const node *n,
int full_checks) int full_checks)
{ {
assert(PyUnicode_Check(name)); assert(PyUnicode_Check(name));
if (_PyUnicode_EqualToASCIIString(name, "__debug__")) { const char * const *p = FORBIDDEN;
ast_error(c, n, "assignment to keyword"); if (!full_checks) {
return 1; /* In most cases, the parser will protect True, False, and None
} from being assign to. */
if (full_checks) { p += 3;
const char * const *p; }
for (p = FORBIDDEN; *p; p++) { for (; *p; p++) {
if (_PyUnicode_EqualToASCIIString(name, *p)) { if (_PyUnicode_EqualToASCIIString(name, *p)) {
ast_error(c, n, "assignment to keyword"); ast_error(c, n, "cannot assign to %U", name);
return 1; return 1;
}
} }
} }
return 0; return 0;
...@@ -1012,22 +1016,25 @@ set_context(struct compiling *c, expr_ty e, expr_context_ty ctx, const node *n) ...@@ -1012,22 +1016,25 @@ set_context(struct compiling *c, expr_ty e, expr_context_ty ctx, const node *n)
expr_name = "dict comprehension"; expr_name = "dict comprehension";
break; break;
case Dict_kind: case Dict_kind:
expr_name = "dict display";
break;
case Set_kind: case Set_kind:
expr_name = "set display";
break;
case JoinedStr_kind: case JoinedStr_kind:
case FormattedValue_kind: case FormattedValue_kind:
expr_name = "literal"; expr_name = "f-string expression";
break; break;
case Constant_kind: { case Constant_kind: {
PyObject *value = e->v.Constant.value; PyObject *value = e->v.Constant.value;
if (value == Py_None || value == Py_False || value == Py_True) { if (value == Py_None || value == Py_False || value == Py_True
expr_name = "keyword"; || value == Py_Ellipsis)
} {
else if (value == Py_Ellipsis) { return ast_error(c, n, "cannot %s %R",
expr_name = "Ellipsis"; ctx == Store ? "assign to" : "delete",
} value);
else {
expr_name = "literal";
} }
expr_name = "literal";
break; break;
} }
case Compare_kind: case Compare_kind:
...@@ -1044,12 +1051,9 @@ set_context(struct compiling *c, expr_ty e, expr_context_ty ctx, const node *n) ...@@ -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 */ /* Check for error string set by switch */
if (expr_name) { if (expr_name) {
char buf[300]; return ast_error(c, n, "cannot %s %s",
PyOS_snprintf(buf, sizeof(buf), ctx == Store ? "assign to" : "delete",
"can't %s %s", expr_name);
ctx == Store ? "assign to" : "delete",
expr_name);
return ast_error(c, n, buf);
} }
/* If the LHS is a list or tuple, we need to set the assignment /* 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) ...@@ -2083,21 +2087,17 @@ ast_for_atom(struct compiling *c, const node *n)
else if (PyErr_ExceptionMatches(PyExc_ValueError)) else if (PyErr_ExceptionMatches(PyExc_ValueError))
errtype = "value error"; errtype = "value error";
if (errtype) { if (errtype) {
char buf[128];
const char *s = NULL;
PyObject *type, *value, *tback, *errstr; PyObject *type, *value, *tback, *errstr;
PyErr_Fetch(&type, &value, &tback); PyErr_Fetch(&type, &value, &tback);
errstr = PyObject_Str(value); errstr = PyObject_Str(value);
if (errstr) if (errstr) {
s = PyUnicode_AsUTF8(errstr); ast_error(c, n, "(%s) %U", errtype, errstr);
if (s) { Py_DECREF(errstr);
PyOS_snprintf(buf, sizeof(buf), "(%s) %s", errtype, s); }
} else { else {
PyErr_Clear(); 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_DECREF(type);
Py_XDECREF(value); Py_XDECREF(value);
Py_XDECREF(tback); Py_XDECREF(tback);
...@@ -2815,18 +2815,10 @@ ast_for_call(struct compiling *c, const node *n, expr_ty func, bool allowgen) ...@@ -2815,18 +2815,10 @@ ast_for_call(struct compiling *c, const node *n, expr_ty func, bool allowgen)
break; break;
expr_node = CHILD(expr_node, 0); expr_node = CHILD(expr_node, 0);
} }
if (TYPE(expr_node) == lambdef) { if (TYPE(expr_node) != NAME) {
// 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.
ast_error(c, chch, ast_error(c, chch,
"lambda cannot contain assignment"); "expression cannot contain assignment, "
return NULL; "perhaps you meant \"==\"?");
}
else if (TYPE(expr_node) != NAME) {
ast_error(c, chch,
"keyword can't be an expression");
return NULL; return NULL;
} }
key = new_identifier(STR(expr_node), c); key = new_identifier(STR(expr_node), c);
...@@ -4127,16 +4119,10 @@ warn_invalid_escape_sequence(struct compiling *c, const node *n, ...@@ -4127,16 +4119,10 @@ warn_invalid_escape_sequence(struct compiling *c, const node *n,
NULL, NULL) < 0) NULL, NULL) < 0)
{ {
if (PyErr_ExceptionMatches(PyExc_SyntaxWarning)) { if (PyErr_ExceptionMatches(PyExc_SyntaxWarning)) {
const char *s;
/* Replace the SyntaxWarning exception with a SyntaxError /* Replace the SyntaxWarning exception with a SyntaxError
to get a more accurate error report */ to get a more accurate error report */
PyErr_Clear(); PyErr_Clear();
ast_error(c, n, "%U", msg);
s = PyUnicode_AsUTF8(msg);
if (s != NULL) {
ast_error(c, n, s);
}
} }
Py_DECREF(msg); Py_DECREF(msg);
return -1; return -1;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment