[pypy-commit] pypy py3.5-fix-globals: (stevie, robert-zaremba) Fix symtable for nonlocal

robert-zaremba pypy.commits at gmail.com
Tue Feb 28 04:59:12 EST 2017


Author: Robert Zaremba <robert.zaremba at scale-it.pl>
Branch: py3.5-fix-globals
Changeset: r90411:802f8b2e090c
Date: 2017-02-27 17:56 +0100
http://bitbucket.org/pypy/pypy/changeset/802f8b2e090c/

Log:	(stevie, robert-zaremba) Fix symtable for nonlocal

	Added tests and updated functionality for symtable construction for
	nonlocal declarations.

diff --git a/pypy/interpreter/astcompiler/symtable.py b/pypy/interpreter/astcompiler/symtable.py
--- a/pypy/interpreter/astcompiler/symtable.py
+++ b/pypy/interpreter/astcompiler/symtable.py
@@ -8,7 +8,7 @@
 # These are for internal use only:
 SYM_BLANK = 0
 SYM_GLOBAL = 1
-SYM_ASSIGNED = 2  # Or deleted actually.
+SYM_ASSIGNED = 2  # (DEF_LOCAL in CPython3). Or deleted actually.
 SYM_PARAM = 2 << 1
 SYM_NONLOCAL = 2 << 2
 SYM_USED = 2 << 3
@@ -123,9 +123,6 @@
     def _finalize_name(self, name, flags, local, bound, free, globs):
         """Decide on the scope of a name."""
         if flags & SYM_GLOBAL:
-            if flags & SYM_NONLOCAL:
-                err = "name '%s' is nonlocal and global" % (name,)
-                raise SyntaxError(err, self.lineno, self.col_offset)
             self.symbols[name] = SCOPE_GLOBAL_EXPLICIT
             globs[name] = None
             if bound:
@@ -134,12 +131,6 @@
                 except KeyError:
                     pass
         elif flags & SYM_NONLOCAL:
-            if flags & SYM_PARAM:
-                err = "name '%s' is parameter and nonlocal" % (name,)
-                raise SyntaxError(err, self.lineno, self.col_offset)
-            if bound is None:
-                err = "nonlocal declaration not allowed at module level"
-                raise SyntaxError(err, self.lineno, self.col_offset)
             if name not in bound:
                 err = "no binding for nonlocal '%s' found" % (name,)
                 raise SyntaxError(err, self.lineno, self.col_offset)
@@ -497,6 +488,9 @@
             if old_role & SYM_PARAM:
                 msg = "name '%s' is parameter and global" % (name,)
                 raise SyntaxError(msg, glob.lineno, glob.col_offset)
+            if old_role & SYM_NONLOCAL:
+                msg = "name '%s' is nonlocal and global" % (name,)
+                raise SyntaxError(msg, glob.lineno, glob.col_offset)
 
             if old_role & (SYM_USED | SYM_ASSIGNED):
                 if old_role & SYM_ASSIGNED:
@@ -513,6 +507,16 @@
     def visit_Nonlocal(self, nonl):
         for name in nonl.names:
             old_role = self.scope.lookup_role(name)
+            msg = ""
+            if old_role & SYM_GLOBAL:
+                msg = "name '%s' is nonlocal and global" % (name,)
+            if old_role & SYM_PARAM:
+                msg = "name '%s' is parameter and nonlocal" % (name,)
+            if type(self.scope) == ModuleScope:
+                msg = "nonlocal declaration not allowed at module level"
+            if msg is not "":
+                raise SyntaxError(msg, nonl.lineno, nonl.col_offset)
+
             if (old_role & (SYM_USED | SYM_ASSIGNED) and not
                     (name == '__class__' and
                      self.scope._hide_bound_from_nested_scopes)):
@@ -525,6 +529,7 @@
                 misc.syntax_warning(self.space, msg,
                                     self.compile_info.filename,
                                     nonl.lineno, nonl.col_offset)
+
             self.note_symbol(name, SYM_NONLOCAL)
 
     def visit_Lambda(self, lamb):
diff --git a/pypy/interpreter/astcompiler/test/test_symtable.py b/pypy/interpreter/astcompiler/test/test_symtable.py
--- a/pypy/interpreter/astcompiler/test/test_symtable.py
+++ b/pypy/interpreter/astcompiler/test/test_symtable.py
@@ -295,17 +295,17 @@
         assert xscp.lookup("y") == symtable.SCOPE_GLOBAL_EXPLICIT
         assert zscp.lookup("y") == symtable.SCOPE_FREE
 
-        code = "def f(x):\n   global x"
-        exc = py.test.raises(SyntaxError, self.func_scope, code).value
+        src = "def f(x):\n   global x"
+        exc = py.test.raises(SyntaxError, self.func_scope, src).value
         assert exc.lineno == 2
         assert exc.msg == "name 'x' is parameter and global"
 
     def test_global_nested(self):
-        code = """
+        src = """
 def f(x):
     def g(x):
         global x"""
-        exc = py.test.raises(SyntaxError, self.func_scope, code).value
+        exc = py.test.raises(SyntaxError, self.func_scope, src).value
         assert exc.lineno == 4
         assert exc.msg == "name 'x' is parameter and global"
 
@@ -319,14 +319,13 @@
         assert x == symtable.SYM_GLOBAL
 
     def test_nonlocal(self):
-        src = str(py.code.Source("""
-                     def f():
-                         nonlocal x
-                         global x
-                 """))
+        src = """
+x = 1
+def f():
+    nonlocal x"""
         exc = py.test.raises(SyntaxError, self.func_scope, src).value
-        assert exc.msg == "name 'x' is nonlocal and global"
-        #
+        assert exc.msg == "no binding for nonlocal 'x' found"
+
         src = str(py.code.Source("""
                      def f(x):
                          nonlocal x
@@ -344,6 +343,58 @@
         src = "nonlocal x"
         exc = py.test.raises(SyntaxError, self.func_scope, src).value
         assert exc.msg == "nonlocal declaration not allowed at module level"
+        assert exc.lineno == 1
+
+        src = "x = 2\nnonlocal x"
+        exc = py.test.raises(SyntaxError, self.func_scope, src).value
+        assert exc.msg == "nonlocal declaration not allowed at module level"
+        assert exc.lineno == 2
+
+    def test_nonlocal_and_global(self):
+        """This test differs from CPython3 behaviour. CPython points to the
+        first occurance of the global/local expression. PyPy will point to the
+        last expression which makes the problem."""
+        src = """
+def f():
+    nonlocal x
+    global x"""
+        exc = py.test.raises(SyntaxError, self.func_scope, src).value
+        assert exc.msg == "name 'x' is nonlocal and global"
+        assert exc.lineno == 4
+
+        src = """
+def f():
+    global x
+    nonlocal x """
+        exc = py.test.raises(SyntaxError, self.func_scope, src).value
+        assert exc.msg == "name 'x' is nonlocal and global"
+        assert exc.lineno == 4
+
+    def test_nonlocal_nested(self):
+        scp = self.func_scope("""
+def f(x):
+    def g():
+        nonlocal x""")
+        g = scp.children[0]
+        x = g.lookup_role('x')
+        assert x == symtable.SYM_NONLOCAL
+
+        scp = self.func_scope("""
+def f():
+    def g():
+        nonlocal x
+    x = 1""")
+        g = scp.children[0]
+        x = g.lookup_role('x')
+        assert x == symtable.SYM_NONLOCAL
+
+        src = """
+def f(x):
+    def g(x):
+        nonlocal x"""
+        exc = py.test.raises(SyntaxError, self.func_scope, src).value
+        assert exc.msg == "name 'x' is parameter and nonlocal"
+        assert exc.lineno == 4
 
     def test_optimization(self):
         assert not self.mod_scope("").can_be_optimized


More information about the pypy-commit mailing list