From 06a391457c43f7badad2a2de04edc825ad9024b4 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 23 Oct 2017 23:26:42 +0200 Subject: [PATCH 1/4] Lexically first global and nonlocal syntax errors at given scope should be detected first --- Lib/test/test_syntax.py | 54 ++++++++++++++++++++++------------------- Python/symtable.c | 24 ++++++++++++------ 2 files changed, 45 insertions(+), 33 deletions(-) diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py index 5d398e6688ba9b..f7b1dc3908f66c 100644 --- a/Lib/test/test_syntax.py +++ b/Lib/test/test_syntax.py @@ -440,22 +440,21 @@ ... SyntaxError: nonlocal declaration not allowed at module level -TODO(jhylton): Figure out how to test SyntaxWarning with doctest. - -## >>> def f(x): -## ... def f(): -## ... print(x) -## ... nonlocal x -## Traceback (most recent call last): -## ... -## SyntaxWarning: name 'x' is assigned to before nonlocal declaration - -## >>> def f(): -## ... x = 1 -## ... nonlocal x -## Traceback (most recent call last): -## ... -## SyntaxWarning: name 'x' is assigned to before nonlocal declaration +Non-local declaration after use is now a syntax error + >>> def f(x): + ... def f(): + ... print(x) + ... nonlocal x + Traceback (most recent call last): + ... + SyntaxError: name 'x' is used prior to nonlocal declaration + + >>> def f(): + ... x = 1 + ... nonlocal x + Traceback (most recent call last): + ... + SyntaxError: name 'x' is assigned to before nonlocal declaration From https://bugs.python.org/issue25973 >>> class A: @@ -568,7 +567,6 @@ import re import unittest -import warnings from test import support @@ -604,19 +602,25 @@ def test_assign_call(self): 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_err_first(self): source = """if 1: def error(a): global a # SyntaxError - def warning(): + def error2(): + b = 1 + global b + """ + self._check_error(source, "parameter and global", lineno=3) + + def test_nonlocal_err_first(self): + source = """if 1: + def error(a): + nonlocal a # SyntaxError + def error2(): b = 1 - global b # SyntaxWarning + global b """ - 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/Python/symtable.c b/Python/symtable.c index 2658b917d7a815..d280fcfa7af261 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" @@ -467,8 +473,7 @@ analyze_name(PySTEntryObject *ste, PyObject *scopes, PyObject *name, long flags, if (flags & DEF_GLOBAL) { if (flags & DEF_PARAM) { PyErr_Format(PyExc_SyntaxError, - "name '%U' is parameter and global", - name); + GLOBAL_PARAM, name); return error_at_directive(ste, name); } if (flags & DEF_NONLOCAL) { @@ -487,8 +492,7 @@ analyze_name(PySTEntryObject *ste, PyObject *scopes, PyObject *name, long flags, if (flags & DEF_NONLOCAL) { if (flags & DEF_PARAM) { PyErr_Format(PyExc_SyntaxError, - "name '%U' is parameter and nonlocal", - name); + NONLOCAL_PARAM, name); return error_at_directive(ste, name); } if (!bound) { @@ -1284,9 +1288,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)) { + if (cur & (DEF_PARAM | DEF_LOCAL | USE | DEF_ANNOT)) { char* msg; - if (cur & USE) { + 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 +1321,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)) { + if (cur & (DEF_PARAM | DEF_LOCAL | USE | DEF_ANNOT)) { char* msg; - if (cur & USE) { + if (cur & DEF_PARAM) { + msg = NONLOCAL_PARAM; + } else if (cur & USE) { msg = NONLOCAL_AFTER_USE; } else if (cur & DEF_ANNOT) { msg = NONLOCAL_ANNOT; From 90e8678e7f56e1978b2e87f74902aacc81e41950 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 23 Oct 2017 23:39:40 +0200 Subject: [PATCH 2/4] Add NEWS entry --- .../Core and Builtins/2017-10-23-23-39-26.bpo-28936.C288Jh.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2017-10-23-23-39-26.bpo-28936.C288Jh.rst 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 00000000000000..334d621eb5dc87 --- /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 ``global`` or +``nonlocal`` is detected first at a given scope. From e431ad134eb0ae984b54761f1e407f286a89be71 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Wed, 25 Oct 2017 12:11:41 +0200 Subject: [PATCH 3/4] Address CR --- Lib/test/test_syntax.py | 13 +++++++++++++ .../2017-10-23-23-39-26.bpo-28936.C288Jh.rst | 2 +- Python/symtable.c | 14 ++------------ 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py index 58022bc1427e41..d2a14db9c4ae51 100644 --- a/Lib/test/test_syntax.py +++ b/Lib/test/test_syntax.py @@ -399,6 +399,13 @@ 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 @@ ... 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(): 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 index 334d621eb5dc87..f722b3fd9adadb 100644 --- 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 @@ -1,2 +1,2 @@ Ensure that lexically first syntax error involving ``global`` or -``nonlocal`` is detected first at a given scope. +``nonlocal`` is detected first at a given scope. Patch by Ivan Levkivskyi. diff --git a/Python/symtable.c b/Python/symtable.c index d280fcfa7af261..55815c91cc9cab 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -471,11 +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, - GLOBAL_PARAM, name); - return error_at_directive(ste, name); - } if (flags & DEF_NONLOCAL) { PyErr_Format(PyExc_SyntaxError, "name '%U' is nonlocal and global", @@ -490,11 +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, - NONLOCAL_PARAM, name); - return error_at_directive(ste, name); - } if (!bound) { PyErr_Format(PyExc_SyntaxError, "nonlocal declaration not allowed at module level"); @@ -1289,7 +1279,7 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s) if (cur < 0) VISIT_QUIT(st, 0); if (cur & (DEF_PARAM | DEF_LOCAL | USE | DEF_ANNOT)) { - char* msg; + const char* msg; if (cur & DEF_PARAM) { msg = GLOBAL_PARAM; } else if (cur & USE) { @@ -1322,7 +1312,7 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s) if (cur < 0) VISIT_QUIT(st, 0); if (cur & (DEF_PARAM | DEF_LOCAL | USE | DEF_ANNOT)) { - char* msg; + const char* msg; if (cur & DEF_PARAM) { msg = NONLOCAL_PARAM; } else if (cur & USE) { From 4f87c991eb914838f4ac4ff0aab8359af26be228 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 26 Oct 2017 22:48:42 +0200 Subject: [PATCH 4/4] Address CR --- Lib/test/test_syntax.py | 8 ++++---- .../2017-10-23-23-39-26.bpo-28936.C288Jh.rst | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py index d2a14db9c4ae51..5d36581859b775 100644 --- a/Lib/test/test_syntax.py +++ b/Lib/test/test_syntax.py @@ -608,23 +608,23 @@ def test_assign_call(self): def test_assign_del(self): self._check_error("del f()", "delete") - def test_global_err_first(self): + def test_global_param_err_first(self): source = """if 1: def error(a): global a # SyntaxError def error2(): b = 1 - global b + global b # SyntaxError """ self._check_error(source, "parameter and global", lineno=3) - def test_nonlocal_err_first(self): + def test_nonlocal_param_err_first(self): source = """if 1: def error(a): nonlocal a # SyntaxError def error2(): b = 1 - global b + global b # SyntaxError """ self._check_error(source, "parameter and nonlocal", lineno=3) 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 index f722b3fd9adadb..8ebdf32e5bea63 100644 --- 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 @@ -1,2 +1,2 @@ -Ensure that lexically first syntax error involving ``global`` or -``nonlocal`` is detected first at a given scope. Patch by Ivan Levkivskyi. +Ensure that lexically first syntax error involving a parameter and ``global`` +or ``nonlocal`` is detected first at a given scope. Patch by Ivan Levkivskyi.