10000 gh-118513: Fix sibling comprehensions with a name bound in one and gl… · python/cpython@c8deb1e · GitHub
[go: up one dir, main page]

Skip to content

Commit c8deb1e

Browse files
carljmJelleZijlstraEclips4
authored
gh-118513: Fix sibling comprehensions with a name bound in one and global in the other (#118526)
Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com> Co-authored-by: Kirill Podoprigora <kirill.bast9@mail.ru>
1 parent 37ccf16 commit c8deb1e

File tree

3 files changed

+57
-39
lines changed

3 files changed

+57
-39
lines changed

Lib/test/test_listcomps.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,20 @@ def test_code_replace_extended_arg(self):
666666
self._check_in_scopes(code, expected)
667667
self._check_in_scopes(code, expected, exec_func=self._replacing_exec)
668668

669+
def test_multiple_comprehension_name_reuse(self):
670+
code = """
671+
[x for x in [1]]
672+
y = [x for _ in [1]]
673+
"""
674+
self._check_in_scopes(code, {"y": [3]}, ns={"x": 3})
675+
676+
code = """
677+
x = 2
678+
[x for x in [1]]
679+
y = [x for _ in [1]]
680+
"""
681+
self._check_in_scopes(code, {"x": 2, "y": [3]}, ns={"x": 3}, scopes=["class"])
682+
self._check_in_scopes(code, {"x": 2, "y": [2]}, ns={"x": 3}, scopes=["function", "module"])
669683

670684
__test__ = {'doctests' : doctests}
671685

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix incorrect :exc:`UnboundLocalError` when two comprehensions in the same function both reference the same name, and in one comprehension the name is bound while in the other it's an implicit global.

Python/compile.c

Lines changed: 42 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -5561,10 +5561,48 @@ push_inlined_comprehension_state(struct compiler *c, location loc,
55615561
while (PyDict_Next(entry->ste_symbols, &pos, &k, &v)) {
55625562
assert(PyLong_Check(v));
55635563
long symbol = PyLong_AS_LONG(v);
5564-
// only values bound in the comprehension (DEF_LOCAL) need to be handled
5565-
// at all; DEF_LOCAL | DEF_NONLOCAL can occur in the case of an
5566-
// assignment expression to a nonlocal in the comprehension, these don't
5567-
// need handling here since they shouldn't be isolated
5564+
long scope = (symbol >> SCOPE_OFFSET) & SCOPE_MASK;
5565+
PyObject *outv = PyDict_GetItemWithError(c->u->u_ste->ste_symbols, k);
5566+
if (outv == NULL) {
5567+
if (PyErr_Occurred()) {
5568+
return ERROR;
5569+
}
5570+
outv = _PyLong_GetZero();
5571+
}
5572+
assert(PyLong_CheckExact(outv));
5573+
long outsc = (PyLong_AS_LONG(outv) >> SCOPE_OFFSET) & SCOPE_MASK;
5574+
// If a name has different scope inside than outside the comprehension,
5575+
// we need to temporarily handle it with the right scope while
5576+
// compiling the comprehension. If it's free in the comprehension
5577+
// scope, no special handling; it should be handled the same as the
5578+
// enclosing scope. (If it's free in outer scope and cell in inner
5579+
// scope, we can't treat it as both cell and free in the same function,
5580+
// but treating it as free throughout is fine; it's *_DEREF
5581+
// either way.)
5582+
if ((scope != outsc && scope != FREE && !(scope == CELL && outsc == FREE))
5583+
|| in_class_block) {
5584+
if (state->temp_symbols == NULL) {
5585+
state->temp_symbols = PyDict_New();
5586+
if (state->temp_symbols == NULL) {
5587+
return ERROR;
5588+
}
5589+
}
5590+
// update the symbol to the in-comprehension version and save
5591+
// the outer version; we'll restore it after running the
5592+
// comprehension
5593+
Py_INCREF(outv);
5594+
if (PyDict_SetItem(c->u->u_ste->ste_symbols, k, v) < 0) {
5595+
Py_DECREF(outv);
5596+
return ERROR;
5597+
}
5598+
if (PyDict_SetItem(state->temp_symbols, k, outv) < 0) {
5599+
Py_DECREF(outv);
5600+
return ERROR;
5601+
}
5602+
Py_DECREF(outv);
5603+
}
5604+
// locals handling for names bound in comprehension (DEF_LOCAL |
5605+
// DEF_NONLOCAL occurs in assignment expression to nonlocal)
55685606
if ((symbol & DEF_LOCAL && !(symbol & DEF_NONLOCAL)) || in_class_block) {
55695607
if (!_PyST_IsFunctionLike(c->u->u_ste)) {
55705608
// non-function scope: override this name to use fast locals
@@ -5589,41 +5627,6 @@ push_inlined_comprehension_state(struct compiler *c, location loc,
55895627
}
55905628
}
55915629
}
5592-
long scope = (symbol >> SCOPE_OFFSET) & SCOPE_MASK;
5593-
PyObject *outv = PyDict_GetItemWithError(c->u->u_ste->ste_symbols, k);
5594-
if (outv == NULL) {
5595-
outv = _PyLong_GetZero();
5596-
}
5597-
assert(PyLong_Check(outv));
5598-
long outsc = (PyLong_AS_LONG(outv) >> SCOPE_OFFSET) & SCOPE_MASK;
5599-
if (scope != outsc && !(scope == CELL && outsc == FREE)) {
5600-
// If a name has different scope inside than outside the
5601-
// comprehension, we need to temporarily handle it with the
5602-
// right scope while compiling the comprehension. (If it's free
5603-
// in outer scope and cell in inner scope, we can't treat it as
5604-
// both cell and free in the same function, but treating it as
5605-
// free throughout is fine; it's *_DEREF either way.)
5606-
5607-
if (state->temp_symbols == NULL) {
5608-
state->temp_symbols = PyDict_New();
5609-
if (state->temp_symbols == NULL) {
5610-
return ERROR;
5611-
}
5612-
}
5613-
// update the symbol to the in-comprehension version and save
5614-
// the outer version; we'll restore it after running the
5615-
// comprehension
5616-
Py_INCREF(outv);
5617-
if (PyDict_SetItem(c->u->u_ste->ste_symbols, k, v) < 0) {
5618< 341A /code>-
Py_DECREF(outv);
5619-
return ERROR;
5620-
}
5621-
if (PyDict_SetItem(state->temp_symbols, k, outv) < 0) {
5622-
Py_DECREF(outv);
5623-
return ERROR;
5624-
}
5625-
Py_DECREF(outv);
5626-
}
56275630
// local names bound in comprehension must be isolated from
56285631
// outer scope; push existing value (which may be NULL if
56295632
// not defined) on stack

0 commit comments

Comments
 (0)
0