]> granicus.if.org Git - python/commitdiff
bpo-28936: Detect lexically first syntax error first (#4097)
authorIvan Levkivskyi <levkivskyi@gmail.com>
Thu, 26 Oct 2017 21:28:35 +0000 (23:28 +0200)
committerSerhiy Storchaka <storchaka@gmail.com>
Thu, 26 Oct 2017 21:28:35 +0000 (00:28 +0300)
Lexically first global and nonlocal syntax errors at given scope should be detected first.

Lib/test/test_syntax.py
Misc/NEWS.d/next/Core and Builtins/2017-10-23-23-39-26.bpo-28936.C288Jh.rst [new file with mode: 0644]
Python/symtable.c

index 7ce3d75a89223b263f4e386bfc2e0dad40083f66..5d36581859b775066273e37192308853d0a3e677 100644 (file)
@@ -399,6 +399,13 @@ build.  The number of blocks must be greater than CO_MAXBLOCKS.  SF #1565514
 
 Misuse of the nonlocal and global statement can lead to a few unique syntax errors.
 
+   >>> def f():
+   ...     print(x)
+   ...     global x
+   Traceback (most recent call last):
+     ...
+   SyntaxError: name 'x' is used prior to global declaration
+
    >>> def f():
    ...     x = 1
    ...     global x
@@ -406,6 +413,12 @@ Misuse of the nonlocal and global statement can lead to a few unique syntax erro
      ...
    SyntaxError: name 'x' is assigned to before global declaration
 
+   >>> def f(x):
+   ...     global x
+   Traceback (most recent call last):
+     ...
+   SyntaxError: name 'x' is parameter and global
+
    >>> def f():
    ...     x = 1
    ...     def g():
@@ -560,7 +573,6 @@ Corner-cases that used to crash:
 
 import re
 import unittest
-import warnings
 
 from test import support
 
@@ -596,19 +608,25 @@ class SyntaxTestCase(unittest.TestCase):
     def test_assign_del(self):
         self._check_error("del f()", "delete")
 
-    def test_global_err_then_warn(self):
-        # Bug #763201:  The SyntaxError raised for one global statement
-        # shouldn't be clobbered by a SyntaxWarning issued for a later one.
+    def test_global_param_err_first(self):
         source = """if 1:
             def error(a):
                 global a  # SyntaxError
-            def warning():
+            def error2():
+                b = 1
+                global b  # SyntaxError
+            """
+        self._check_error(source, "parameter and global", lineno=3)
+
+    def test_nonlocal_param_err_first(self):
+        source = """if 1:
+            def error(a):
+                nonlocal a  # SyntaxError
+            def error2():
                 b = 1
-                global b  # SyntaxWarning
+                global b  # SyntaxError
             """
-        warnings.filterwarnings(action='ignore', category=SyntaxWarning)
-        self._check_error(source, "global")
-        warnings.filters.pop(0)
+        self._check_error(source, "parameter and nonlocal", lineno=3)
 
     def test_break_outside_loop(self):
         self._check_error("break", "outside loop")
diff --git a/Misc/NEWS.d/next/Core and Builtins/2017-10-23-23-39-26.bpo-28936.C288Jh.rst b/Misc/NEWS.d/next/Core and Builtins/2017-10-23-23-39-26.bpo-28936.C288Jh.rst
new file mode 100644 (file)
index 0000000..8ebdf32
--- /dev/null
@@ -0,0 +1,2 @@
+Ensure that lexically first syntax error involving a parameter and ``global``
+or ``nonlocal`` is detected first at a given scope. Patch by Ivan Levkivskyi.
index 2658b917d7a815ea5237561f5f411b476190929f..55815c91cc9cabdcbc695e488756bf87e3c92dd2 100644 (file)
@@ -9,6 +9,12 @@
 #include "structmember.h"
 
 /* error strings used for warnings */
+#define GLOBAL_PARAM \
+"name '%U' is parameter and global"
+
+#define NONLOCAL_PARAM \
+"name '%U' is parameter and nonlocal"
+
 #define GLOBAL_AFTER_ASSIGN \
 "name '%U' is assigned to before global declaration"
 
@@ -465,12 +471,6 @@ analyze_name(PySTEntryObject *ste, PyObject *scopes, PyObject *name, long flags,
              PyObject *global)
 {
     if (flags & DEF_GLOBAL) {
-        if (flags & DEF_PARAM) {
-            PyErr_Format(PyExc_SyntaxError,
-                        "name '%U' is parameter and global",
-                        name);
-            return error_at_directive(ste, name);
-        }
         if (flags & DEF_NONLOCAL) {
             PyErr_Format(PyExc_SyntaxError,
                          "name '%U' is nonlocal and global",
@@ -485,12 +485,6 @@ analyze_name(PySTEntryObject *ste, PyObject *scopes, PyObject *name, long flags,
         return 1;
     }
     if (flags & DEF_NONLOCAL) {
-        if (flags & DEF_PARAM) {
-            PyErr_Format(PyExc_SyntaxError,
-                         "name '%U' is parameter and nonlocal",
-                         name);
-            return error_at_directive(ste, name);
-        }
         if (!bound) {
             PyErr_Format(PyExc_SyntaxError,
                          "nonlocal declaration not allowed at module level");
@@ -1284,9 +1278,11 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s)
             long cur = symtable_lookup(st, name);
             if (cur < 0)
                 VISIT_QUIT(st, 0);
-            if (cur & (DEF_LOCAL | USE | DEF_ANNOT)) {
-                char* msg;
-                if (cur & USE) {
+            if (cur & (DEF_PARAM | DEF_LOCAL | USE | DEF_ANNOT)) {
+                const char* msg;
+                if (cur & DEF_PARAM) {
+                    msg = GLOBAL_PARAM;
+                } else if (cur & USE) {
                     msg = GLOBAL_AFTER_USE;
                 } else if (cur & DEF_ANNOT) {
                     msg = GLOBAL_ANNOT;
@@ -1315,9 +1311,11 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s)
             long cur = symtable_lookup(st, name);
             if (cur < 0)
                 VISIT_QUIT(st, 0);
-            if (cur & (DEF_LOCAL | USE | DEF_ANNOT)) {
-                char* msg;
-                if (cur & USE) {
+            if (cur & (DEF_PARAM | DEF_LOCAL | USE | DEF_ANNOT)) {
+                const char* msg;
+                if (cur & DEF_PARAM) {
+                    msg = NONLOCAL_PARAM;
+                } else if (cur & USE) {
                     msg = NONLOCAL_AFTER_USE;
                 } else if (cur & DEF_ANNOT) {
                     msg = NONLOCAL_ANNOT;