Skip to content

Commit 2326d6c

Browse files
gh-109118: Make comprehensions work within annotation scopes, but without inlining (#118160)
Co-authored-by: Carl Meyer <[email protected]>
1 parent 51aefc5 commit 2326d6c

File tree

4 files changed

+39
-34
lines changed

4 files changed

+39
-34
lines changed

Doc/whatsnew/3.13.rst

+3-1
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,9 @@ Other Language Changes
276276
(Contributed by Pedro Sousa Lacerda in :gh:`66449`.)
277277

278278
* :ref:`annotation scope <annotation-scopes>` within class scopes can now
279-
contain lambdas. (Contributed by Jelle Zijlstra in :gh:`109118`.)
279+
contain lambdas and comprehensions. Comprehensions that are located within
280+
class scopes are not inlined into their parent scope. (Contributed by
281+
Jelle Zijlstra in :gh:`109118` and :gh:`118160`.)
280282

281283

282284
New Modules

Lib/test/test_type_params.py

+29-19
Original file line numberDiff line numberDiff line change
@@ -436,9 +436,11 @@ class C[T]:
436436
class Inner[U](make_base(T for _ in (1,)), make_base(T)):
437437
pass
438438
"""
439-
with self.assertRaisesRegex(SyntaxError,
440-
"Cannot use comprehension in annotation scope within class scope"):
441-
run_code(code)
439+
ns = run_code(code)
440+
inner = ns["C"].Inner
441+
base1, base2, _ = inner.__bases__
442+
self.assertEqual(list(base1.__arg__), [ns["C"].__type_params__[0]])
443+
self.assertEqual(base2.__arg__, "class")
442444

443445
def test_listcomp_in_nested_class(self):
444446
code = """
@@ -464,9 +466,11 @@ class C[T]:
464466
class Inner[U](make_base([T for _ in (1,)]), make_base(T)):
465467
pass
466468
"""
467-
with self.assertRaisesRegex(SyntaxError,
468-
"Cannot use comprehension in annotation scope within class scope"):
469-
run_code(code)
469+
ns = run_code(code)
470+
inner = ns["C"].Inner
471+
base1, base2, _ = inner.__bases__
472+
self.assertEqual(base1.__arg__, [ns["C"].__type_params__[0]])
473+
self.assertEqual(base2.__arg__, "class")
470474

471475
def test_gen_exp_in_generic_method(self):
472476
code = """
@@ -475,27 +479,33 @@ class C[T]:
475479
def meth[U](x: (T for _ in (1,)), y: T):
476480
pass
477481
"""
478-
with self.assertRaisesRegex(SyntaxError,
479-
"Cannot use comprehension in annotation scope within class scope"):
480-
run_code(code)
482+
ns = run_code(code)
483+
meth = ns["C"].meth
484+
self.assertEqual(list(meth.__annotations__["x"]), [ns["C"].__type_params__[0]])
485+
self.assertEqual(meth.__annotations__["y"], "class")
481486

482487
def test_nested_scope_in_generic_alias(self):
483488
code = """
484-
class C[T]:
489+
T = "global"
490+
class C:
485491
T = "class"
486492
{}
487493
"""
488-
error_cases = [
489-
"type Alias3[T] = (T for _ in (1,))",
490-
"type Alias4 = (T for _ in (1,))",
491-
"type Alias5[T] = [T for _ in (1,)]",
492-
"type Alias6 = [T for _ in (1,)]",
494+
cases = [
495+
"type Alias[T] = (T for _ in (1,))",
496+
"type Alias = (T for _ in (1,))",
497+
"type Alias[T] = [T for _ in (1,)]",
498+
"type Alias = [T for _ in (1,)]",
493499
]
494-
for case in error_cases:
500+
for case in cases:
495501
with self.subTest(case=case):
496-
with self.assertRaisesRegex(SyntaxError,
497-
r"Cannot use [a-z]+ in annotation scope within class scope"):
498-
run_code(code.format(case))
502+
ns = run_code(code.format(case))
503+
alias = ns["C"].Alias
504+
value = list(alias.__value__)[0]
505+
if alias.__type_params__:
506+
self.assertIs(value, alias.__type_params__[0])
507+
else:
508+
self.assertEqual(value, "global")
499509

500510
def test_lambda_in_alias_in_class(self):
501511
code = """
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:ref:`Annotation scopes <annotation-scopes>` within classes can now contain
2+
comprehensions. However, such comprehensions are not inlined into their
3+
parent scope at runtime. Patch by Jelle Zijlstra.

Python/symtable.c

+4-14
Original file line numberDiff line numberDiff line change
@@ -1154,10 +1154,12 @@ analyze_block(PySTEntryObject *ste, PyObject *bound, PyObject *free,
11541154
}
11551155
}
11561156

1157-
// we inline all non-generator-expression comprehensions
1157+
// we inline all non-generator-expression comprehensions,
1158+
// except those in annotation scopes that are nested in classes
11581159
int inline_comp =
11591160
entry->ste_comprehension &&
1160-
!entry->ste_generator;
1161+
!entry->ste_generator &&
1162+
!ste->ste_can_see_class_scope;
11611163

11621164
if (!analyze_child_block(entry, newbound, newfree, newglobal,
11631165
type_params, new_class_entry, &child_free))
@@ -2589,18 +2591,6 @@ symtable_handle_comprehension(struct symtable *st, expr_ty e,
25892591
identifier scope_name, asdl_comprehension_seq *generators,
25902592
expr_ty elt, expr_ty value)
25912593
{
2592-
if (st->st_cur->ste_can_see_class_scope) {
2593-
// gh-109118
2594-
PyErr_Format(PyExc_SyntaxError,
2595-
"Cannot use comprehension in annotation scope within class scope");
2596-
PyErr_RangedSyntaxLocationObject(st->st_filename,
2597-
e->lineno,
2598-
e->col_offset + 1,
2599-
e->end_lineno,
2600-
e->end_col_offset + 1);
2601-
VISIT_QUIT(st, 0);
2602-
}
2603-
26042594
int is_generator = (e->kind == GeneratorExp_kind);
26052595
comprehension_ty outermost = ((comprehension_ty)
26062596
asdl_seq_GET(generators, 0));

0 commit comments

Comments
 (0)