mirror of https://github.com/python/cpython
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:
parent
51aefc5bf9
commit
2326d6c868
|
@ -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
|
||||
|
|
|
@ -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 = """
|
||||
|
|
|
@ -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.
|
|
@ -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));
|
||||
|
|
Loading…
Reference in New Issue