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

Lines changed: 55 additions & 4 deletions
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

Lines changed: 12 additions & 3 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy