https://github.com/python/cpython/commit/ce916dc50644bb1de940f5fb580bd9907cceb959
commit: ce916dc50644bb1de940f5fb580bd9907cceb959
branch: main
author: Bartosz Sławecki <[email protected]>
committer: carljm <[email protected]>
date: 2026-06-09T15:22:13-07:00
summary:
gh-150700: Fix class-scope inline comprehensions when nested scopes reference
`__class__` and friends (#150735)
* Fix class-scope inline comprehensions when nested scopes reference
`__class__` and friends
In `inline_comprehension()`, when `__class__` / `__classdict__` /
`__conditional_annotations__` appears as `FREE` in a comprehension's
symbol table because a nested scope captured it (e.g. nested lambdas),
this name is still discarded from `comp_free` unconditionally.
This prevents `drop_class_free()` from seeing it, so the appropriate
`ste_needs_(...)` flag is never set on the enclosing class.
That leads to `codegen_make_closure()` throwing `SystemError` when it
couldn't find `__class__` / `__classdict__` /
`__conditional_annotations__` in the class's cellvars.
>From now on we just discard from `comp_free` when no child scope
(e.g. a lambda) still needs the name as `FREE`. When a child scope does
need it, keep it in `comp_free` so `drop_class_free()` can set the
appropriate flag and the class creates the implicit cell.
* Fix tests
* Fix typo
* Fix formatting
* Add test checking validity of `__class__` returned
* Prefer 'used' to 'deferred'
files:
A
Misc/NEWS.d/next/Core_and_Builtins/2026-06-01-19-00-00.gh-issue-150700.W8CzVR.rst
M Lib/test/test_listcomps.py
M Python/symtable.c
diff --git a/Lib/test/test_listcomps.py b/Lib/test/test_listcomps.py
index cee528722b85aa..cf3796d9480801 100644
--- a/Lib/test/test_listcomps.py
+++ b/Lib/test/test_listcomps.py
@@ -171,6 +171,17 @@ def test_references___class__(self):
"""
self._check_in_scopes(code, raises=NameError)
+ def test_references___class___nested(self):
+ code = """
+ res = [(lambda: __class__)() for _ in [1]]
+ """
+ self._check_in_scopes(code, raises=NameError)
+
+ def test_references___class___nested_used(self):
+ class _C:
+ res = [lambda: __class__ for _ in [1]]
+ self.assertIs(_C.res[0](), _C)
+
def test_references___class___defined(self):
code = """
__class__ = 2
@@ -180,18 +191,38 @@ def test_references___class___defined(self):
code, outputs={"res": [2]}, scopes=["module", "function"])
self._check_in_scopes(code, raises=NameError, scopes=["class"])
+ def test_references___class___defined_nested(self):
+ code = """
+ __class__ = 2
+ res = [(lambda: __class__)() for x in [1]]
+ """
+ self._check_in_scopes(
+ code, outputs={"res": [2]}, scopes=["module", "function"])
+ self._check_in_scopes(code, raises=NameError, scopes=["class"])
+
def test_references___classdict__(self):
code = """
class i: [__classdict__ for x in y]
"""
self._check_in_scopes(code, raises=NameError)
+ def test_references___classdict___nested(self):
+ class _C:
+ res = [(lambda: __classdict__)() for _ in [1]]
+ self.assertIn("res", _C.res[0])
+
def test_references___conditional_annotations__(self):
code = """
class i: [__conditional_annotations__ for x in y]
"""
self._check_in_scopes(code, raises=NameError)
+ def test_references___conditional_annotations___nested(self):
+ code = """
+ class i: [lambda: __conditional_annotations__ for x in y]
+ """
+ self._check_in_scopes(code, raises=NameError)
+
def test_references___class___enclosing(self):
code = """
__class__ = 2
diff --git
a/Misc/NEWS.d/next/Core_and_Builtins/2026-06-01-19-00-00.gh-issue-150700.W8CzVR.rst
b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-01-19-00-00.gh-issue-150700.W8CzVR.rst
new file mode 100644
index 00000000000000..e7734034ff5c81
--- /dev/null
+++
b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-01-19-00-00.gh-issue-150700.W8CzVR.rst
@@ -0,0 +1,3 @@
+Fix a :exc:`SystemError` when compiling a class-scope comprehension containing
+a ``lambda`` that references ``__class__``, ``__classdict__``, or
+``__conditional_annotations__``. Patch by Bartosz Sławecki.
diff --git a/Python/symtable.c b/Python/symtable.c
index 070e374101b5cd..00ac510fe76b97 100644
--- a/Python/symtable.c
+++ b/Python/symtable.c
@@ -834,17 +834,22 @@ inline_comprehension(PySTEntryObject *ste,
PySTEntryObject *comp,
return 0;
}
// __class__, __classdict__ and __conditional_annotations__ are
- // never allowed to be free through a class scope (see
- // drop_class_free)
+ // not allowed to be free through a class scope (see
+ // drop_class_free) unless children scopes need it
if (scope == FREE && ste->ste_type == ClassBlock &&
(_PyUnicode_EqualToASCIIString(k, "__class__") ||
_PyUnicode_EqualToASCIIString(k, "__classdict__") ||
_PyUnicode_EqualToASCIIString(k,
"__conditional_annotations__"))) {
scope = GLOBAL_IMPLICIT;
- if (PySet_Discard(comp_free, k) < 0) {
+ int child_needs_free = is_free_in_any_child(comp, k);
+ if (child_needs_free < 0) {
return 0;
}
-
+ if (!child_needs_free) {
+ if (PySet_Discard(comp_free, k) < 0) {
+ return 0;
+ }
+ }
if (_PyUnicode_EqualToASCIIString(k, "__class__")) {
remove_dunder_class = 1;
}
_______________________________________________
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]