https://github.com/python/cpython/commit/cc3a0c9d3a260ed78c9751d40af4041bb826bf75
commit: cc3a0c9d3a260ed78c9751d40af4041bb826bf75
branch: 3.15
author: Carl Meyer <[email protected]>
committer: carljm <[email protected]>
date: 2026-10-23T10:41:48-07:00
summary:
[3.15] gh-160299: Preserve references from inlined comprehensions (GH-156819)
(#160413)
Adapt the reference-flag propagation from GH-156819 to fix gh-160299 without
backporting the comprehensive changes to comprehension scope representation and
compilation.
files:
A Misc/NEWS.d/next/Library/2026-10-22-12-00-00.gh-issue-160299.CompUse.rst
M Lib/test/test_symtable.py
M Python/symtable.c
diff --git a/Lib/test/test_symtable.py b/Lib/test/test_symtable.py
index c748243110df9f..4d435f152b08a6 100644
--- a/Lib/test/test_symtable.py
+++ b/Lib/test/test_symtable.py
@@ -617,6 +617,58 @@ def test_filter_syntax_warnings_by_module(self):
class ComprehensionTests(unittest.TestCase):
+ def test_referenced_in_comprehension(self):
+ comps = [
+ "[x + k + local for x in xs]",
+ "{x + k + local for x in xs}",
+ "{x: k + local for x in xs}",
+ "[[x + k + local for x in xs] for _ in xs]",
+ ]
+ for comp in comps:
+ with self.subTest(comp=comp):
+ st = symtable.symtable(
+ "def f(xs, k):\n"
+ " local = 1\n"
+ f" return {comp}\n", "?", "exec")
+ f = find_block(st, "f")
+ self.assertTrue(f.lookup("k").is_referenced())
+ self.assertTrue(f.lookup("local").is_referenced())
+ self.assertEqual(f.get_children(), [])
+
+ def test_nonlocal_referenced_in_comprehension(self):
+ st = symtable.symtable(
+ "def outer(k):\n"
+ " def f(xs):\n"
+ " nonlocal k\n"
+ " return [x + k for x in xs]\n", "?", "exec")
+ f = find_block(find_block(st, "outer"), "f")
+ self.assertTrue(f.lookup("k").is_referenced())
+ self.assertTrue(f.lookup("k").is_nonlocal())
+
+ def test_not_referenced_in_comprehension(self):
+ comps = [
+ "[k for k in xs]", # The iteration variable shadows the parameter.
+ "[[k for _ in ()] for k in xs]", # Shadowing in a nested
comprehension.
+ "[(lambda: k) for x in xs]", # The load is in a nested function.
+ "(x + k for x in xs)", # Generator expressions are not inlined.
+ ]
+ for comp in comps:
+ with self.subTest(comp=comp):
+ st = symtable.symtable(
+ "def f(xs, k):\n"
+ f" return {comp}\n", "?", "exec")
+ self.assertFalse(find_block(st,
"f").lookup("k").is_referenced())
+
+ def test_class_local_not_referenced_in_comprehension(self):
+ st = symtable.symtable(
+ "def outer(k):\n"
+ " class C:\n"
+ " k = 1\n"
+ " values = [k for _ in ()]\n", "?", "exec")
+ cls = find_block(find_block(st, "outer"), "C")
+ # The comprehension refers to outer's k, not the class attribute.
+ self.assertFalse(cls.lookup("k").is_referenced())
+
def get_identifiers_recursive(self, st, res):
res.extend(st.get_identifiers())
for ch in st.get_children():
diff --git
a/Misc/NEWS.d/next/Library/2026-10-22-12-00-00.gh-issue-160299.CompUse.rst
b/Misc/NEWS.d/next/Library/2026-10-22-12-00-00.gh-issue-160299.CompUse.rst
new file mode 100644
index 00000000000000..6bdfaa8a7ba1aa
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-10-22-12-00-00.gh-issue-160299.CompUse.rst
@@ -0,0 +1,2 @@
+Fix :meth:`symtable.Symbol.is_referenced` for local and nonlocal symbols in
+function scopes when their only uses are in inlined comprehensions.
diff --git a/Python/symtable.c b/Python/symtable.c
index 81909120f22152..74b9d73b8062d6 100644
--- a/Python/symtable.c
+++ b/Python/symtable.c
@@ -879,6 +879,20 @@ inline_comprehension(PySTEntryObject *ste, PySTEntryObject
*comp,
if (flags == -1 && PyErr_Occurred()) {
return 0;
}
+ // Propagate uses of enclosing bindings, but not class locals,
+ // which are not visible to comprehensions.
+ if (scope == FREE && ste->ste_type != ClassBlock &&
+ (comp_flags & USE) && !(flags & USE)) {
+ PyObject *v_flags = PyLong_FromLong(flags | USE);
+ if (v_flags == NULL) {
+ return 0;
+ }
+ int ok = PyDict_SetItem(ste->ste_symbols, k, v_flags);
+ Py_DECREF(v_flags);
+ if (ok < 0) {
+ return 0;
+ }
+ }
if ((flags & DEF_BOUND) && ste->ste_type != ClassBlock) {
// free vars in comprehension that are locals in outer scope
can
// now simply be locals, unless they are free in comp children,
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]