diff options
-rw-r--r-- | Lib/symtable.py | 3 | ||||
-rw-r--r-- | Lib/test/test_scope.py | 26 | ||||
-rw-r--r-- | Lib/test/test_symtable.py | 2 | ||||
-rw-r--r-- | Python/symtable.c | 114 |
4 files changed, 123 insertions, 22 deletions
diff --git a/Lib/symtable.py b/Lib/symtable.py index 0d73870..7548a7f 100644 --- a/Lib/symtable.py +++ b/Lib/symtable.py @@ -191,6 +191,9 @@ class Symbol(object): def is_global(self): return bool(self.__scope in (GLOBAL_IMPLICIT, GLOBAL_EXPLICIT)) + def is_declared_global(self): + return bool(self.__scope == GLOBAL_EXPLICIT) + def is_local(self): return bool(self.__flags & DEF_BOUND) diff --git a/Lib/test/test_scope.py b/Lib/test/test_scope.py index 65d87cf..fb1e26f 100644 --- a/Lib/test/test_scope.py +++ b/Lib/test/test_scope.py @@ -618,7 +618,6 @@ self.assert_(X.passed) self.assertEqual(dec(), 0) def testNonLocalMethod(self): - def f(x): class c: def inc(self): @@ -630,13 +629,36 @@ self.assert_(X.passed) x -= 1 return x return c() - c = f(0) self.assertEqual(c.inc(), 1) self.assertEqual(c.inc(), 2) self.assertEqual(c.dec(), 1) self.assertEqual(c.dec(), 0) + def testGlobalInParallelNestedFunctions(self): + # A symbol table bug leaked the global statement from one + # function to other nested functions in the same block. + # This test verifies that a global statement in the first + # function does not affect the second function. + CODE = """def f(): + y = 1 + def g(): + global y + return y + def h(): + return y + 1 + return g, h +y = 9 +g, h = f() +result9 = g() +result2 = h() +""" + local_ns = {} + global_ns = {} + exec(CODE, local_ns, global_ns) + self.assertEqual(2, global_ns["result2"]) + self.assertEqual(9, global_ns["result9"]) + def testNonLocalClass(self): def f(x): diff --git a/Lib/test/test_symtable.py b/Lib/test/test_symtable.py index 7e0206d..45b7be8 100644 --- a/Lib/test/test_symtable.py +++ b/Lib/test/test_symtable.py @@ -88,7 +88,9 @@ class SymtableTest(unittest.TestCase): def test_globals(self): self.assertTrue(self.spam.lookup("glob").is_global()) + self.assertFalse(self.spam.lookup("glob").is_declared_global()) self.assertTrue(self.spam.lookup("bar").is_global()) + self.assertTrue(self.spam.lookup("bar").is_declared_global()) self.assertFalse(self.internal.lookup("x").is_global()) self.assertFalse(self.Mine.lookup("instance_var").is_global()) diff --git a/Python/symtable.c b/Python/symtable.c index 732b85c..59af958 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -377,8 +377,9 @@ PyST_GetScope(PySTEntryObject *ste, PyObject *name) /* Decide on scope of name, given flags. - The dicts passed in as arguments are modified as necessary. - ste is passed so that flags can be updated. + The namespace dictionaries may be modified to record information + about the new name. For example, a new global will add an entry to + global. A name that was global can be changed to local. */ static int @@ -454,7 +455,7 @@ analyze_name(PySTEntryObject *ste, PyObject *scopes, PyObject *name, long flags, explicit? It could also be global implicit. */ if (global && PySet_Contains(global, name)) { - SET_SCOPE(scopes, name, GLOBAL_EXPLICIT); + SET_SCOPE(scopes, name, GLOBAL_IMPLICIT); return 1; } if (ste->ste_nested) @@ -628,28 +629,56 @@ error: } /* Make final symbol table decisions for block of ste. + Arguments: ste -- current symtable entry (input/output) - bound -- set of variables bound in enclosing scopes (input) + bound -- set of variables bound in enclosing scopes (input). bound + is NULL for module blocks. free -- set of free variables in enclosed scopes (output) globals -- set of declared global variables in enclosing scopes (input) + + The implementation uses two mutually recursive functions, + analyze_block() and analyze_child_block(). analyze_block() is + responsible for analyzing the individual names defined in a block. + analyze_child_block() prepares temporary namespace dictionaries + used to evaluated nested blocks. + + The two functions exist because a child block should see the name + bindings of its enclosing blocks, but those bindings should not + propagate back to a parent block. */ static int +analyze_child_block(PySTEntryObject *entry, PyObject *bound, PyObject *free, + PyObject *global, PyObject* child_free); + +static int analyze_block(PySTEntryObject *ste, PyObject *bound, PyObject *free, PyObject *global) { PyObject *name, *v, *local = NULL, *scopes = NULL, *newbound = NULL; - PyObject *newglobal = NULL, *newfree = NULL; + PyObject *newglobal = NULL, *newfree = NULL, *allfree = NULL; int i, success = 0; Py_ssize_t pos = 0; - scopes = PyDict_New(); - if (!scopes) - goto error; - local = PySet_New(NULL); + local = PySet_New(NULL); /* collect new names bound in block */ if (!local) goto error; + scopes = PyDict_New(); /* collect scopes defined for each name */ + if (!scopes) + goto error; + + /* Allocate new global and bound variable dictionaries. These + dictionaries hold the names visible in nested blocks. For + ClassBlocks, the bound and global names are initialized + before analyzing names, because class bindings aren't + visible in methods. For other blocks, they are initialized + after names are analyzed. + */ + + /* TODO(jhylton): Package these dicts in a struct so that we + can write reasonable helper functions? + */ newglobal = PySet_New(NULL); if (!newglobal) goto error; @@ -666,25 +695,22 @@ analyze_block(PySTEntryObject *ste, PyObject *bound, PyObject *free, this one. */ if (ste->ste_type == ClassBlock) { + /* Pass down known globals */ + if (!PyNumber_InPlaceOr(newglobal, global)) + goto error; + Py_DECREF(newglobal); /* Pass down previously bound symbols */ if (bound) { if (!PyNumber_InPlaceOr(newbound, bound)) goto error; Py_DECREF(newbound); } - /* Pass down known globals */ - if (!PyNumber_InPlaceOr(newglobal, global)) - goto error; - Py_DECREF(newglobal); } - /* Analyze symbols in current scope */ - assert(PySTEntry_Check(ste)); - assert(PyDict_Check(ste->ste_symbols)); while (PyDict_Next(ste->ste_symbols, &pos, &name, &v)) { long flags = PyLong_AS_LONG(v); - if (!analyze_name(ste, scopes, name, flags, bound, local, free, - global)) + if (!analyze_name(ste, scopes, name, flags, + bound, local, free, global)) goto error; } @@ -716,19 +742,31 @@ analyze_block(PySTEntryObject *ste, PyObject *bound, PyObject *free, goto error; } - /* Recursively call analyze_block() on each child block */ + /* Recursively call analyze_block() on each child block. + + newbound, newglobal now contain the names visible in + nested blocks. The free variables in the children will + be collected in allfree. + */ + allfree = PySet_New(NULL); + if (!allfree) + goto error; for (i = 0; i < PyList_GET_SIZE(ste->ste_children); ++i) { PyObject *c = PyList_GET_ITEM(ste->ste_children, i); PySTEntryObject* entry; assert(c && PySTEntry_Check(c)); entry = (PySTEntryObject*)c; - if (!analyze_block(entry, newbound, newfree, newglobal)) + if (!analyze_child_block(entry, newbound, newfree, newglobal, + allfree)) goto error; /* Check if any children have free variables */ if (entry->ste_free || entry->ste_child_free) ste->ste_child_free = 1; } + if (PyNumber_InPlaceOr(newfree, allfree) < 0) + goto error; + /* Check if any local variables must be converted to cell variables */ if (ste->ste_type == FunctionBlock && !analyze_cells(scopes, newfree, NULL)) @@ -753,12 +791,48 @@ analyze_block(PySTEntryObject *ste, PyObject *bound, PyObject *free, Py_XDECREF(newbound); Py_XDECREF(newglobal); Py_XDECREF(newfree); + Py_XDECREF(allfree); if (!success) assert(PyErr_Occurred()); return success; } static int +analyze_child_block(PySTEntryObject *entry, PyObject *bound, PyObject *free, + PyObject *global, PyObject* child_free) +{ + PyObject *temp_bound = NULL, *temp_global = NULL, *temp_free = NULL; + int success = 0; + + /* Copy the bound and global dictionaries. + + These dictionary are used by all blocks enclosed by the + current block. The analyze_block() call modifies these + dictionaries. + + */ + temp_bound = PySet_New(bound); + if (!temp_bound) + goto error; + temp_free = PySet_New(free); + if (!temp_free) + goto error; + temp_global = PySet_New(global); + if (!temp_global) + goto error; + + if (!analyze_block(entry, temp_bound, temp_free, temp_global)) + goto error; + success = PyNumber_InPlaceOr(child_free, temp_free) >= 0; + success = 1; + error: + Py_XDECREF(temp_bound); + Py_XDECREF(temp_free); + Py_XDECREF(temp_global); + return success; +} + +static int symtable_analyze(struct symtable *st) { PyObject *free, *global; |