Skip to content

Commit f37708e

Browse files
committed
Merged revisions 70801,70809 via svnmerge from
svn+ssh://pythondev@svn.python.org/python/trunk The merge ran into a lot of conflicts because dicts were replaced with sets in the Python 3 version of the symbol table. ........ r70801 | jeremy.hylton | 2009-03-31 09:17:03 -0400 (Tue, 31 Mar 2009) | 3 lines Add is_declared_global() which distinguishes between implicit and explicit global variables. ........ r70809 | jeremy.hylton | 2009-03-31 09:48:15 -0400 (Tue, 31 Mar 2009) | 14 lines Global statements from one function leaked into parallel functions. Re http://bugs.python.org/issue4315 The symbol table used the same name dictionaries to recursively analyze each of its child blocks, even though the dictionaries are modified during analysis. The fix is to create new temporary dictionaries via the analyze_child_block(). The only information that needs to propagate back up is the names of the free variables. Add more comments and break out a helper function. This code doesn't get any easier to understand when you only look at it once a year. ........
1 parent 1052f89 commit f37708e

4 files changed

Lines changed: 123 additions & 22 deletions

File tree

Lib/symtable.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,9 @@ def is_parameter(self):
191191
def is_global(self):
192192
return bool(self.__scope in (GLOBAL_IMPLICIT, GLOBAL_EXPLICIT))
193193

194+
def is_declared_global(self):
195+
return bool(self.__scope == GLOBAL_EXPLICIT)
196+
194197
def is_local(self):
195198
return bool(self.__flags & DEF_BOUND)
196199

Lib/test/test_scope.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -618,7 +618,6 @@ def dec():
618618
self.assertEqual(dec(), 0)
619619

620620
def testNonLocalMethod(self):
621-
622621
def f(x):
623622
class c:
624623
def inc(self):
@@ -630,13 +629,36 @@ def dec(self):
630629
x -= 1
631630
return x
632631
return c()
633-
634632
c = f(0)
635633
self.assertEqual(c.inc(), 1)
636634
self.assertEqual(c.inc(), 2)
637635
self.assertEqual(c.dec(), 1)
638636
self.assertEqual(c.dec(), 0)
639637

638+
def testGlobalInParallelNestedFunctions(self):
639+
# A symbol table bug leaked the global statement from one
640+
# function to other nested functions in the same block.
641+
# This test verifies that a global statement in the first
642+
# function does not affect the second function.
643+
CODE = """def f():
644+
y = 1
645+
def g():
646+
global y
647+
return y
648+
def h():
649+
return y + 1
650+
return g, h
651+
y = 9
652+
g, h = f()
653+
result9 = g()
654+
result2 = h()
655+
"""
656+
local_ns = {}
657+
global_ns = {}
658+
exec(CODE, local_ns, global_ns)
659+
self.assertEqual(2, global_ns["result2"])
660+
self.assertEqual(9, global_ns["result9"])
661+
640662
def testNonLocalClass(self):
641663

642664
def f(x):

Lib/test/test_symtable.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,9 @@ def test_function_info(self):
8888

8989
def test_globals(self):
9090
self.assertTrue(self.spam.lookup("glob").is_global())
91+
self.assertFalse(self.spam.lookup("glob").is_declared_global())
9192
self.assertTrue(self.spam.lookup("bar").is_global())
93+
self.assertTrue(self.spam.lookup("bar").is_declared_global())
9294
self.assertFalse(self.internal.lookup("x").is_global())
9395
self.assertFalse(self.Mine.lookup("instance_var").is_global())
9496

Python/symtable.c

Lines changed: 94 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -377,8 +377,9 @@ PyST_GetScope(PySTEntryObject *ste, PyObject *name)
377377

378378
/* Decide on scope of name, given flags.
379379
380-
The dicts passed in as arguments are modified as necessary.
381-
ste is passed so that flags can be updated.
380+
The namespace dictionaries may be modified to record information
381+
about the new name. For example, a new global will add an entry to
382+
global. A name that was global can be changed to local.
382383
*/
383384

384385
static int
@@ -454,7 +455,7 @@ analyze_name(PySTEntryObject *ste, PyObject *scopes, PyObject *name, long flags,
454455
explicit? It could also be global implicit.
455456
*/
456457
if (global && PySet_Contains(global, name)) {
457-
SET_SCOPE(scopes, name, GLOBAL_EXPLICIT);
458+
SET_SCOPE(scopes, name, GLOBAL_IMPLICIT);
458459
return 1;
459460
}
460461
if (ste->ste_nested)
@@ -628,28 +629,56 @@ update_symbols(PyObject *symbols, PyObject *scopes,
628629
}
629630

630631
/* Make final symbol table decisions for block of ste.
632+
631633
Arguments:
632634
ste -- current symtable entry (input/output)
633-
bound -- set of variables bound in enclosing scopes (input)
635+
bound -- set of variables bound in enclosing scopes (input). bound
636+
is NULL for module blocks.
634637
free -- set of free variables in enclosed scopes (output)
635638
globals -- set of declared global variables in enclosing scopes (input)
639+
640+
The implementation uses two mutually recursive functions,
641+
analyze_block() and analyze_child_block(). analyze_block() is
642+
responsible for analyzing the individual names defined in a block.
643+
analyze_child_block() prepares temporary namespace dictionaries
644+
used to evaluated nested blocks.
645+
646+
The two functions exist because a child block should see the name
647+
bindings of its enclosing blocks, but those bindings should not
648+
propagate back to a parent block.
636649
*/
637650

651+
static int
652+
analyze_child_block(PySTEntryObject *entry, PyObject *bound, PyObject *free,
653+
PyObject *global, PyObject* child_free);
654+
638655
static int
639656
analyze_block(PySTEntryObject *ste, PyObject *bound, PyObject *free,
640657
PyObject *global)
641658
{
642659
PyObject *name, *v, *local = NULL, *scopes = NULL, *newbound = NULL;
643-
PyObject *newglobal = NULL, *newfree = NULL;
660+
PyObject *newglobal = NULL, *newfree = NULL, *allfree = NULL;
644661
int i, success = 0;
645662
Py_ssize_t pos = 0;
646663

647-
scopes = PyDict_New();
648-
if (!scopes)
649-
goto error;
650-
local = PySet_New(NULL);
664+
local = PySet_New(NULL); /* collect new names bound in block */
651665
if (!local)
652666
goto error;
667+
scopes = PyDict_New(); /* collect scopes defined for each name */
668+
if (!scopes)
669+
goto error;
670+
671+
/* Allocate new global and bound variable dictionaries. These
672+
dictionaries hold the names visible in nested blocks. For
673+
ClassBlocks, the bound and global names are initialized
674+
before analyzing names, because class bindings aren't
675+
visible in methods. For other blocks, they are initialized
676+
after names are analyzed.
677+
*/
678+
679+
/* TODO(jhylton): Package these dicts in a struct so that we
680+
can write reasonable helper functions?
681+
*/
653682
newglobal = PySet_New(NULL);
654683
if (!newglobal)
655684
goto error;
@@ -666,25 +695,22 @@ analyze_block(PySTEntryObject *ste, PyObject *bound, PyObject *free,
666695
this one.
667696
*/
668697
if (ste->ste_type == ClassBlock) {
698+
/* Pass down known globals */
699+
if (!PyNumber_InPlaceOr(newglobal, global))
700+
goto error;
701+
Py_DECREF(newglobal);
669702
/* Pass down previously bound symbols */
670703
if (bound) {
671704
if (!PyNumber_InPlaceOr(newbound, bound))
672705
goto error;
673706
Py_DECREF(newbound);
674707
}
675-
/* Pass down known globals */
676-
if (!PyNumber_InPlaceOr(newglobal, global))
677-
goto error;
678-
Py_DECREF(newglobal);
679708
}
680709

681-
/* Analyze symbols in current scope */
682-
assert(PySTEntry_Check(ste));
683-
assert(PyDict_Check(ste->ste_symbols));
684710
while (PyDict_Next(ste->ste_symbols, &pos, &name, &v)) {
685711
long flags = PyLong_AS_LONG(v);
686-
if (!analyze_name(ste, scopes, name, flags, bound, local, free,
687-
global))
712+
if (!analyze_name(ste, scopes, name, flags,
713+
bound, local, free, global))
688714
goto error;
689715
}
690716

@@ -716,19 +742,31 @@ analyze_block(PySTEntryObject *ste, PyObject *bound, PyObject *free,
716742
goto error;
717743
}
718744

719-
/* Recursively call analyze_block() on each child block */
745+
/* Recursively call analyze_block() on each child block.
746+
747+
newbound, newglobal now contain the names visible in
748+
nested blocks. The free variables in the children will
749+
be collected in allfree.
750+
*/
751+
allfree = PySet_New(NULL);
752+
if (!allfree)
753+
goto error;
720754
for (i = 0; i < PyList_GET_SIZE(ste->ste_children); ++i) {
721755
PyObject *c = PyList_GET_ITEM(ste->ste_children, i);
722756
PySTEntryObject* entry;
723757
assert(c && PySTEntry_Check(c));
724758
entry = (PySTEntryObject*)c;
725-
if (!analyze_block(entry, newbound, newfree, newglobal))
759+
if (!analyze_child_block(entry, newbound, newfree, newglobal,
760+
allfree))
726761
goto error;
727762
/* Check if any children have free variables */
728763
if (entry->ste_free || entry->ste_child_free)
729764
ste->ste_child_free = 1;
730765
}
731766

767+
if (PyNumber_InPlaceOr(newfree, allfree) < 0)
768+
goto error;
769+
732770
/* Check if any local variables must be converted to cell variables */
733771
if (ste->ste_type == FunctionBlock && !analyze_cells(scopes, newfree,
734772
NULL))
@@ -753,11 +791,47 @@ analyze_block(PySTEntryObject *ste, PyObject *bound, PyObject *free,
753791
Py_XDECREF(newbound);
754792
Py_XDECREF(newglobal);
755793
Py_XDECREF(newfree);
794+
Py_XDECREF(allfree);
756795
if (!success)
757796
assert(PyErr_Occurred());
758797
return success;
759798
}
760799

800+
static int
801+
analyze_child_block(PySTEntryObject *entry, PyObject *bound, PyObject *free,
802+
PyObject *global, PyObject* child_free)
803+
{
804+
PyObject *temp_bound = NULL, *temp_global = NULL, *temp_free = NULL;
805+
int success = 0;
806+
807+
/* Copy the bound and global dictionaries.
808+
809+
These dictionary are used by all blocks enclosed by the
810+
current block. The analyze_block() call modifies these
811+
dictionaries.
812+
813+
*/
814+
temp_bound = PySet_New(bound);
815+
if (!temp_bound)
816+
goto error;
817+
temp_free = PySet_New(free);
818+
if (!temp_free)
819+
goto error;
820+
temp_global = PySet_New(global);
821+
if (!temp_global)
822+
goto error;
823+
824+
if (!analyze_block(entry, temp_bound, temp_free, temp_global))
825+
goto error;
826+
success = PyNumber_InPlaceOr(child_free, temp_free) >= 0;
827+
success = 1;
828+
error:
829+
Py_XDECREF(temp_bound);
830+
Py_XDECREF(temp_free);
831+
Py_XDECREF(temp_global);
832+
return success;
833+
}
834+
761835
static int
762836
symtable_analyze(struct symtable *st)
763837
{

0 commit comments

Comments
 (0)