Skip to content

Commit 7c2656e

Browse files
committed
is_free_in_any_child checks children of nested comprehensions
1 parent 90b6e28 commit 7c2656e

2 files changed

Lines changed: 46 additions & 0 deletions

File tree

‎Lib/test/test_listcomps.py‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,44 @@ def test_nested_references_super(self):
512512
"""
513513
self._check_in_scopes(code, outputs={"res": [[super]]})
514514

515+
def test_nested_inlined_super_does_not_require_class_cell(self):
516+
# Nested inlined comps compile super/__class__ as global lookups.
517+
# They must not inject __classcell__ the way a nested function would.
518+
class Meta(type):
519+
def __new__(mcls, name, bases, ns):
520+
ns.pop('__classcell__', None)
521+
return type.__new__(mcls, name, bases, ns)
522+
523+
cases = [
524+
("[[super for _ in (0,)] for _ in (0,)]", [[super]]),
525+
("[{super for _ in (0,)} for _ in (0,)]", [{super}]),
526+
("{0: {1: super for _ in (0,)} for _ in (0,)}", {0: {1: super}}),
527+
]
528+
for expr, expected in cases:
529+
with self.subTest(expr=expr):
530+
ns = {"Meta": Meta}
531+
exec(f"class C(metaclass=Meta):\n result = {expr}", ns)
532+
self.assertEqual(ns["C"].result, expected)
533+
534+
def test_nested_inlined_lambda_class_ref_requires_class_cell(self):
535+
class Meta(type):
536+
def __new__(mcls, name, bases, ns):
537+
ns.pop('__classcell__', None)
538+
return type.__new__(mcls, name, bases, ns)
539+
540+
exprs = [
541+
"[[lambda: __class__ for _ in (0,)] for _ in (0,)]",
542+
"[{lambda: __class__ for _ in (0,)} for _ in (0,)]",
543+
"{0: {1: (lambda: __class__) for _ in (0,)} for _ in (0,)}",
544+
]
545+
for expr in exprs:
546+
with self.subTest(expr=expr):
547+
with self.assertRaisesRegex(
548+
RuntimeError,
549+
r"__class__ not set.*__classcell__ propagated"):
550+
exec(f"class C(metaclass=Meta):\n result = {expr}",
551+
{"Meta": Meta})
552+
515553
def test_nested_2(self):
516554
code = """
517555
l = [1, 2, 3]

‎Python/symtable.c‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -805,6 +805,14 @@ is_free_in_any_child(PySTEntryObject *entry, PyObject *key)
805805
for (Py_ssize_t i = 0; i < PyList_GET_SIZE(entry->ste_children); i++) {
806806
PySTEntryObject *child_ste = (PySTEntryObject *)PyList_GET_ITEM(
807807
entry->ste_children, i);
808+
if (child_ste->ste_type == InlinedComprehensionBlock) {
809+
/* this is the same scope, so we check its children */
810+
int nested = is_free_in_any_child(child_ste, key);
811+
if (nested != 0) {
812+
return nested;
813+
}
814+
continue;
815+
}
808816
long scope = _PyST_GetScope(child_ste, key);
809817
if (scope < 0) {
810818
return -1;

0 commit comments

Comments
 (0)