]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-109118: Make comprehensions work within annotation scopes, but without inlining...
authorJelle Zijlstra <jelle.zijlstra@gmail.com>
Sun, 28 Apr 2024 13:21:28 +0000 (06:21 -0700)
committerGitHub <noreply@github.com>
Sun, 28 Apr 2024 13:21:28 +0000 (06:21 -0700)
Co-authored-by: Carl Meyer <carl@oddbird.net>
Doc/whatsnew/3.13.rst
Lib/test/test_type_params.py
Misc/NEWS.d/next/Core and Builtins/2024-04-25-21-18-19.gh-issue-118160.GH5SMc.rst [new file with mode: 0644]
Python/symtable.c

index 083a70ce2405e3986d5f0bd7955c2b1d036783e5..98349a5984bb7eaa8f1ec884280097cd1a71fdc5 100644 (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
index fbb80d9aac99421b00f7285877a046f8973172e5..4b86395ee74f75300f6cb034ab6422dd5c00c6c3 100644 (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 = """
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 (file)
index 0000000..c4e798d
--- /dev/null
@@ -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.
index 483ef1c3c465422840a3bd88a2bddf1ce03486c7..eecd159b2c3f174b7e130b826b33f6e44aa11ae8 100644 (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));