Skip to content

Commit c1442c5

Browse files
committed
pythongh-104602: ensure all cellvars are known up front
1 parent 152227b commit c1442c5

File tree

4 files changed

+35
-22
lines changed

4 files changed

+35
-22
lines changed

Include/internal/pycore_symtable.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,14 +120,15 @@ extern PyObject* _Py_Mangle(PyObject *p, PyObject *name);
120120
#define DEF_ANNOT 2<<7 /* this name is annotated */
121121
#define DEF_COMP_ITER 2<<8 /* this name is a comprehension iteration variable */
122122
#define DEF_TYPE_PARAM 2<<9 /* this name is a type parameter */
123+
#define DEF_COMP_CELL 2<<10 /* this name is a cell in an inlined comprehension */
123124

124125
#define DEF_BOUND (DEF_LOCAL | DEF_PARAM | DEF_IMPORT)
125126

126127
/* GLOBAL_EXPLICIT and GLOBAL_IMPLICIT are used internally by the symbol
127128
table. GLOBAL is returned from PyST_GetScope() for either of them.
128-
It is stored in ste_symbols at bits 12-15.
129+
It is stored in ste_symbols at bits 13-16.
129130
*/
130-
#define SCOPE_OFFSET 11
131+
#define SCOPE_OFFSET 12
131132
#define SCOPE_MASK (DEF_GLOBAL | DEF_LOCAL | DEF_PARAM | DEF_NONLOCAL)
132133

133134
#define LOCAL 1

Lib/test/test_listcomps.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,17 @@ def f():
379379
with self.assertRaises(UnboundLocalError):
380380
f()
381381

382+
def test_global_outside_cellvar_inside_plus_freevar(self):
383+
code = """
384+
a = 1
385+
def f():
386+
[(lambda: b) for b in [a]]
387+
return b
388+
x = f()
389+
"""
390+
self._check_in_scopes(
391+
code, {"x": 2}, ns={"b": 2}, scopes=["function", "module"])
392+
382393

383394
__test__ = {'doctests' : doctests}
384395

Python/compile.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1250,7 +1250,7 @@ compiler_enter_scope(struct compiler *c, identifier name,
12501250
}
12511251
u->u_metadata.u_name = Py_NewRef(name);
12521252
u->u_metadata.u_varnames = list2dict(u->u_ste->ste_varnames);
1253-
u->u_metadata.u_cellvars = dictbytype(u->u_ste->ste_symbols, CELL, 0, 0);
1253+
u->u_metadata.u_cellvars = dictbytype(u->u_ste->ste_symbols, CELL, DEF_COMP_CELL, 0);
12541254
if (!u->u_metadata.u_varnames || !u->u_metadata.u_cellvars) {
12551255
compiler_unit_free(u);
12561256
return ERROR;

Python/symtable.c

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -632,7 +632,7 @@ is_free_in_any_child(PySTEntryObject *entry, PyObject *key)
632632
static int
633633
inline_comprehension(PySTEntryObject *ste, PySTEntryObject *comp,
634634
PyObject *scopes, PyObject *comp_free,
635-
PyObject *promote_to_cell)
635+
PyObject *inlined_cells)
636636
{
637637
PyObject *k, *v;
638638
Py_ssize_t pos = 0;
@@ -645,6 +645,11 @@ inline_comprehension(PySTEntryObject *ste, PySTEntryObject *comp,
645645
}
646646
int scope = (comp_flags >> SCOPE_OFFSET) & SCOPE_MASK;
647647
int only_flags = comp_flags & ((1 << SCOPE_OFFSET) - 1);
648+
if (scope == CELL) {
649+
if (PySet_Add(inlined_cells, k) < 0) {
650+
return 0;
651+
}
652+
}
648653
PyObject *existing = PyDict_GetItemWithError(ste->ste_symbols, k);
649654
if (existing == NULL && PyErr_Occurred()) {
650655
return 0;
@@ -665,14 +670,6 @@ inline_comprehension(PySTEntryObject *ste, PySTEntryObject *comp,
665670
}
666671
else {
667672
if (PyLong_AsLong(existing) & DEF_BOUND) {
668-
// cell vars in comprehension that are locals in outer scope
669-
// must be promoted to cell so u_cellvars isn't wrong
670-
if (scope == CELL && _PyST_IsFunctionLike(ste)) {
671-
if (PySet_Add(promote_to_cell, k) < 0) {
672-
return 0;
673-
}
674-
}
675-
676673
// free vars in comprehension that are locals in outer scope can
677674
// now simply be locals, unless they are free in comp children
678675
if (!is_free_in_any_child(comp, k)) {
@@ -697,7 +694,7 @@ inline_comprehension(PySTEntryObject *ste, PySTEntryObject *comp,
697694
*/
698695

699696
static int
700-
analyze_cells(PyObject *scopes, PyObject *free, PyObject *promote_to_cell)
697+
analyze_cells(PyObject *scopes, PyObject *free, PyObject *inlined_cells)
701698
{
702699
PyObject *name, *v, *v_cell;
703700
int success = 0;
@@ -712,7 +709,7 @@ analyze_cells(PyObject *scopes, PyObject *free, PyObject *promote_to_cell)
712709
scope = PyLong_AS_LONG(v);
713710
if (scope != LOCAL)
714711
continue;
715-
if (!PySet_Contains(free, name) && !PySet_Contains(promote_to_cell, name))
712+
if (!PySet_Contains(free, name) && !PySet_Contains(inlined_cells, name))
716713
continue;
717714
/* Replace LOCAL with CELL for this name, and remove
718715
from free. It is safe to replace the value of name
@@ -752,7 +749,8 @@ drop_class_free(PySTEntryObject *ste, PyObject *free)
752749
*/
753750
static int
754751
update_symbols(PyObject *symbols, PyObject *scopes,
755-
PyObject *bound, PyObject *free, int classflag)
752+
PyObject *bound, PyObject *free,
753+
PyObject *inlined_cells, int classflag)
756754
{
757755
PyObject *name = NULL, *itr = NULL;
758756
PyObject *v = NULL, *v_scope = NULL, *v_new = NULL, *v_free = NULL;
@@ -763,6 +761,9 @@ update_symbols(PyObject *symbols, PyObject *scopes,
763761
long scope, flags;
764762
assert(PyLong_Check(v));
765763
flags = PyLong_AS_LONG(v);
764+
if (PySet_Contains(inlined_cells, name)) {
765+
flags |= DEF_COMP_CELL;
766+
}
766767
v_scope = PyDict_GetItemWithError(scopes, name);
767768
assert(v_scope && PyLong_Check(v_scope));
768769
scope = PyLong_AS_LONG(v_scope);
@@ -869,7 +870,7 @@ analyze_block(PySTEntryObject *ste, PyObject *bound, PyObject *free,
869870
PySTEntryObject *class_entry)
870871
{
871872
PyObject *name, *v, *local = NULL, *scopes = NULL, *newbound = NULL;
872-
PyObject *newglobal = NULL, *newfree = NULL, *promote_to_cell = NULL;
873+
PyObject *newglobal = NULL, *newfree = NULL, *inlined_cells = NULL;
873874
PyObject *temp;
874875
int success = 0;
875876
Py_ssize_t i, pos = 0;
@@ -901,8 +902,8 @@ analyze_block(PySTEntryObject *ste, PyObject *bound, PyObject *free,
901902
newbound = PySet_New(NULL);
902903
if (!newbound)
903904
goto error;
904-
promote_to_cell = PySet_New(NULL);
905-
if (!promote_to_cell)
905+
inlined_cells = PySet_New(NULL);
906+
if (!inlined_cells)
906907
goto error;
907908

908909
/* Class namespace has no effect on names visible in
@@ -996,7 +997,7 @@ analyze_block(PySTEntryObject *ste, PyObject *bound, PyObject *free,
996997
goto error;
997998
}
998999
if (inline_comp) {
999-
if (!inline_comprehension(ste, entry, scopes, child_free, promote_to_cell)) {
1000+
if (!inline_comprehension(ste, entry, scopes, child_free, inlined_cells)) {
10001001
Py_DECREF(child_free);
10011002
goto error;
10021003
}
@@ -1027,12 +1028,12 @@ analyze_block(PySTEntryObject *ste, PyObject *bound, PyObject *free,
10271028
}
10281029

10291030
/* Check if any local variables must be converted to cell variables */
1030-
if (_PyST_IsFunctionLike(ste) && !analyze_cells(scopes, newfree, promote_to_cell))
1031+
if (_PyST_IsFunctionLike(ste) && !analyze_cells(scopes, newfree, inlined_cells))
10311032
goto error;
10321033
else if (ste->ste_type == ClassBlock && !drop_class_free(ste, newfree))
10331034
goto error;
10341035
/* Records the results of the analysis in the symbol table entry */
1035-
if (!update_symbols(ste->ste_symbols, scopes, bound, newfree,
1036+
if (!update_symbols(ste->ste_symbols, scopes, bound, newfree, inlined_cells,
10361037
ste->ste_type == ClassBlock))
10371038
goto error;
10381039

@@ -1047,7 +1048,7 @@ analyze_block(PySTEntryObject *ste, PyObject *bound, PyObject *free,
10471048
Py_XDECREF(newbound);
10481049
Py_XDECREF(newglobal);
10491050
Py_XDECREF(newfree);
1050-
Py_XDECREF(promote_to_cell);
1051+
Py_XDECREF(inlined_cells);
10511052
if (!success)
10521053
assert(PyErr_Occurred());
10531054
return success;

0 commit comments

Comments
 (0)