From 43d91fdb4f1e42fc21c57b1f450b3da3ab0a406e Mon Sep 17 00:00:00 2001 From: johnslavik Date: Mon, 1 Jun 2026 22:13:52 +0200 Subject: [PATCH 1/4] 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. --- Lib/test/test_listcomps.py | 16 ++++++++++++++++ ...026-06-01-19-00-00.gh-issue-150700.W8CzVR.rst | 3 +++ Python/symtable.c | 13 +++++++++---- 3 files changed, 28 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-06-01-19-00-00.gh-issue-150700.W8CzVR.rst diff --git a/Lib/test/test_listcomps.py b/Lib/test/test_listcomps.py index cee528722b85aa..2d925f35052425 100644 --- a/Lib/test/test_listcomps.py +++ b/Lib/test/test_listcomps.py @@ -171,6 +171,22 @@ def test_references___class__(self): """ self._check_in_scopes(code, raises=NameError) + def test_lambda_in_comprehension_references___class__(self): + # gh-150700: lambda in class-scope comprehension referencing __class__ + # must compile without SystemError and raise NameError at runtime + # (__class__ cell is not yet filled during class body execution). + code = """ + res = [(lambda: __class__)() for _ in [1]] + """ + self._check_in_scopes(code, raises=NameError) + + def test_lambda_in_comprehension_references___classdict__(self): + # gh-150700: lambda in class-scope comprehension referencing __classdict__ + # must compile without SystemError; __classdict__ is available at runtime. + class _C: + res = [(lambda: __classdict__)() for _ in [1]] + self.assertIn("res", _C.res[0]) + def test_references___class___defined(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 2263a2d8db9097..741e617a98d8b9 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; } From 903f916be55a53adbe261a3cdaf112642515cda8 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Mon, 1 Jun 2026 23:12:29 +0200 Subject: [PATCH 2/4] Fix tests --- Lib/test/test_listcomps.py | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/Lib/test/test_listcomps.py b/Lib/test/test_listcomps.py index 2d925f35052425..ae64f182c84cd1 100644 --- a/Lib/test/test_listcomps.py +++ b/Lib/test/test_listcomps.py @@ -170,23 +170,11 @@ def test_references___class__(self): res = [__class__ for x in [1]] """ self._check_in_scopes(code, raises=NameError) - - def test_lambda_in_comprehension_references___class__(self): - # gh-150700: lambda in class-scope comprehension referencing __class__ - # must compile without SystemError and raise NameError at runtime - # (__class__ cell is not yet filled during class body execution). + def test_references___class___nested(self): code = """ - res = [(lambda: __class__)() for _ in [1]] + res = [lambda: __class__ for _ in [1]] """ self._check_in_scopes(code, raises=NameError) - - def test_lambda_in_comprehension_references___classdict__(self): - # gh-150700: lambda in class-scope comprehension referencing __classdict__ - # must compile without SystemError; __classdict__ is available at runtime. - class _C: - res = [(lambda: __classdict__)() for _ in [1]] - self.assertIn("res", _C.res[0]) - def test_references___class___defined(self): code = """ __class__ = 2 @@ -196,18 +184,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___define_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 From e2547b86ed3a8e0c76195608a5c395e160bf2501 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Mon, 1 Jun 2026 23:13:11 +0200 Subject: [PATCH 3/4] Fix typo --- Lib/test/test_listcomps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_listcomps.py b/Lib/test/test_listcomps.py index ae64f182c84cd1..e98dbe68ec832c 100644 --- a/Lib/test/test_listcomps.py +++ b/Lib/test/test_listcomps.py @@ -184,7 +184,7 @@ 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___define_nested(self): + def test_references___class___defined_nested(self): code = """ __class__ = 2 res = [(lambda: __class__)() for x in [1]] From 633c54cedf031aa8f46c0ca5994b10669ff5e5e2 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Mon, 1 Jun 2026 23:13:34 +0200 Subject: [PATCH 4/4] Fix formatting --- Lib/test/test_listcomps.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/test/test_listcomps.py b/Lib/test/test_listcomps.py index e98dbe68ec832c..1d7daf6b9fb535 100644 --- a/Lib/test/test_listcomps.py +++ b/Lib/test/test_listcomps.py @@ -170,11 +170,13 @@ def test_references___class__(self): res = [__class__ for x in [1]] """ 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___defined(self): code = """ __class__ = 2