From 8c83c23fa32405aa9212f028d234f4129d105a23 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 26 Oct 2017 23:28:35 +0200 Subject: [PATCH] bpo-28936: Detect lexically first syntax error first (#4097) Lexically first global and nonlocal syntax errors at given scope should be detected first. --- Lib/test/test_syntax.py | 36 ++++++++++++++----- .../2017-10-23-23-39-26.bpo-28936.C288Jh.rst | 2 ++ Python/symtable.c | 34 +++++++++--------- 3 files changed, 45 insertions(+), 27 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2017-10-23-23-39-26.bpo-28936.C288Jh.rst diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py index 7ce3d75a892..5d36581859b 100644 --- a/Lib/test/test_syntax.py +++ b/Lib/test/test_syntax.py @@ -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 # 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 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 # SyntaxError + """ + 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 index 00000000000..8ebdf32e5be --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2017-10-23-23-39-26.bpo-28936.C288Jh.rst @@ -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. diff --git a/Python/symtable.c b/Python/symtable.c index 2658b917d7a..55815c91cc9 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -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;