Skip to content

Commit ce916dc

Browse files
authored
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'
1 parent 5804991 commit ce916dc

3 files changed

Lines changed: 43 additions & 4 deletions

File tree

Lib/test/test_listcomps.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,17 @@ def test_references___class__(self):
171171
"""
172172
self._check_in_scopes(code, raises=NameError)
173173

174+
def test_references___class___nested(self):
175+
code = """
176+
res = [(lambda: __class__)() for _ in [1]]
177+
"""
178+
self._check_in_scopes(code, raises=NameError)
179+
180+
def test_references___class___nested_used(self):
181+
class _C:
182+
res = [lambda: __class__ for _ in [1]]
183+
self.assertIs(_C.res[0](), _C)
184+
174185
def test_references___class___defined(self):
175186
code = """
176187
__class__ = 2
@@ -180,18 +191,38 @@ def test_references___class___defined(self):
180191
code, outputs={"res": [2]}, scopes=["module", "function"])
181192
self._check_in_scopes(code, raises=NameError, scopes=["class"])
182193

194+
def test_references___class___defined_nested(self):
195+
code = """
196+
__class__ = 2
197+
res = [(lambda: __class__)() for x in [1]]
198+
"""
199+
self._check_in_scopes(
200+
code, outputs={"res": [2]}, scopes=["module", "function"])
201+
self._check_in_scopes(code, raises=NameError, scopes=["class"])
202+
183203
def test_references___classdict__(self):
184204
code = """
185205
class i: [__classdict__ for x in y]
186206
"""
187207
self._check_in_scopes(code, raises=NameError)
188208

209+
def test_references___classdict___nested(self):
210+
class _C:
211+
res = [(lambda: __classdict__)() for _ in [1]]
212+
self.assertIn("res", _C.res[0])
213+
189214
def test_references___conditional_annotations__(self):
190215
code = """
191216
class i: [__conditional_annotations__ for x in y]
192217
"""
193218
self._check_in_scopes(code, raises=NameError)
194219

220+
def test_references___conditional_annotations___nested(self):
221+
code = """
222+
class i: [lambda: __conditional_annotations__ for x in y]
223+
"""
224+
self._check_in_scopes(code, raises=NameError)
225+
195226
def test_references___class___enclosing(self):
196227
code = """
197228
__class__ = 2
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix a :exc:`SystemError` when compiling a class-scope comprehension containing
2+
a ``lambda`` that references ``__class__``, ``__classdict__``, or
3+
``__conditional_annotations__``. Patch by Bartosz Sławecki.

Python/symtable.c

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -834,17 +834,22 @@ inline_comprehension(PySTEntryObject *ste, PySTEntryObject *comp,
834834
return 0;
835835
}
836836
// __class__, __classdict__ and __conditional_annotations__ are
837-
// never allowed to be free through a class scope (see
838-
// drop_class_free)
837+
// not allowed to be free through a class scope (see
838+
// drop_class_free) unless children scopes need it
839839
if (scope == FREE && ste->ste_type == ClassBlock &&
840840
(_PyUnicode_EqualToASCIIString(k, "__class__") ||
841841
_PyUnicode_EqualToASCIIString(k, "__classdict__") ||
842842
_PyUnicode_EqualToASCIIString(k, "__conditional_annotations__"))) {
843843
scope = GLOBAL_IMPLICIT;
844-
if (PySet_Discard(comp_free, k) < 0) {
844+
int child_needs_free = is_free_in_any_child(comp, k);
845+
if (child_needs_free < 0) {
845846
return 0;
846847
}
847-
848+
if (!child_needs_free) {
849+
if (PySet_Discard(comp_free, k) < 0) {
850+
return 0;
851+
}
852+
}
848853
if (_PyUnicode_EqualToASCIIString(k, "__class__")) {
849854
remove_dunder_class = 1;
850855
}

0 commit comments

Comments
 (0)