Skip to content

Commit ac66cc1

Browse files
authored
gh-104377: fix cell in comprehension that is free in outer scope (#104394)
1 parent 37a5d25 commit ac66cc1

File tree

2 files changed

+67
-7
lines changed

2 files changed

+67
-7
lines changed

Lib/test/test_listcomps.py

+55-4
Original file line numberDiff line numberDiff line change
@@ -117,15 +117,15 @@ def get_output(moddict, name):
117117
newcode = code
118118
def get_output(moddict, name):
119119
return moddict[name]
120-
ns = ns or {}
120+
newns = ns.copy() if ns else {}
121121
try:
122-
exec(newcode, ns)
122+
exec(newcode, newns)
123123
except raises as e:
124124
# We care about e.g. NameError vs UnboundLocalError
125125
self.assertIs(type(e), raises)
126126
else:
127127
for k, v in (outputs or {}).items():
128-
self.assertEqual(get_output(ns, k), v)
128+
self.assertEqual(get_output(newns, k), v)
129129

130130
def test_lambdas_with_iteration_var_as_default(self):
131131
code = """
@@ -180,6 +180,26 @@ def test_closure_can_jump_over_comp_scope(self):
180180
z = [x() for x in items]
181181
"""
182182
outputs = {"z": [2, 2, 2, 2, 2]}
183+
self._check_in_scopes(code, outputs, scopes=["module", "function"])
184+
185+
def test_cell_inner_free_outer(self):
186+
code = """
187+
def f():
188+
return [lambda: x for x in (x, [1])[1]]
189+
x = ...
190+
y = [fn() for fn in f()]
191+
"""
192+
outputs = {"y": [1]}
193+
self._check_in_scopes(code, outputs, scopes=["module", "function"])
194+
195+
def test_free_inner_cell_outer(self):
196+
code = """
197+
g = 2
198+
def f():
199+
return g
200+
y = [g for x in [1]]
201+
"""
202+
outputs = {"y": [2]}
183203
self._check_in_scopes(code, outputs)
184204

185205
def test_inner_cell_shadows_outer_redefined(self):
@@ -203,6 +223,37 @@ def inner():
203223
outputs = {"x": -1}
204224
self._check_in_scopes(code, outputs, ns={"g": -1})
205225

226+
def test_explicit_global(self):
227+
code = """
228+
global g
229+
x = g
230+
g = 2
231+
items = [g for g in [1]]
232+
y = g
233+
"""
234+
outputs = {"x": 1, "y": 2, "items": [1]}
235+
self._check_in_scopes(code, outputs, ns={"g": 1})
236+
237+
def test_explicit_global_2(self):
238+
code = """
239+
global g
240+
x = g
241+
g = 2
242+
items = [g for x in [1]]
243+
y = g
244+
"""
245+
outputs = {"x": 1, "y": 2, "items": [2]}
246+
self._check_in_scopes(code, outputs, ns={"g": 1})
247+
248+
def test_explicit_global_3(self):
249+
code = """
250+
global g
251+
fns = [lambda: g for g in [2]]
252+
items = [fn() for fn in fns]
253+
"""
254+
outputs = {"items": [2]}
255+
self._check_in_scopes(code, outputs, ns={"g": 1})
256+
206257
def test_assignment_expression(self):
207258
code = """
208259
x = -1
@@ -250,7 +301,7 @@ def g():
250301
g()
251302
"""
252303
outputs = {"x": 1}
253-
self._check_in_scopes(code, outputs)
304+
self._check_in_scopes(code, outputs, scopes=["module", "function"])
254305

255306
def test_introspecting_frame_locals(self):
256307
code = """

Python/compile.c

+12-3
Original file line numberDiff line numberDiff line change
@@ -5028,14 +5028,19 @@ push_inlined_comprehension_state(struct compiler *c, location loc,
50285028
long scope = (symbol >> SCOPE_OFFSET) & SCOPE_MASK;
50295029
PyObject *outv = PyDict_GetItemWithError(c->u->u_ste->ste_symbols, k);
50305030
if (outv == NULL) {
5031+
assert(PyErr_Occurred());
50315032
return ERROR;
50325033
}
50335034
assert(PyLong_Check(outv));
50345035
long outsc = (PyLong_AS_LONG(outv) >> SCOPE_OFFSET) & SCOPE_MASK;
5035-
if (scope != outsc) {
5036+
if (scope != outsc && !(scope == CELL && outsc == FREE)) {
50365037
// If a name has different scope inside than outside the
50375038
// comprehension, we need to temporarily handle it with the
5038-
// right scope while compiling the comprehension.
5039+
// right scope while compiling the comprehension. (If it's free
5040+
// in outer scope and cell in inner scope, we can't treat it as
5041+
// both cell and free in the same function, but treating it as
5042+
// free throughout is fine; it's *_DEREF either way.)
5043+
50395044
if (state->temp_symbols == NULL) {
50405045
state->temp_symbols = PyDict_New();
50415046
if (state->temp_symbols == NULL) {
@@ -5071,7 +5076,11 @@ push_inlined_comprehension_state(struct compiler *c, location loc,
50715076
// comprehension and restore the original one after
50725077
ADDOP_NAME(c, loc, LOAD_FAST_AND_CLEAR, k, varnames);
50735078
if (scope == CELL) {
5074-
ADDOP_NAME(c, loc, MAKE_CELL, k, cellvars);
5079+
if (outsc == FREE) {
5080+
ADDOP_NAME(c, loc, MAKE_CELL, k, freevars);
5081+
} else {
5082+
ADDOP_NAME(c, loc, MAKE_CELL, k, cellvars);
5083+
}
50755084
}
50765085
if (PyList_Append(state->pushed_locals, k) < 0) {
50775086
return ERROR;

0 commit comments

Comments
 (0)