summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/whatsnew/3.13.rst4
-rw-r--r--Lib/test/test_type_params.py48
-rw-r--r--Misc/NEWS.d/next/Core and Builtins/2024-04-25-21-18-19.gh-issue-118160.GH5SMc.rst3
-rw-r--r--Python/symtable.c18
4 files changed, 39 insertions, 34 deletions
diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst
index 083a70c..98349a5 100644
--- a/Doc/whatsnew/3.13.rst
+++ b/Doc/whatsnew/3.13.rst
@@ -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
diff --git a/Lib/test/test_type_params.py b/Lib/test/test_type_params.py
index fbb80d9..4b86395 100644
--- a/Lib/test/test_type_params.py
+++ b/Lib/test/test_type_params.py
@@ -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 = """
diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-04-25-21-18-19.gh-issue-118160.GH5SMc.rst b/Misc/NEWS.d/next/Core and Builtins/2024-04-25-21-18-19.gh-issue-118160.GH5SMc.rst
new file mode 100644
index 0000000..c4e798d
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2024-04-25-21-18-19.gh-issue-118160.GH5SMc.rst
@@ -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.
diff --git a/Python/symtable.c b/Python/symtable.c
index 483ef1c..eecd159 100644
--- a/Python/symtable.c
+++ b/Python/symtable.c
@@ -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));