From fe37499b481b29608c7ab18a1e7b321426fa4162 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 1 Sep 2025 13:54:50 -0700 Subject: [PATCH 1/3] gh-138349: Fix crash when combining module-level annotation and listcomp --- Lib/test/test_type_annotations.py | 37 +++++++++++++++++++ ...-09-01-13-54-43.gh-issue-138349.0fGmAi.rst | 2 + Objects/setobject.c | 1 + Python/codegen.c | 10 +++-- 4 files changed, 46 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-09-01-13-54-43.gh-issue-138349.0fGmAi.rst diff --git a/Lib/test/test_type_annotations.py b/Lib/test/test_type_annotations.py index 1415bbca22707c..d40eb382c536d9 100644 --- a/Lib/test/test_type_annotations.py +++ b/Lib/test/test_type_annotations.py @@ -835,3 +835,40 @@ def test_complex_comprehension_inlining_exec(self): genexp = annos["unique_name_2"][0] lamb = list(genexp)[0] self.assertEqual(lamb(), 42) + + # gh-138349 + def test_module_level_annotation_plus_listcomp(self): + cases = [ + """ + def report_error(): + pass + try: + [0 for name_2 in unique_name_0 if (lambda: name_2)] + except: + pass + annotated_name: 0 + """, + """ + class Generic: + pass + try: + [0 for name_2 in unique_name_0 if (0 for unique_name_1 in unique_name_2 for unique_name_3 in name_2)] + except: + pass + annotated_name: 0 + """, + """ + class Generic: + pass + annotated_name: 0 + try: + [0 for name_2 in [[0]] for unique_name_1 in unique_name_2 if (lambda: name_2)] + except: + pass + """, + ] + for code in cases: + with self.subTest(code=code): + mod = build_module(code) + annos = mod.__annotations__ + self.assertEqual(annos, {"annotated_name": 0}) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-09-01-13-54-43.gh-issue-138349.0fGmAi.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-09-01-13-54-43.gh-issue-138349.0fGmAi.rst new file mode 100644 index 00000000000000..9b94eec38d9625 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-09-01-13-54-43.gh-issue-138349.0fGmAi.rst @@ -0,0 +1,2 @@ +Fix crash in certain cases where a module contains both a module-level +annotation and a comprehension. diff --git a/Objects/setobject.c b/Objects/setobject.c index d8340499be5aae..577a898c931579 100644 --- a/Objects/setobject.c +++ b/Objects/setobject.c @@ -142,6 +142,7 @@ set_add_entry_takeref(PySetObject *so, PyObject *key, Py_hash_t hash) size_t i; /* Unsigned for defined overflow behavior */ int probes; int cmp; + assert(PySet_CheckExact(so) || PyFrozenSet_CheckExact(so)); restart: diff --git a/Python/codegen.c b/Python/codegen.c index 53388592330a5d..dd6c6d82552518 100644 --- a/Python/codegen.c +++ b/Python/codegen.c @@ -5492,10 +5492,12 @@ codegen_annassign(compiler *c, stmt_ty s) RETURN_IF_ERROR(_PyCompile_AddDeferredAnnotation( c, s, &conditional_annotation_index)); if (conditional_annotation_index != NULL) { - ADDOP_NAME( - c, loc, - SCOPE_TYPE(c) == COMPILE_SCOPE_CLASS ? LOAD_DEREF : LOAD_NAME, - &_Py_ID(__conditional_annotations__), cellvars); + if (SCOPE_TYPE(c) == COMPILE_SCOPE_CLASS) { + ADDOP_NAME(c, loc, LOAD_DEREF, &_Py_ID(__conditional_annotations__), cellvars); + } + else { + ADDOP_NAME(c, loc, LOAD_NAME, &_Py_ID(__conditional_annotations__), names); + } ADDOP_LOAD_CONST_NEW(c, loc, conditional_annotation_index); ADDOP_I(c, loc, SET_ADD, 1); ADDOP(c, loc, POP_TOP); From 38a868c15006286dc8106239be1a195268853d54 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 1 Sep 2025 14:32:12 -0700 Subject: [PATCH 2/3] drop the assertion --- Objects/setobject.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Objects/setobject.c b/Objects/setobject.c index 577a898c931579..d8340499be5aae 100644 --- a/Objects/setobject.c +++ b/Objects/setobject.c @@ -142,7 +142,6 @@ set_add_entry_takeref(PySetObject *so, PyObject *key, Py_hash_t hash) size_t i; /* Unsigned for defined overflow behavior */ int probes; int cmp; - assert(PySet_CheckExact(so) || PyFrozenSet_CheckExact(so)); restart: From a9d175d7644f0a6d1d499d63930ddb120da77c8b Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 9 Sep 2025 08:27:43 -0700 Subject: [PATCH 3/3] magic --- Include/internal/pycore_magic_number.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Include/internal/pycore_magic_number.h b/Include/internal/pycore_magic_number.h index 81bfd162c7ea80..7ec7bd1c695516 100644 --- a/Include/internal/pycore_magic_number.h +++ b/Include/internal/pycore_magic_number.h @@ -279,11 +279,13 @@ Known values: Python 3.14b1 3624 (Don't optimize LOAD_FAST when local is killed by DELETE_FAST) Python 3.14b3 3625 (Fix handling of opcodes that may leave operands on the stack when optimizing LOAD_FAST) Python 3.14rc2 3626 (Fix missing exception handlers in logical expression) + Python 3.14rc3 3627 (Fix miscompilation of some module-level annotations) Python 3.15a0 3650 (Initial version) Python 3.15a1 3651 (Simplify LOAD_CONST) Python 3.15a1 3652 (Virtual iterators) Python 3.15a1 3653 (Fix handling of opcodes that may leave operands on the stack when optimizing LOAD_FAST) Python 3.15a1 3654 (Fix missing exception handlers in logical expression) + Python 3.15a1 3655 (Fix miscompilation of some module-level annotations) Python 3.16 will start with 3700 @@ -297,7 +299,7 @@ PC/launcher.c must also be updated. */ -#define PYC_MAGIC_NUMBER 3654 +#define PYC_MAGIC_NUMBER 3655 /* This is equivalent to converting PYC_MAGIC_NUMBER to 2 bytes (little-endian) and then appending b'\r\n'. */ #define PYC_MAGIC_NUMBER_TOKEN \