From ad56c5fbc2c598ead76bc9a1bbbd31b9287a193e Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 17 Apr 2024 18:03:20 -0700 Subject: [PATCH 1/6] Desired behavior --- Lib/test/test_type_params.py | 44 ++++++++++++++++++++++-------------- Python/symtable.c | 12 ---------- 2 files changed, 27 insertions(+), 29 deletions(-) diff --git a/Lib/test/test_type_params.py b/Lib/test/test_type_params.py index fbb80d9aac9942..cbb576be8126f2 100644 --- a/Lib/test/test_type_params.py +++ b/Lib/test/test_type_params.py @@ -436,9 +436,11 @@ class C[T]: 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 C[T]: 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 C[T]: 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,)]", + "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: 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/Python/symtable.c b/Python/symtable.c index 483ef1c3c46542..47a92eb6bf0504 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -2589,18 +2589,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)); From 5ade8d4836a23567eddff7251ae385c502f7d981 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 17 Apr 2024 18:12:41 -0700 Subject: [PATCH 2/6] not correct --- Python/compile.c | 5 ++++- Python/symtable.c | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Python/compile.c b/Python/compile.c index 3d856b7e4ddd97..4ade34d104d7a8 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -371,6 +371,9 @@ static PyCodeObject *optimize_and_assemble(struct compiler *, int addNone); #define CAPSULE_NAME "compile.c compiler unit" +static inline bool in_class_like_block(struct compiler *c) { + return c->u->u_ste->ste_type == ClassBlock || c->u->u_ste->ste_can_see_class_scope; +} static int compiler_setup(struct compiler *c, mod_ty mod, PyObject *filename, @@ -5460,7 +5463,7 @@ push_inlined_comprehension_state(struct compiler *c, location loc, PySTEntryObject *entry, inlined_comprehension_state *state) { - int in_class_block = (c->u->u_ste->ste_type == ClassBlock) && !c->u->u_in_inlined_comp; + int in_class_block = in_class_like_block(c) && !c->u->u_in_inlined_comp; c->u->u_in_inlined_comp++; // iterate over names bound in the comprehension and ensure we isolate // them from the outer scope as needed diff --git a/Python/symtable.c b/Python/symtable.c index 47a92eb6bf0504..4a369819231fbe 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -810,7 +810,8 @@ inline_comprehension(PySTEntryObject *ste, PySTEntryObject *comp, // free vars in comprehension that are locals in outer scope can // now simply be locals, unless they are free in comp children, // or if the outer scope is a class block - if (!is_free_in_any_child(comp, k) && ste->ste_type != ClassBlock) { + if (!is_free_in_any_child(comp, k) && ste->ste_type != ClassBlock + && !ste->ste_can_see_class_scope) { if (PySet_Discard(comp_free, k) < 0) { return 0; } From ba90c9764ba62b288ef0b262ff3b57ecb9e16161 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 25 Apr 2024 21:03:42 -0700 Subject: [PATCH 3/6] Revert "not correct" This reverts commit 5ade8d4836a23567eddff7251ae385c502f7d981. --- Python/compile.c | 5 +---- Python/symtable.c | 3 +-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/Python/compile.c b/Python/compile.c index 4ade34d104d7a8..3d856b7e4ddd97 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -371,9 +371,6 @@ static PyCodeObject *optimize_and_assemble(struct compiler *, int addNone); #define CAPSULE_NAME "compile.c compiler unit" -static inline bool in_class_like_block(struct compiler *c) { - return c->u->u_ste->ste_type == ClassBlock || c->u->u_ste->ste_can_see_class_scope; -} static int compiler_setup(struct compiler *c, mod_ty mod, PyObject *filename, @@ -5463,7 +5460,7 @@ push_inlined_comprehension_state(struct compiler *c, location loc, PySTEntryObject *entry, inlined_comprehension_state *state) { - int in_class_block = in_class_like_block(c) && !c->u->u_in_inlined_comp; + int in_class_block = (c->u->u_ste->ste_type == ClassBlock) && !c->u->u_in_inlined_comp; c->u->u_in_inlined_comp++; // iterate over names bound in the comprehension and ensure we isolate // them from the outer scope as needed diff --git a/Python/symtable.c b/Python/symtable.c index 4a369819231fbe..47a92eb6bf0504 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -810,8 +810,7 @@ inline_comprehension(PySTEntryObject *ste, PySTEntryObject *comp, // free vars in comprehension that are locals in outer scope can // now simply be locals, unless they are free in comp children, // or if the outer scope is a class block - if (!is_free_in_any_child(comp, k) && ste->ste_type != ClassBlock - && !ste->ste_can_see_class_scope) { + if (!is_free_in_any_child(comp, k) && ste->ste_type != ClassBlock) { if (PySet_Discard(comp_free, k) < 0) { return 0; } From 1a9b8eda9f4782534ec3930039a2e2633c677a8f Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 25 Apr 2024 21:18:31 -0700 Subject: [PATCH 4/6] Do not inline them --- .../2024-04-25-21-18-19.gh-issue-118160.GH5SMc.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-04-25-21-18-19.gh-issue-118160.GH5SMc.rst 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 00000000000000..c4e798df5de702 --- /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 ` within classes can now contain +comprehensions. However, such comprehensions are not inlined into their +parent scope at runtime. Patch by Jelle Zijlstra. From 79e9332f87e5f4a051d03eb52e1dae795d9ae18d Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 25 Apr 2024 21:19:12 -0700 Subject: [PATCH 5/6] Rename "error_cases" --- Doc/whatsnew/3.13.rst | 4 +++- Lib/test/test_type_params.py | 4 ++-- Python/symtable.c | 6 ++++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index ad107aad5db3bd..0cd8b4784e712c 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 ` 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 inliked 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 cbb576be8126f2..4b86395ee74f75 100644 --- a/Lib/test/test_type_params.py +++ b/Lib/test/test_type_params.py @@ -491,13 +491,13 @@ class C: T = "class" {} """ - error_cases = [ + 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): ns = run_code(code.format(case)) alias = ns["C"].Alias diff --git a/Python/symtable.c b/Python/symtable.c index 47a92eb6bf0504..eecd159b2c3f17 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)) From 9e6ade7446bffa32eb39ac4f7903a2fd3a9d4add Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Fri, 26 Apr 2024 16:46:10 -0700 Subject: [PATCH 6/6] Update Doc/whatsnew/3.13.rst Co-authored-by: Carl Meyer --- Doc/whatsnew/3.13.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 0cd8b4784e712c..13252d99de6c68 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -277,7 +277,7 @@ Other Language Changes * :ref:`annotation scope ` within class scopes can now contain lambdas and comprehensions. Comprehensions that are located within - class scopes are not inliked into their parent scope. (Contributed by + class scopes are not inlined into their parent scope. (Contributed by Jelle Zijlstra in :gh:`109118` and :gh:`118160`.)