bpo-34983: Expose symtable.Symbol.is_nonlocal() in the symtable module (GH-9872)
authorPablo Galindo <Pablogsal@gmail.com>
Sat, 20 Oct 2018 00:46:00 +0000 (01:46 +0100)
committerGitHub <noreply@github.com>
Sat, 20 Oct 2018 00:46:00 +0000 (01:46 +0100)
The symbol table was not exposing functionality to query the nonlocal symbols
in a function or to check if a particular symbol is nonlocal.

Doc/library/symtable.rst
Lib/symtable.py
Lib/test/test_symtable.py
Misc/NEWS.d/next/Core and Builtins/2018-10-14-17-26-41.bpo-34983.l8XaZd.rst [new file with mode: 0644]
Modules/symtablemodule.c

index ba2caff589453782b35d86c0e89c783ee9b8849e..7c6ac4dccf8b76d4ddb8a9f01d5dbac9aa8b4515 100644 (file)
@@ -105,6 +105,10 @@ Examining Symbol Tables
 
       Return a tuple containing names of globals in this function.
 
+   .. method:: get_nonlocals()
+
+      Return a tuple containing names of nonlocals in this function.
+
    .. method:: get_frees()
 
       Return a tuple containing names of free variables in this function.
@@ -144,6 +148,10 @@ Examining Symbol Tables
 
       Return ``True`` if the symbol is global.
 
+   .. method:: is_nonlocal()
+
+      Return ``True`` if the symbol is nonlocal.
+
    .. method:: is_declared_global()
 
       Return ``True`` if the symbol is declared global with a global statement.
index c7627a6ef68856a68a87a3c19942461e8dcc9cff..5bea7cf6155486907c532af2aab26ae1dc339bfa 100644 (file)
@@ -1,7 +1,7 @@
 """Interface to the compiler's internal symbol tables"""
 
 import _symtable
-from _symtable import (USE, DEF_GLOBAL, DEF_LOCAL, DEF_PARAM,
+from _symtable import (USE, DEF_GLOBAL, DEF_NONLOCAL, DEF_LOCAL, DEF_PARAM,
      DEF_IMPORT, DEF_BOUND, DEF_ANNOT, SCOPE_OFF, SCOPE_MASK, FREE,
      LOCAL, GLOBAL_IMPLICIT, GLOBAL_EXPLICIT, CELL)
 
@@ -117,6 +117,7 @@ class Function(SymbolTable):
     __locals = None
     __frees = None
     __globals = None
+    __nonlocals = None
 
     def __idents_matching(self, test_func):
         return tuple(ident for ident in self.get_identifiers()
@@ -141,6 +142,11 @@ class Function(SymbolTable):
             self.__globals = self.__idents_matching(test)
         return self.__globals
 
+    def get_nonlocals(self):
+        if self.__nonlocals is None:
+            self.__nonlocals = self.__idents_matching(lambda x:x & DEF_NONLOCAL)
+        return self.__nonlocals
+
     def get_frees(self):
         if self.__frees is None:
             is_free = lambda x:((x >> SCOPE_OFF) & SCOPE_MASK) == FREE
@@ -184,6 +190,9 @@ class Symbol(object):
     def is_global(self):
         return bool(self.__scope in (GLOBAL_IMPLICIT, GLOBAL_EXPLICIT))
 
+    def is_nonlocal(self):
+        return bool(self.__flags & DEF_NONLOCAL)
+
     def is_declared_global(self):
         return bool(self.__scope == GLOBAL_EXPLICIT)
 
index 8d76f6fe45f9b45b21020c281fdd057e22915951..0a1cb8d5b43283e72ab12b921e00c6d16a9de994 100644 (file)
@@ -10,6 +10,7 @@ TEST_CODE = """
 import sys
 
 glob = 42
+some_var = 12
 
 class Mine:
     instance_var = 24
@@ -19,10 +20,15 @@ class Mine:
 def spam(a, b, *var, **kw):
     global bar
     bar = 47
+    some_var = 10
     x = 23
     glob
     def internal():
         return x
+    def other_internal():
+        nonlocal some_var
+        some_var = 3
+        return some_var
     return internal
 
 def foo():
@@ -47,6 +53,7 @@ class SymtableTest(unittest.TestCase):
     a_method = find_block(Mine, "a_method")
     spam = find_block(top, "spam")
     internal = find_block(spam, "internal")
+    other_internal = find_block(spam, "other_internal")
     foo = find_block(top, "foo")
 
     def test_type(self):
@@ -75,12 +82,12 @@ class SymtableTest(unittest.TestCase):
 
     def test_lineno(self):
         self.assertEqual(self.top.get_lineno(), 0)
-        self.assertEqual(self.spam.get_lineno(), 11)
+        self.assertEqual(self.spam.get_lineno(), 12)
 
     def test_function_info(self):
         func = self.spam
         self.assertEqual(sorted(func.get_parameters()), ["a", "b", "kw", "var"])
-        expected = ["a", "b", "internal", "kw", "var", "x"]
+        expected = ['a', 'b', 'internal', 'kw', 'other_internal', 'some_var', 'var', 'x']
         self.assertEqual(sorted(func.get_locals()), expected)
         self.assertEqual(sorted(func.get_globals()), ["bar", "glob"])
         self.assertEqual(self.internal.get_frees(), ("x",))
@@ -93,6 +100,12 @@ class SymtableTest(unittest.TestCase):
         self.assertFalse(self.internal.lookup("x").is_global())
         self.assertFalse(self.Mine.lookup("instance_var").is_global())
 
+    def test_nonlocal(self):
+        self.assertFalse(self.spam.lookup("some_var").is_nonlocal())
+        self.assertTrue(self.other_internal.lookup("some_var").is_nonlocal())
+        expected = ("some_var",)
+        self.assertEqual(self.other_internal.get_nonlocals(), expected)
+
     def test_local(self):
         self.assertTrue(self.spam.lookup("x").is_local())
         self.assertFalse(self.internal.lookup("x").is_local())
diff --git a/Misc/NEWS.d/next/Core and Builtins/2018-10-14-17-26-41.bpo-34983.l8XaZd.rst b/Misc/NEWS.d/next/Core and Builtins/2018-10-14-17-26-41.bpo-34983.l8XaZd.rst
new file mode 100644 (file)
index 0000000..dd76b63
--- /dev/null
@@ -0,0 +1,2 @@
+Expose :meth:`symtable.Symbol.is_nonlocal` in the symtable module. Patch by
+Pablo Galindo.
index 810f88da488e4210ea5269347db3e7e898a7c754..e8d2f5b582be20b7ece56baac4a4cb77ad2eab44 100644 (file)
@@ -84,6 +84,7 @@ PyInit__symtable(void)
         return NULL;
     PyModule_AddIntMacro(m, USE);
     PyModule_AddIntMacro(m, DEF_GLOBAL);
+    PyModule_AddIntMacro(m, DEF_NONLOCAL);
     PyModule_AddIntMacro(m, DEF_LOCAL);
     PyModule_AddIntMacro(m, DEF_PARAM);
     PyModule_AddIntMacro(m, DEF_FREE);