Skip to content

Commit 829258e

Browse files
committed
gh-156664: Fix free variables in sibling comprehensions
1 parent d59d4e7 commit 829258e

3 files changed

Lines changed: 68 additions & 15 deletions

File tree

Lib/test/test_listcomps.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -757,6 +757,24 @@ def test_multiple_comprehension_name_reuse(self):
757757
self._check_in_scopes(code, {"x": 2, "y": [3]}, ns={"x": 3}, scopes=["class"])
758758
self._check_in_scopes(code, {"x": 2, "y": [2]}, ns={"x": 3}, scopes=["function", "module"])
759759

760+
x = 3
761+
762+
def f():
763+
[x for x in [1]]
764+
return [x for _ in [1]]
765+
766+
self.assertEqual(f(), [3])
767+
768+
def g():
769+
[x for x in [1]]
770+
771+
def inner():
772+
return x
773+
774+
return inner()
775+
776+
self.assertEqual(g(), 3)
777+
760778
def test_exception_locations(self):
761779
# The location of an exception raised from __init__ or
762780
# __next__ should be the iterator expression
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix scope analysis for a name bound in one inlined comprehension and used as
2+
a free variable by a sibling comprehension or nested function.

Python/symtable.c

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -804,7 +804,7 @@ is_free_in_any_child(PySTEntryObject *entry, PyObject *key)
804804
static int
805805
inline_comprehension(PySTEntryObject *ste, PySTEntryObject *comp,
806806
PyObject *scopes, PyObject *comp_free,
807-
PyObject *inlined_cells)
807+
PyObject *inlined_cells, PyObject *local)
808808
{
809809
PyObject *k, *v;
810810
Py_ssize_t pos = 0;
@@ -880,17 +880,23 @@ inline_comprehension(PySTEntryObject *ste, PySTEntryObject *comp,
880880
return 0;
881881
}
882882
if ((flags & DEF_BOUND) && ste->ste_type != ClassBlock) {
883-
// free vars in comprehension that are locals in outer scope can
884-
// now simply be locals, unless they are free in comp children,
885-
// or if the outer scope is a class block
886-
int ok = is_free_in_any_child(comp, k);
887-
if (ok < 0) {
883+
int is_local = PySet_Contains(local, k);
884+
if (is_local < 0) {
888885
return 0;
889886
}
890-
if (!ok) {
891-
if (PySet_Discard(comp_free, k) < 0) {
887+
if (is_local) {
888+
// free vars in comprehension that are locals in outer scope can
889+
// now simply be locals, unless they are free in comp children,
890+
// or if the outer scope is a class block
891+
int ok = is_free_in_any_child(comp, k);
892+
if (ok < 0) {
892893
return 0;
893894
}
895+
if (!ok) {
896+
if (PySet_Discard(comp_free, k) < 0) {
897+
return 0;
898+
}
899+
}
894900
}
895901
}
896902
}
@@ -913,20 +919,27 @@ inline_comprehension(PySTEntryObject *ste, PySTEntryObject *comp,
913919
provides the binding for the free variable. The name should be
914920
marked CELL in this block and removed from the free list.
915921
916-
Note that the current block's free variables are included in free.
917-
That's safe because no name can be free and local in the same scope.
922+
Note that the current block's free variables are included in free. A name
923+
can appear local in scopes and free if the local binding was copied from an
924+
inlined comprehension; such a name is not in local and must remain free.
918925
*/
919926

920927
static int
921-
analyze_cells(PyObject *scopes, PyObject *free, PyObject *inlined_cells)
928+
analyze_cells(PyObject *scopes, PyObject *free, PyObject *inlined_cells,
929+
PyObject *local)
922930
{
923-
PyObject *name, *v, *v_cell;
931+
PyObject *name, *v, *v_cell, *v_free;
924932
int success = 0;
925933
Py_ssize_t pos = 0;
926934

927935
v_cell = PyLong_FromLong(CELL);
928936
if (!v_cell)
929937
return 0;
938+
v_free = PyLong_FromLong(FREE);
939+
if (!v_free) {
940+
Py_DECREF(v_cell);
941+
return 0;
942+
}
930943
while (PyDict_Next(scopes, &pos, &name, &v)) {
931944
long scope = PyLong_AsLong(v);
932945
if (scope == -1 && PyErr_Occurred()) {
@@ -938,7 +951,22 @@ analyze_cells(PyObject *scopes, PyObject *free, PyObject *inlined_cells)
938951
if (contains < 0) {
939952
goto error;
940953
}
941-
if (!contains) {
954+
if (contains) {
955+
int is_local = PySet_Contains(local, name);
956+
if (is_local < 0) {
957+
goto error;
958+
}
959+
if (!is_local) {
960+
// This binding was copied from an inlined comprehension, not
961+
// defined in this scope. Another child may still need the
962+
// name from an enclosing scope.
963+
if (PyDict_SetItem(scopes, name, v_free) < 0) {
964+
goto error;
965+
}
966+
continue;
967+
}
968+
}
969+
else {
942970
contains = PySet_Contains(inlined_cells, name);
943971
if (contains < 0) {
944972
goto error;
@@ -959,6 +987,7 @@ analyze_cells(PyObject *scopes, PyObject *free, PyObject *inlined_cells)
959987
success = 1;
960988
error:
961989
Py_DECREF(v_cell);
990+
Py_DECREF(v_free);
962991
return success;
963992
}
964993

@@ -1275,7 +1304,8 @@ analyze_block(PySTEntryObject *ste, PyObject *bound, PyObject *free,
12751304
goto error;
12761305
}
12771306
if (inline_comp) {
1278-
if (!inline_comprehension(ste, entry, scopes, child_free, inlined_cells)) {
1307+
if (!inline_comprehension(ste, entry, scopes, child_free,
1308+
inlined_cells, local)) {
12791309
Py_DECREF(child_free);
12801310
goto error;
12811311
}
@@ -1303,8 +1333,11 @@ analyze_block(PySTEntryObject *ste, PyObject *bound, PyObject *free,
13031333
}
13041334

13051335
/* Check if any local variables must be converted to cell variables */
1306-
if (_PyST_IsFunctionLike(ste) && !analyze_cells(scopes, newfree, inlined_cells))
1336+
if (_PyST_IsFunctionLike(ste) &&
1337+
!analyze_cells(scopes, newfree, inlined_cells, local))
1338+
{
13071339
goto error;
1340+
}
13081341
else if (ste->ste_type == ClassBlock && !drop_class_free(ste, newfree))
13091342
goto error;
13101343
/* Records the results of the analysis in the symbol table entry */

0 commit comments

Comments
 (0)