summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarl Meyer <carl@oddbird.net>2023-05-10 19:08:40 -0600
committerGitHub <noreply@github.com>2023-05-10 18:08:40 -0700
commitfcd5fb49b1d71165f3c503c3d2e74a082ddb2f21 (patch)
treeb806e49c3b5bb80f82c9b5a32d5c3ee916efc6d2
parent94f30c75576bb8a20724b2ac758fa33af089a522 (diff)
downloadcpython-git-fcd5fb49b1d71165f3c503c3d2e74a082ddb2f21.tar.gz
gh-104357: fix inlined comprehensions that close over iteration var (#104368)
-rw-r--r--Lib/test/test_listcomps.py10
-rw-r--r--Python/symtable.c19
2 files changed, 23 insertions, 6 deletions
diff --git a/Lib/test/test_listcomps.py b/Lib/test/test_listcomps.py
index 92fed98dd0..1cc202bb59 100644
--- a/Lib/test/test_listcomps.py
+++ b/Lib/test/test_listcomps.py
@@ -163,6 +163,16 @@ class ListComprehensionTest(unittest.TestCase):
outputs = {"y": [4, 4, 4, 4, 4], "i": 20}
self._check_in_scopes(code, outputs)
+ def test_inner_cell_shadows_outer_no_store(self):
+ code = """
+ def f(x):
+ return [lambda: x for x in range(x)], x
+ fns, x = f(2)
+ y = [fn() for fn in fns]
+ """
+ outputs = {"y": [1, 1], "x": 2}
+ self._check_in_scopes(code, outputs)
+
def test_closure_can_jump_over_comp_scope(self):
code = """
items = [(lambda: y) for i in range(5)]
diff --git a/Python/symtable.c b/Python/symtable.c
index 6e74d76424..9361674bf1 100644
--- a/Python/symtable.c
+++ b/Python/symtable.c
@@ -607,12 +607,19 @@ inline_comprehension(PySTEntryObject *ste, PySTEntryObject *comp,
SET_SCOPE(scopes, k, scope);
}
else {
- // free vars in comprehension that are locals in outer scope can
- // now simply be locals, unless they are free in comp children
- if ((PyLong_AsLong(existing) & DEF_BOUND) &&
- !is_free_in_any_child(comp, k)) {
- if (PySet_Discard(comp_free, k) < 0) {
- return 0;
+ if (PyLong_AsLong(existing) & DEF_BOUND) {
+ // cell vars in comprehension that are locals in outer scope
+ // must be promoted to cell so u_cellvars isn't wrong
+ if (scope == CELL && ste->ste_type == FunctionBlock) {
+ SET_SCOPE(scopes, k, scope);
+ }
+
+ // free vars in comprehension that are locals in outer scope can
+ // now simply be locals, unless they are free in comp children
+ if (!is_free_in_any_child(comp, k)) {
+ if (PySet_Discard(comp_free, k) < 0) {
+ return 0;
+ }
}
}
}