Skip to content

Commit

Permalink
pythongh-118513: Fix sibling comprehensions with a name bound in one …
Browse files Browse the repository at this point in the history
…and global in the other
  • Loading branch information
carljm committed May 2, 2024
1 parent 16acecd commit 1258fad
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 39 deletions.
7 changes: 7 additions & 0 deletions Lib/test/test_listcomps.py
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,13 @@ def test_code_replace_extended_arg(self):
self._check_in_scopes(code, expected)
self._check_in_scopes(code, expected, exec_func=self._replacing_exec)

def test_multiple_comprehension_name_reuse(self):
code = """
[x for x in [1]]
y = [x for _ in [1]]
"""
self._check_in_scopes(code, {"y": [3]}, ns={"x": 3})


__test__ = {'doctests' : doctests}

Expand Down
78 changes: 39 additions & 39 deletions Python/compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -5500,10 +5500,45 @@ push_inlined_comprehension_state(struct compiler *c, location loc,
while (PyDict_Next(entry->ste_symbols, &pos, &k, &v)) {
assert(PyLong_Check(v));
long symbol = PyLong_AS_LONG(v);
// only values bound in the comprehension (DEF_LOCAL) need to be handled
// at all; DEF_LOCAL | DEF_NONLOCAL can occur in the case of an
// assignment expression to a nonlocal in the comprehension, these don't
// need handling here since they shouldn't be isolated
long scope = (symbol >> SCOPE_OFFSET) & SCOPE_MASK;
PyObject *outv = PyDict_GetItemWithError(c->u->u_ste->ste_symbols, k);
if (outv == NULL) {
outv = _PyLong_GetZero();
}
assert(PyLong_Check(outv));
long outsc = (PyLong_AS_LONG(outv) >> SCOPE_OFFSET) & SCOPE_MASK;
// If a name has different scope inside than outside the comprehension,
// we need to temporarily handle it with the right scope while
// compiling the comprehension. If it's free in the comprehension
// scope, no special handling; it should be handled the same as the
// enclosing scope. (If it's free in outer scope and cell in inner
// scope, we can't treat it as both cell and free in the same function,
// but treating it as free throughout is fine; it's *_DEREF
// either way.)
if ((scope != outsc && scope != FREE && !(scope == CELL && outsc == FREE))
|| in_class_block) {
if (state->temp_symbols == NULL) {
state->temp_symbols = PyDict_New();
if (state->temp_symbols == NULL) {
return ERROR;
}
}
// update the symbol to the in-comprehension version and save
// the outer version; we'll restore it after running the
// comprehension
Py_INCREF(outv);
if (PyDict_SetItem(c->u->u_ste->ste_symbols, k, v) < 0) {
Py_DECREF(outv);
return ERROR;
}
if (PyDict_SetItem(state->temp_symbols, k, outv) < 0) {
Py_DECREF(outv);
return ERROR;
}
Py_DECREF(outv);
}
// locals handling for names bound in comprehension (DEF_LOCAL |
// DEF_NONLOCAL occurs in assignment expression to nonlocal)
if ((symbol & DEF_LOCAL && !(symbol & DEF_NONLOCAL)) || in_class_block) {
if (!_PyST_IsFunctionLike(c->u->u_ste)) {
// non-function scope: override this name to use fast locals
Expand All @@ -5528,41 +5563,6 @@ push_inlined_comprehension_state(struct compiler *c, location loc,
}
}
}
long scope = (symbol >> SCOPE_OFFSET) & SCOPE_MASK;
PyObject *outv = PyDict_GetItemWithError(c->u->u_ste->ste_symbols, k);
if (outv == NULL) {
outv = _PyLong_GetZero();
}
assert(PyLong_Check(outv));
long outsc = (PyLong_AS_LONG(outv) >> SCOPE_OFFSET) & SCOPE_MASK;
if (scope != outsc && !(scope == CELL && outsc == FREE)) {
// If a name has different scope inside than outside the
// comprehension, we need to temporarily handle it with the
// right scope while compiling the comprehension. (If it's free
// in outer scope and cell in inner scope, we can't treat it as
// both cell and free in the same function, but treating it as
// free throughout is fine; it's *_DEREF either way.)

if (state->temp_symbols == NULL) {
state->temp_symbols = PyDict_New();
if (state->temp_symbols == NULL) {
return ERROR;
}
}
// update the symbol to the in-comprehension version and save
// the outer version; we'll restore it after running the
// comprehension
Py_INCREF(outv);
if (PyDict_SetItem(c->u->u_ste->ste_symbols, k, v) < 0) {
Py_DECREF(outv);
return ERROR;
}
if (PyDict_SetItem(state->temp_symbols, k, outv) < 0) {
Py_DECREF(outv);
return ERROR;
}
Py_DECREF(outv);
}
// local names bound in comprehension must be isolated from
// outer scope; push existing value (which may be NULL if
// not defined) on stack
Expand Down

0 comments on commit 1258fad

Please sign in to comment.