[Python-checkins] bpo-35169: Improve error messages for forbidden assignments. (GH-10342)

Serhiy Storchaka webhook-mailer at python.org
Tue Nov 20 12:27:20 EST 2018


https://github.com/python/cpython/commit/97f1efb6062188645a470daaa91e3669d739c75f
commit: 97f1efb6062188645a470daaa91e3669d739c75f
branch: master
author: Serhiy Storchaka <storchaka at gmail.com>
committer: GitHub <noreply at 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;



More information about the Python-checkins mailing list