gh-109118: Make comprehensions work within annotation scopes, but without inlining (#118160)

Co-authored-by: Carl Meyer <carl@oddbird.net>
This commit is contained in:
Jelle Zijlstra 2024-04-28 06:21:28 -07:00 committed by GitHub
parent 51aefc5bf9
commit 2326d6c868
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 39 additions and 34 deletions

View File

@ -276,7 +276,9 @@ Other Language Changes
(Contributed by Pedro Sousa Lacerda in :gh:`66449`.)
* :ref:`annotation scope <annotation-scopes>` within class scopes can now
contain lambdas. (Contributed by Jelle Zijlstra in :gh:`109118`.)
contain lambdas and comprehensions. Comprehensions that are located within
class scopes are not inlined into their parent scope. (Contributed by
Jelle Zijlstra in :gh:`109118` and :gh:`118160`.)
New Modules

View File

@ -436,9 +436,11 @@ class TypeParamsAccessTest(unittest.TestCase):
class Inner[U](make_base(T for _ in (1,)), make_base(T)):
pass
"""
with self.assertRaisesRegex(SyntaxError,
"Cannot use comprehension in annotation scope within class scope"):
run_code(code)
ns = run_code(code)
inner = ns["C"].Inner
base1, base2, _ = inner.__bases__
self.assertEqual(list(base1.__arg__), [ns["C"].__type_params__[0]])
self.assertEqual(base2.__arg__, "class")
def test_listcomp_in_nested_class(self):
code = """
@ -464,9 +466,11 @@ class TypeParamsAccessTest(unittest.TestCase):
class Inner[U](make_base([T for _ in (1,)]), make_base(T)):
pass
"""
with self.assertRaisesRegex(SyntaxError,
"Cannot use comprehension in annotation scope within class scope"):
run_code(code)
ns = run_code(code)
inner = ns["C"].Inner
base1, base2, _ = inner.__bases__
self.assertEqual(base1.__arg__, [ns["C"].__type_params__[0]])
self.assertEqual(base2.__arg__, "class")
def test_gen_exp_in_generic_method(self):
code = """
@ -475,27 +479,33 @@ class TypeParamsAccessTest(unittest.TestCase):
def meth[U](x: (T for _ in (1,)), y: T):
pass
"""
with self.assertRaisesRegex(SyntaxError,
"Cannot use comprehension in annotation scope within class scope"):
run_code(code)
ns = run_code(code)
meth = ns["C"].meth
self.assertEqual(list(meth.__annotations__["x"]), [ns["C"].__type_params__[0]])
self.assertEqual(meth.__annotations__["y"], "class")
def test_nested_scope_in_generic_alias(self):
code = """
class C[T]:
T = "global"
class C:
T = "class"
{}
"""
error_cases = [
"type Alias3[T] = (T for _ in (1,))",
"type Alias4 = (T for _ in (1,))",
"type Alias5[T] = [T for _ in (1,)]",
"type Alias6 = [T for _ in (1,)]",
cases = [
"type Alias[T] = (T for _ in (1,))",
"type Alias = (T for _ in (1,))",
"type Alias[T] = [T for _ in (1,)]",
"type Alias = [T for _ in (1,)]",
]
for case in error_cases:
for case in cases:
with self.subTest(case=case):
with self.assertRaisesRegex(SyntaxError,
r"Cannot use [a-z]+ in annotation scope within class scope"):
run_code(code.format(case))
ns = run_code(code.format(case))
alias = ns["C"].Alias
value = list(alias.__value__)[0]
if alias.__type_params__:
self.assertIs(value, alias.__type_params__[0])
else:
self.assertEqual(value, "global")
def test_lambda_in_alias_in_class(self):
code = """

View File

@ -0,0 +1,3 @@
:ref:`Annotation scopes <annotation-scopes>` within classes can now contain
comprehensions. However, such comprehensions are not inlined into their
parent scope at runtime. Patch by Jelle Zijlstra.

View File

@ -1154,10 +1154,12 @@ analyze_block(PySTEntryObject *ste, PyObject *bound, PyObject *free,
}
}
// we inline all non-generator-expression comprehensions
// we inline all non-generator-expression comprehensions,
// except those in annotation scopes that are nested in classes
int inline_comp =
entry->ste_comprehension &&
!entry->ste_generator;
!entry->ste_generator &&
!ste->ste_can_see_class_scope;
if (!analyze_child_block(entry, newbound, newfree, newglobal,
type_params, new_class_entry, &child_free))
@ -2589,18 +2591,6 @@ symtable_handle_comprehension(struct symtable *st, expr_ty e,
identifier scope_name, asdl_comprehension_seq *generators,
expr_ty elt, expr_ty value)
{
if (st->st_cur->ste_can_see_class_scope) {
// gh-109118
PyErr_Format(PyExc_SyntaxError,
"Cannot use comprehension in annotation scope within class scope");
PyErr_RangedSyntaxLocationObject(st->st_filename,
e->lineno,
e->col_offset + 1,
e->end_lineno,
e->end_col_offset + 1);
VISIT_QUIT(st, 0);
}
int is_generator = (e->kind == GeneratorExp_kind);
comprehension_ty outermost = ((comprehension_ty)
asdl_seq_GET(generators, 0));