Skip to content

bpo-28936: Detect lexically first syntax error first #4097

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Oct 26, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 27 additions & 9 deletions Lib/test/test_syntax.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,13 +399,26 @@

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
Traceback (most recent call last):
...
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():
Expand Down Expand Up @@ -560,7 +573,6 @@

import re
import unittest
import warnings

from test import support

Expand Down Expand Up @@ -596,19 +608,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_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")
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
34 changes: 16 additions & 18 deletions Python/symtable.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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",
Expand All @@ -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");
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down