https://github.com/python/cpython/commit/73e5d444ac4f0801074c3a27271070774ff88b62 commit: 73e5d444ac4f0801074c3a27271070774ff88b62 branch: 3.15 author: Miss Islington (bot) <[email protected]> committer: carljm <[email protected]> date: 2026-06-09T16:33:04-07:00 summary:
[3.15] gh-150700: Fix class-scope inline comprehensions when nested scopes reference `__class__` and friends (GH-150735) (#151211) gh-150700: Fix class-scope inline comprehensions when nested scopes reference `__class__` and friends (GH-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' (cherry picked from commit ce916dc50644bb1de940f5fb580bd9907cceb959) Co-authored-by: Bartosz Sławecki <[email protected]> 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 9a2e278caaf9e2..bd2b5145efdd48 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]
